前面已经过完了类加载的过程,接下来是类的初始化过程,主要分为三个步骤,分别是
1、vertification
验证文件是否符合JVM规范
2、preparation
静态成员变量默认值
3、resolution
将类、方法、属性等符号引用解析为直接引用
常量池中的各种符号引用解析为指针,偏移量等内存地址的直接引用

4、initializing
静态变量初始化
public class T001_ClassLoadingProcedure {
public static void main(String[] args) {
System.out.println(T.count);
}
}
class T {
/*
public static int count = 2;
public static T t = new T();
此方式count = 3,另外一种输出count=2
*/
//初始化过程首先调用new T() count++,count = 1,然后第二步赋值为2
public static T t = new T(); // null
public static int count = 2; //0
//private int m = 8;
private T() {
count ++;
//System.out.println("--" + count);
}
}
new 一个对象T,里面有成员变量m,第一步先给这个对象申请内存空间,然后先给这里面的成员变量
先赋一个默认值值0,然后调用构造方法给成员变量赋值一个初始值8
第一步new是申请内存赋默认值
invokespecial调用构造方法赋初始值
astore_1赋值引用
其上指令可能会重排序,所以单例的DCL需要加上volatile修饰
Object o = new Object() 分三步
1.new 分配空间
2.invokespecial 调用构造方法
3.astore_1将对象赋给o
乱序指的就是比如像2,3这两条无关的命令之间可能存在乱序, 假如线程先执行了3, 再执行2,就会导致对象初始化到一半时线程就挂起了,当另一个线程来的时候, o不为空,直接返回一个有问题的对象

Java内存模型(Java Memory Model简称JMM)是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,前面说过,工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

CPU读取数据的时候先去离CPU最近的L1里面去找,如果没有逐步向下去找,然后向上缓存,

底层多个cpu之间的缓存一致性则通过缓存一致性协议去保证。
参考博客:https://www.cnblogs.com/z00377750/p/9180644.html

现在的CPU的底层的一致性是通过缓存一致性协加上总线锁来实现的。
CPU读取数据是按缓存行去读取的数据,CPU的多数缓存行为64个字节,
假如有两个数x,y,一个CPU是只要x而另一个CPU只要y,但是他们同时读入了
x,y这两个数。就会导致其中一个CPU将x的值改了,就要通过缓存一致性协议去通知
另一个CPU重新读取x的值【重新读取了缓存行数据】。

CPU为了提高效率会打乱原有的执行顺序,会在一条指令执行的过程中(比如从
内存读数据)去同时执行另一条指令,前提是,两条指令没有依赖关系。
参考链接: https://www.cnblogs.com/liushaodong/p/4777308.html
写指令也可能是乱序的
WCBuffer(write combing 合并写):CPU写操作进行合并,对一个数做多次更改合并最终的结果到下级的缓存
里面去。


//写时合并代码
public final class WriteCombining {
private static final int ITERATIONS = Integer.MAX_VALUE;
private static final int ITEMS = 1 << 24;
private static final int MASK = ITEMS - 1;
private static final byte[] arrayA = new byte[ITEMS];
private static final byte[] arrayB = new byte[ITEMS];
private static final byte[] arrayC = new byte[ITEMS];
private static final byte[] arrayD = new byte[ITEMS];
private static final byte[] arrayE = new byte[ITEMS];
private static final byte[] arrayF = new byte[ITEMS];
public static void main(final String[] args) {
for (int i = 1; i <= 3; i++) {
System.out.println(i + " SingleLoop duration (ns) = " + runCaseOne());
System.out.println(i + " SplitLoop duration (ns) = " + runCaseTwo());
}
}
public static long runCaseOne() {
long start = System.nanoTime();
int i = ITERATIONS;
//第一个是一起改
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayA[slot] = b;
arrayB[slot] = b;
arrayC[slot] = b;
arrayD[slot] = b;
arrayE[slot] = b;
arrayF[slot] = b;
}
return System.nanoTime() - start;
}
//第二个是分成两次做同样的操作
public static long runCaseTwo() {
long start = System.nanoTime();
int i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayA[slot] = b;
arrayB[slot] = b;
arrayC[slot] = b;
}
i = ITERATIONS;
while (--i != 0) {
int slot = i & MASK;
byte b = (byte) i;
arrayD[slot] = b;
arrayE[slot] = b;
arrayF[slot] = b;
}
return System.nanoTime() - start;
}
}
public class T01_Disorder {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
for (long i = 0; i < Long.MAX_VALUE; i++) {
x = 0;
y = 0;
a = 0;
b = 0;
CountDownLatch latch = new CountDownLatch(2);
Thread one = new Thread(new Runnable() {
public void run() {
a = 1;
x = b;
latch.countDown();
}
});
Thread other = new Thread(new Runnable() {
public void run() {
b = 1;
y = a;
latch.countDown();
}
});
one.start();
other.start();
latch.await();
String result = "第" + i + "次 (" + x + "," + y + ")";
if (x == 0 && y == 0) {
System.err.println(result);
break;
}
}
}
}
为了保证特定情况下的有序性:volatile
加CPU级别的内存屏障:
s:save
l:load
m:mix

lock指令后面有一条指令,当我这个指令执行完成之前,你lock对某个内存是锁定的

属于JSR规范里面的,由JVM自身实现的

加了一个ACC_VOLATILE标记

在JVM读取到这个ACC_VOLATILE这个access_flag层面的时候,
对所有的写操作的前面加了一个StoreStoreBarrier,在后面加了一个StoreLoadBarrier
对所有的读操作的前面加了一个LoadLoadBarrier,在后面加了一个LoadStoreBarrier

使用hsdis -HotSpot Dis Assembler(观察虚拟机编译好的字节码在CPU级别是用什么指令来完成的)
发现是lock指令xxx执行xxx指令的时候保证对内存区域加锁。
windows上采用lock指令实现的
参考博客:https://blog.csdn.net/qq_26222859/article/details/52235930
以如下代码为例:
public class TestSync {
synchronized void m() {
}
void n() {
synchronized (this) {
}
}
public static void main(String[] args) {
}
}
先来看m方法,就是加了一个acc_flag的标记ACC_SYNCHRONIZED:

再来看n方法:
会看到这样一个指令
monitorenter:监视器进入
monitorexit:监视器退出
两个exit是为了当发现异常的时候自动退出

C/C++调用了操作系统提供的同步机制
通过lock指令实现:
x86:lock comxchg xxx
参考文章:https://blog.csdn.net/21aspnet/article/details/88571740
java语言的要求

不管如何重排序,单线程执行的结果不变
1、解释一下对象的创建过程?
分为如下几步:
(1)class loading
(2)class linking(verification,preparation,resolution)
(3)class initializing【静态语句块初始化】
(4)申请对象内存
(5)成员变量赋默认值
(6)调用构造方法 【成员变量顺序赋初始值,执行构造方法语句】
2、对象在内存中的存储布局?
普通对象:
(1)对象头 markword 占用8个字节
(2)ClassPointer指针:-xx:+UseCompressedClassPointers开启为四字节,不开启为8字节【
class的指针,指针指向class的对象(t.class)】
(3)实例数据(一般指成员变量):
1、引用类型:-XX:UseCompressedOops开启为4字节,不开启为8字节
Oops Ordinary Object Pointers
(4)Padding对齐,8的倍数【读取是按块来读的,这样增加效率】
数组对象:
(1)对象头 markword 占用8个字节
(2)ClassPointer指针:-xx:+UseCompressedClassPointers为四字节,不开启为8字节【
class的指针,指针指向class的对象(t.class)】
(3)数组长度:4字节
(4)数组数据
(5)对齐的8的倍数
3、Object的大小 new Object
16个字节
markword:8个字节
查看JVM的配置参数发现是用了类压缩指针的
所以classPointer是4个字节
再加上padding是4个字节
总的为16个字节

4、查看数组的大小: new int[]
首先markword 8个字节
然后是classpointer,压缩了4个字节
然后数组长度,4个字节
总共16个字节
实验发现当不压缩classpointer发现占用24个字节
markword 8个字节
classpointer 8个字节
数组长度 4个字节
再加上padding 4个
总共24个字节
5、下面看一个对象:
总共占用32个字节
private static class P {
//8 _markword
//4 _class pointer
int id; //4
//是个引用类型,正常占8,但是+UseCompressedOops【普通对象指针】开启了变成4个字节
String name; //4
int age; //4
byte b1; //1
byte b2; //1
Object o; //4
byte b3; //1
}
6、对象头具体包括什么?
来源于如下文件:

三位代表锁的状态
只有调用hashcode的时候才会被记录在对象头里面去
这里是32位的图:

轻量级锁:是在JVM栈上实现的一个锁,没有涉及到内核
重量级锁:调用内核的锁,消耗资源比较高
问题:为什么GC年龄默认为15?对象头最大4位年龄为15
参考链接:
https://cloud.tencent.com/developer/article/1480590
https://cloud.tencent.com/developer/article/1484167
https://cloud.tencent.com/developer/article/1485795
https://cloud.tencent.com/developer/article/1482500
7、对象怎么定位
如T t = new T();
1、句柄池【简介】(查找效率低,但是GC效率高)
其中的小t是怎么找到 new T()的呢,第一种方式就是通过句柄池,通过一个间接指针,
这个间接指针分为两个指针,第一个是指向它的对象,第二个是指向t.class【将classpointer单独拿出来了】
2、直接指针(hotspot用)查找效率高
就是通过指针直接指向那个对象,然后这个对象又指向t.class
8、对象怎么分配
首先尝试往栈里面放,栈能放下则放在则栈弹出对象消失,如果栈上放不下且对象大,
则直接分配到堆内存,如果不大会先在线程本地分配,线程本地分配不下则找eden区
