深入JVM《二》
JVM:
1、PC寄存器
2、方法区:放一些 类的信息
3、java堆:(new出来的),所有线程共享。GC算法不同堆也不同。
分代GC,堆也分代。 分为 eden/s0/s1/tenured。
4、java栈:线程私有的。有一系列帧(方法的局部变量、操作数栈、常量池指针)组成。
java栈 - 局部变量表:包含函数参数和局部变量。
java栈 - 操作数栈: 每次操作,压栈出栈
java栈 - 栈上分配: 小对象(一般几十个bytes),在没有逃逸的情况下(没有别的线程使用),可以直接分配栈上。可以减轻GC压力
// 堆上分配,每次需要清理空间 // 在C++中,对象new出来都是堆上分配,如果不主动回收delete,会导致堆溢出内存泄漏。 Demo* demo = new Demo(); // 得到一个指针 delete demo; // 栈上分配,函数调用完成自动清理 // 而如果是下面这样写,则是栈上分配,用完后自己会回收。 Demo demo; // 直接得到一个对象引用,是一个局部变量。
在java中,我们可以借鉴:
-XX:-DoEscapeAnalysis 堆上分配 -XX:+DoEscapeAnalysis 栈上分配 -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
5、栈、堆、方法区 交互
6、Java的存储模型
内存模型:有一个全局的主内存、每一个线程都有自己的工作内存。工作内存存放主存中变量的值的拷贝
当数据从主内存复制到工作存储时,必须出现两个动作:第一,由主内存执行的读(read)操作;第二,由工作内存执行的相应的load操作;当数据从工作内存拷贝到主内存时,也出现两个操作:第一个,由工作内存执行的存储(store)操作;第二,由主内存执行的相应的写(write)操作
。
每一个操作都是原子的,即执行期间不会被中断
。但是两个操作之间不是原子的。
对于普通变量,一个线程中更新的值,不能马上反应在其他变量中
如果需要在其他线程中立即可见,需要使用 volatile 关键字
。
volatile可以保证可见性(一个线程修改了变量,其他线程可以立即知道)。
保证可见性的方法:volatile
、synchronized(unlock之前,写变量值回主存)
、final(一旦初始化完成,其他线程就可见)
有序性:在本线程内,操作都是有序的
。在线程外观察,操作都是无序的。(指令重排 或 主内存同步延时)
指令重排:提供效率,破坏线程间的有序性。
可以通过加synchronized关键字来保证有序性。
7、指令重排的基本原则
:
1、程序顺序原则:一个线程内保证语义的串行性
2、volatile规则:volatile变量的写,先发生于读
3、锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
4、传递性:A先于B,B先于C 那么A必然先于C
5、线程的start方法先于它的每一个动作
6、线程的所有操作先于线程的终结(Thread.join())
7、线程的中断(interrupt())先于被中断线程的代码
8、对象的构造函数执行结束先于finalize()方法
8、解释运行与编译运行(JIT)
解释运行
:
解释执行以解释方式运行字节码
解释执行的意思是:读一句执行一句
编译运行(JIT):
将字节码编译成机器码
直接执行机器码
运行时编译
编译后性能有数量级的提升
(至少快10倍以上)