Java虚拟机垃圾回收和类加载初探

“Write once. Run everywhere”.这是Java 1.0版本发布之时提出的口号,要实现这一目标,关键技术在Java虚拟机。虚拟机自Sun Classic VM发布以来已经历经二十多年的发展,各类虚拟机层出不穷,其中最为广泛运用的便是HotSpotx虚拟机,它不断改进和更新,其定位是面向各种不同应用场景的全功能Java虚拟机,以下内容基于HotSpot虚拟机。

内存管理


对于Java程序员来说,无需为数据的内存分配进行手动管理,而是交给虚拟机进行自动内存管理。 Java虚拟机在执行程序的过程中会将所管理的内存划分为若干不同的数据区域,jdk1.8时有细微变化。

图片来源见水印

判断对象是否已死(哪些对象需要回收)


算法名称 算法思想 优点 缺点
引用计数算法 对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的 原理简单;判定效率高 面对复杂情况有缺陷,单纯的引用计数就很难解决对象之间相互循环引用的问题。
可达性分析算法 通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的 可以应对复杂情况 引用链很长时费时

垃圾回收算法(GC)


从如何判定对象消亡的角度出发,垃圾收集算法可以划分为“引用计数式垃圾收集”(Reference Counting GC)和“追踪式垃圾收集”(Tracing GC)两大类,这两类也常被称作“直接垃圾收集”和“间接垃圾收集”。

标记-清除算法
算法分为标记清除两个阶段:首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。 缺点:1.执行效率不稳定;2.内存空间碎片化,标记、清除之后会产生大量不连续的内存碎片

标记-复制算法
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。 缺点:1.空间浪费严重,可用内存缩小为原来的一半;2.如果多数对象都是存活的话,会产生大量的内存间复制的开销。

标记-整理算法
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

垃圾收集器


Serial收集器
单线程收集器,最基础最悠久的收集器,除了单线程工作,在垃圾收集的时候必须暂停其他工作线程

ParNew收集器
serial收集器的多线程版本,支持多线程并行收集,在参数控制、收集算法、Stop The World、对象分配规则、回收策略等都与Serial收集器完全一致。在单核心处理器的环境中绝对不会有比Serial收集器更好的效果


CMS (Concurrent Mark Sweep)收集器
HotSpot虚拟机中第一款真正意义上支持并发的垃圾收集器,以获取最短回收停顿时间为目标,首次实现了让垃圾收集线程与用户线程(基本上)同时工作,使用标记-清除算法

Serial Old收集器
Serial收集器的老年代版本,使用标记-整理算法


Garbage First(简称G1)收集器
一款主要面向服务端应用的垃圾收集器,基于Region的堆内存布局,可以面向堆内存的任何部分组成回收集进行回收、用户可以自定义最大停顿时间,指定区域存放大对象。从整体上来看,基于标记-整理算法,但从局部(两个Region之间)上看又是基于“标记-复制”算法实现

CMS和G1分别使用增量更新和原始快照技术,实现了标记阶段的并发,不会因管理的堆内存变大,要标记的对象变多而导致停顿时间随之增长。


Shenandoah收集器
一款HotSpot第三方垃圾收集器,只在openjdk中可用。关键过程:并发标记、并发回收、并发引用更新。Shenandoah使用转发指针和读屏障来实现并发整理

ZGC收集器
一款基于Region内存布局的,(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,以低延迟为首要目标的一款垃圾收集器。

虚拟机类加载的过程


加载:加载阶段,Java虚拟机需要完成以下三件事情:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证:验证阶段大致上会完成下面四个阶段的检验动作:文件格式验证、元数据验证、字节码验证和符号引用验证,确保class文件的字节流符合虚拟机的规范,进而来保护虚拟机的安全

准备:为类中定义的变量分配内存并设置变量的初始零值

解析:解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程

初始化:执行类中编写的代码

双亲委派模型

工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。

虚拟机中的解释器和编译器


解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即运行。当程序启动后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码,这样可以减少解释器的中间损耗,获得更高的执行效率。当程序运行环境中内存资源限制较大,可以使用解释执行节约内存(如部分嵌入式系统中和大部分的JavaCard应用中就只有解释器的存在),反之可以使用编译执行来提升效率。

即时编译器编译的目标是“热点代码”,这里所指的热点代码主要有两类,包括:
·被多次调用的方法。
·被多次执行的循环体。

热点探测的两种主流方法

方法 原理 优点 缺点
基于采样的热点探测(Sample Based Hot Spot Code Detection) 采用这种方法的虚拟机会周期性地检查各个线程的调用栈顶,如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法” 实现简单高效,并且可以很容易的获得方法调用关系 难以精确的确认一个方法的热度,容易受到线程阻塞等其他外界因素影响
基于计数器的热点探测(Counter Based Hot Spot Code Detection) 为每个方法建立计数器,执行次数超过指定阈值就认为是“热点方法” 统计结果更加严谨 实现起来更麻烦,无法直接获取到方法的调用关系

参考来源

《深入理解Java虚拟机-JVM高级特性与最佳实践》第三版-周志明

本文已结束 ❤ 感谢阅读
觉得文章不错,赞赏站长一包辣条( •̆ ᵕ •̆ )◞ ❤
0%