JVM学习笔记
JVM结构
对于运行时的数据区域,主要分为PC,虚拟机栈,本地方法栈,堆以及方法区。PC是当前线程执行的字节码的行号指示器。虚拟机栈是线程私有的,其每一个栈帧对应一个运行的方法。本地方法栈使用的是本地NATIVE方法。堆是线程共享的,主要存储对象的实例,也是GC的主要场所。方法区作为堆的逻辑分区,主要存储类信息,常量,静态变量,缓存代码等数据。以上四部分作为JVM内存结构,除此之外JVM还管理JVM以外的内存:直接内存和元数据区。
其中一些主要的主要存储的区域:
- 局部变量表:存储在虚拟机栈的栈帧中,其存储了基本数据类型变量,对象引用(reference)和返回地址。
- 堆:堆主要存储对象的实例信息
- 方法区:存放已经被JVM加载的类型信息,常量,静态变量。
- 运行时常量池:类的版本,方法等描述信息。
JVM的对象
对象的创建分为以下步骤:jvm接受到new的字节码,之后从常量池找到类的符号引用,如果该类未被加载则需要执行类的加载过程。之后为对象分配内存并进行必要设置,最后执行对象的初始化。
对象的内存分配与并发
JVM的内存分配方式有“指针碰撞”和“空闲列表”。
· 指针碰撞:所有未使用和使用的内存被规范地放在两边,则分配内存只需要移动边界指针即可。
· 空闲列表: 两者内存穿插使用,需要维护已使用内存块的列表。
其是否规整由GC Compact决定。
而在并发中创建对象非线程安全,需要执行以下策略之一:1、对分配空间进行同步处理 2、将内存分配在不同的空间,即每个线程预先分配一小块内存,成为本地线程分配缓存(TLAB)。
对象的内存布局
对象可分为三部分:对象头,实例数据,对象填充。其中对象头包含哈希码,GC年代,锁标记等信息(markword)和对对象元数据的引用(reference),而对象填充确保了对象总长度为8字节的整数倍。
对象的访问方式
对象在创建时会同时在堆和栈中同时分配内存,堆中存放对象的实际数据,而栈中存放指向该对象的指针。
对象的访问方式分为句柄访问和直接访问。句柄访问指堆中专门划分一块句柄区,该区域存放对象实例数据和类型数据的对应关系。而栈中使用是指针指向句柄。而直接访问指的是将对应关系存放到实际数据中,栈指向实际数据,只需要一次寻址就可以完成对象的访问。
对象的存活方式
在运行时对象有可能被内存回收,判断对象存活的方法有:
- 引用计数法:当引用数为0时回收,但会出现循环引用问题。
- 可达性分析法:所有和GC root有关联的对象都是有效对象。而GC root只包含栈和方法区中的引用对象,而不包括堆中的引用对象。
值得一提的是,对象如果被覆盖了finalize()方法,则会在被回收时被执行一次。
少量的常量或类也会被回收,但条件较苛刻。
GC算法
判断了上述的无效对象,无效类或常量后,接下来就需要进行垃圾回收。
标记清除法
遍历GC roots并标记所有的可达性节点(标记),从而清除内存中的非可达节点(清除)。但效率低且容易产生内存碎片。
复制算法
将空间平均分为两部分,当第一部分准备GC时将存活变量一并放到第二部分并清除第一部分即可。该方法不会产生碎片但导致了最大内存变为原来一半。
再次基础上,将内存分为了Eden,From Survior,To Survior三部分,分别为8:1:1。每次GC时把Eden和From Survior的存活对象放入To Survior即可,再清空Eden和From Survior。
但当存活对象超过10%时,还需要分配担保。
当存活对象过多时,会将其由分配担保划入老年代。
标记整理法
老年代中依旧遍历GC roots的可达性节点(标记),再移动对象使其连续排列(整理)。
分代收集法
将堆内部划分为新生代和老年代,分别执行相应算法:
· 新生代:复制算法
· 老年代:标记清除/整理法