JVM学习笔记

JVM结构

对于运行时的数据区域,主要分为PC,虚拟机栈,本地方法栈,堆以及方法区。PC是当前线程执行的字节码的行号指示器。虚拟机栈是线程私有的,其每一个栈帧对应一个运行的方法。本地方法栈使用的是本地NATIVE方法。堆是线程共享的,主要存储对象的实例,也是GC的主要场所。方法区作为堆的逻辑分区,主要存储类信息,常量,静态变量,缓存代码等数据。以上四部分作为JVM内存结构,除此之外JVM还管理JVM以外的内存:直接内存和元数据区

其中一些主要的主要存储的区域:

  • 局部变量表:存储在虚拟机栈的栈帧中,其存储了基本数据类型变量,对象引用(reference)和返回地址。
  • 堆:堆主要存储对象的实例信息
  • 方法区:存放已经被JVM加载的类型信息,常量,静态变量。
    • 运行时常量池:类的版本,方法等描述信息。

JVM的对象

对象的创建分为以下步骤:jvm接受到new的字节码,之后从常量池找到类的符号引用,如果该类未被加载则需要执行类的加载过程。之后为对象分配内存并进行必要设置,最后执行对象的初始化。

对象的内存分配与并发

JVM的内存分配方式有“指针碰撞”和“空闲列表”。

· 指针碰撞:所有未使用和使用的内存被规范地放在两边,则分配内存只需要移动边界指针即可。

· 空闲列表: 两者内存穿插使用,需要维护已使用内存块的列表。

其是否规整由GC Compact决定。

而在并发中创建对象非线程安全,需要执行以下策略之一:1、对分配空间进行同步处理 2、将内存分配在不同的空间,即每个线程预先分配一小块内存,成为本地线程分配缓存(TLAB)。

对象的内存布局

对象可分为三部分:对象头,实例数据,对象填充。其中对象头包含哈希码,GC年代,锁标记等信息(markword)和对对象元数据的引用(reference),而对象填充确保了对象总长度为8字节的整数倍。

对象的访问方式

对象在创建时会同时在堆和栈中同时分配内存,堆中存放对象的实际数据,而栈中存放指向该对象的指针

对象的访问方式分为句柄访问和直接访问。句柄访问指堆中专门划分一块句柄区,该区域存放对象实例数据和类型数据的对应关系。而栈中使用是指针指向句柄。而直接访问指的是将对应关系存放到实际数据中,栈指向实际数据,只需要一次寻址就可以完成对象的访问。

对象的存活方式

在运行时对象有可能被内存回收,判断对象存活的方法有:

  1. 引用计数法:当引用数为0时回收,但会出现循环引用问题。
  2. 可达性分析法:所有和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的可达性节点(标记),再移动对象使其连续排列(整理)。

分代收集法

将堆内部划分为新生代和老年代,分别执行相应算法:

· 新生代:复制算法

· 老年代:标记清除/整理法