本文主要讨论以下内容:
1. JVM启动流程
2. JVM基本结构
3. 内存模型
JVM启动流程
1. 我们在启动一个JAVA程序的时候一般是通过java命令启动。
2. 运行java命令后JVM会首先加载jvm.cfg文件
3. 通过cfg找到JVM.DLL
4. jvm.dll初始化jvm并获得JNIEnv接口用来findClass
5. 最后运行class里的main函数。
JVM基本结构
关于JVM内存结构可参考
点击打开链接
这里提一下java栈
1. 线程私有
2. 栈由一系列帧组成(因此Java栈也叫做帧栈)
3. 帧保存一个方法的局部变量、操作数栈、常量池指针
4. 每一次方法调用创建一个帧,并压栈
栈的局部变量表包含参数和局部变量
public class StackDemo {
public static int runStatic(int i,long l,float f,Object o ,byte b){
return 0;
}//对于类方法,参数或局部变量分别存储在栈
public int runInstance(char c,short s,boolean b){
return 0;
}//对于实例方法,栈的第一个指向的是对象本身
}
栈上分配
由于堆上分配会造成多次GC,因此建议在栈上进行分配,因为方法执行完后所有栈内的信息都会回收。但是对于较大的对象,或逃逸对象无法分配在栈上。
栈 堆 方法区的交互
概念:
1. 每一个线程都有一个工作内存和主存独立。工作内存存放变量的值拷贝。
2. 当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作
3. 每一个操作都是原子的,不会被中断
4. 对于一个普通变量,一个线程的值的更改不会马上反应在其他线程,如需要马上反应则应对该变量使用volatile关键字
5. 可见性。一个线程变量的修改,其他线程可以立即知道。
6. 保证可见性的方法。volatile, synchronized(unlock前写入主内存),final关键字
7. 有序性。在本线程中,所有的操作都是有序的。但在外部线程看来,操作都是无序的(指令重排)
8. 指令重排。
JVM为提高执行效率会对代码进行重排序。
写后读 a = 1;b = a;写一个变量之后,再读这个位置。
写后写 a = 1;a = 2;写一个变量之后,再写这个变量。
读后写 a = b;b = 1;读一个变量之后,再写这个变量。
以上语句不可重排
写后写 a = 1;a = 2;写一个变量之后,再写这个变量。
读后写 a = b;b = 1;读一个变量之后,再写这个变量。
以上语句不可重排
编译器不考虑多线程间的语义
可重排: a=1;b=2;//因为对于虚拟机来说a=1和b=2没有任何关联。
例: 指令重排破坏线程有序性
9. 重排序原则
程序顺序原则:一个线程内保证语义的串行性
volatile规则:volatile变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A先于B,B先于C 那么A必然先于C
线程的start方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程的中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于finalize()方法
volatile规则:volatile变量的写,先发生于读
锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
传递性:A先于B,B先于C 那么A必然先于C
线程的start方法先于它的每一个动作
线程的所有操作先于线程的终结(Thread.join())
线程的中断(interrupt())先于被中断线程的代码
对象的构造函数执行结束先于finalize()方法