Java虚拟机

关于虚拟机的内容大部分都可以包含在内存管理、垃圾回收、优化工具、代码加载执行这几部分。

1 内存管理

1.1 区域划分

JVM的内存区域由其管理并根据职能划分为不同的数据区域,根据《Java虚拟机规范》的规定,内存区域划分为:

其中方法区和堆为线程共享、其他为非共享。

程序计数器 简单来说,「程序计数器」是线程维度的,用于标识线程当前执行的字节码位置的行号指示器。如果线程执行的是Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,那么为空。

多线程情景下的线程切换(切换到上次执行的位置),分支、循环、跳转、异常处理等等都依赖程序计数器。

虚拟机栈 Java虚拟机栈是线程维度的,用于存储线程执行过程中方法粒度的信息,这些信息存储在创建的栈帧中,包含局部变量表、操作栈、方法出口等信息。每一个方法执行的过程,就对应一个入栈到出栈的过程。

这个区域可能出现如下两种异常:

本地方法栈 按照虚拟机规范来讲,本地方法栈和虚拟机栈的区别在于虚拟机栈负责Java方法的执行,本地方法栈负责Native方法的执行。HotSpot虚拟机无此区分,二者为一个区域。

Java堆 堆区域为线程共享,存放所有类实例的内存区域。堆是垃圾回收管理的主要区域,以此角度分,堆可以分为新生代和老年代。

Java堆被划分为两个区域:新生代(young) + 老年代(old)。前者默认占 1/3堆空间,后者2/3。针对于JDK1.7以前,堆区域还存在Perm区,用于存放类、常量等信息。

同样,这对于GC,分为Minor GC、Full GC两种。

Minor GC也成为Young GC,主要流程:

  1. 对象创建在Eden 或者 From 区(当对象较大,需要连续的内存空间时,可能直接分配在 Old区)。
  2. 一次Minor GC,如果对象仍然存活,那么将其移到 To Survivor 区(如果能够放得下),清理 Eden和From区,并将存活的年龄加1.
  3. 当对象的年龄到达15(通过XX:MaxTenuringThreshold设置)时,这些对象复制到Old区。

通常情况下,Old GC 会同时对Young/Old区进行清理操作。

方法区 方法区为线程共享的,用于存放类、常量、静态变量、编译后的代码等数据。低版本的HotSpot,使用「永久区」的方式实现方法区。

运行时常量池是方法区的一部分,用于存放编译期或动态生成的字符常量和符号引用。

除以上虚拟机运行内存区之外,直接内存(Direct Memory)也可以被JVM通过Native方法使用,这类内存不受JVM分配的内存大小的限制,但是同样会因为内存不足导致 OutOfMemroy。

1.2 一个例子

以 MyObj o = new MyObj()为例:

以上,为JVM虚拟机内存区域划分,以及每个内存区域的作用与相互之间的联系。

2 垃圾回收

垃圾回收的讨论无非是四个问题:

What

‘已死’的对象需要被回收,判断对象已死通常是通过是否存在与「根对象」之间的引用而不是通过自身引用数来判断,后者通常使用「引用计数算法」实现,问题是无法解决相互引用的问题,而「根搜索」就是用来判断对象与根对象之间的引用关系的算法。

通常意义的引用是指reference类型的值为被引用对象内存的起始地址,在垃圾回收的范畴内讲,引用还可以细化为:strong引用、soft引用、weak引用、phantom引用。强引用为通常意义上Class a = new A()的引用,只要引用存在,就不会被垃圾回收。软引用的对象在OOM之前会触发回收操作,如果对这类对象的回收后仍然内存不足,才会OOM。弱引用的对象只要进行垃圾回收,不论内存是否充足,都会被进行回收。

How

垃圾收集器构建与垃圾收集算法之上,常见的垃圾收集算法有:

垃圾回收器 讨论垃圾回收器一般就是两个点:

  1. 底层的垃圾回收算法
  2. 工程情景与垃圾回收器的选择,主要是GC时间和吞吐量的考虑。
年轻代 老年代
Serial CMS
ParNew Serial Old
Parallel Scavenge Parallel Old
G1 G1

上图中同一行并不代表固定的组合关系。