深入理解java虚拟机(四)垃圾收集算法及HotSpot实现

垃圾收集算法

一般来说,垃圾收集算法分为四类:

标记-清除算法

最基础的算法便是标记-清除算法(Mark-Sweep)。算法分为“标记”和“清除”两个阶段:首先标记处需要收集的对象,在标记完成之后,再统一回收所有被标记的对象。

这是最简单的一种算法,但是缺点也是很明显的:一个是效率问题,标记和清除效率都不高。二是空间问题,清除之后会产生大量的空间碎片,导致之后分配大对象找不到足够的连续对象而不得不触发另一次垃圾收集动作。算法执行过程如下图。

复制算法

复制算法(Copying)将可用内存按照容量大小分成相等的两份,每次只使用一半。当这一块内存用完了,就会将还存活的对象复制到另一块内存上,然后将之前的那块内存清空。优点是解决了空间碎片的问题,而且分配新对象的时候顺序分配,实现简单,运行高效。缺点是内存减小了一半。算法示意图如下。

现在的商业虚拟机都采用这种收集算法来回收新生代。由于新生代对象死亡率较高,所以可以将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和一块Survivor。当回收时,将Eden和一块Survivor中还存活的对象复制到另一块Survivor上,然后清理掉Eden和之前使用的Survivor空间。HotSpot虚拟机默认Eden和Survivor比例为8:1,也就是只有10%的内存会被“浪费”。young
区和old 区使用的回收对象算法不一样,因为回收young 区满了需要回收时,Old不需要被回收,而当Old区满了要回收对象时,整个内存堆都要清理,而且使用者可以设置 young区和old区的回收是多线程还是单线程的,所以设计者是希望对象能够多点时间留在young 区,以提高回收对象的效率。设计成From
和 To 两个平行的区,我觉得是为了筛选真正符合old区的要求的对象(即需要长时间持有的引用的对象),然后再将他们放入old区。

标记-整理算法

复制算法在对象存活率较高的情况下,效率会变低。而且浪费了50%的空间。

根据老年代的特点,有人提出了另外一种“标记-整理”算法(Mark-Compact)。算法的也分为标记和整理两个阶段。标记和“标记-清除”算法的标记过程一样。当标记完成之后,并不直接对可回收对象进行整理,而是所有存活的对象整理成连续的,然后清理掉剩余的空间。算法示意图如下。

分代收集算法

当前商业虚拟机都采用“分代收集”(Generational Collection)算法,根据对象存活的周期不同将内存划分为几块。一般是将Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。新生代采用复制算法,年老带采用标记-清理或者标记-整理算法。

HotSpot算法实现

枚举根节点

在可达性分析中,可以作为GC Roots的节点有很多,但是现在很多应用仅仅方法区就有上百MB,如果逐个检查的话,效率就会变得不可接受。

而且,可达性分析必须在一个一致性的快照中进行-即整个分析期间,系统就像冻结了一样。否则如果一边分析,系统一边动态表化,得到的结果就没有准确性。这就导致了系统GC时必须停顿所有的Java执行线程。

目前主流Java虚拟机使用的都是准确式GC,所以当执行系统都停顿下来之后,并不需要一个不漏的检查完所有执行上下文和全局的引用位置,虚拟机应该有办法直接知道哪些地方存放着对象引用。在HotSpot实现中,使用一组称为OopMap的数据结构来达到这个目的。OopMap会在类加载完成的时候,记录对象内什么偏移量上是什么类型的数据,在JTI编译过程中,也会在特定的位置记录下栈和寄存器哪些位置是引用。这样,在GC扫描的时候就可以直接得到这些信息了。

安全点

可能导致引用关系变化,或者说OopMap内容变化的指令非常多,HotSpot并不会为每条指令都产生OopMap,只是在特定的位置记录了这些信息,这些位置成为“安全点”(SafePoint)。程序执行时只有在达到安全点的时候才停顿开始GC。一般具有较长运行时间的指令才能被选为安全点,如方法调用、循环跳转、异常跳转等。

接下来要考虑的便是,如何在GC时保证所有的线程都“跑”到安全点上停顿下来。这里有两种方案:抢先式中断(Preemptive Suspension)和主动式中断(Voluntary
Suspension)。

抢先式中断会把所有线程中断,如果某个线程不在安全点上,就恢复让它跑到安全点上。几乎没有虚拟机采用这种方式。

主动式中断思想是设立一个GC标志,各个线程会轮询这个标志并在需要时自己中断挂起。这样,标志和安全点是重合的。

安全区域

Safepoint机制可以保证某一程序在运行的时候,在不长的时间里就可以进入GC的Safepoint。但是如果程序没有分配CPU时间,例如处于Sleep状态或者Blocked状态,这时候线程无法响应JVM的中断请求。对于这种情况,只能用安全区域(Safe
Region)来解决。

安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域中任意地方开始都是安全的。在线程执行到Safe Region中的代码时,就标记自己已经进入了Safe Region,这样JVM在发起GC时就跳过这些线程。在线程要离开Safe Region时,它要检查系统是否已经完成了枚举(或GC过程),如果完成了线程就继续执行,否则就等待。

时间: 2024-08-09 20:03:35

深入理解java虚拟机(四)垃圾收集算法及HotSpot实现的相关文章

深入理解Java虚拟机笔记---垃圾收集算法

当对象判定为"已死"状态,虚拟就要采取一定的手段将这些对象从内存中移除,即回收垃圾,回收过程有采用一定的算法.如下是一些主要的垃圾收集算法: 1.标记-清除算法 该算法是最基础的算法,分为"标记"和"清除"两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象.之所有说它是最基础的算法是因为后续的收集算法都是基于这种思路并对其缺点进行改进得到的.它的缺点主要有两个:一个是效率问题,标记和清除过程效率都不高:另外一个是空间问

深入理解Java虚拟机之垃圾收集一

"生存还是死亡" 如何来判定对象是否存活?针对这个问题书中给出了两种算法,分别是引用计数算法和可达性分析算法 引用计数算法 该算法的思路简单并且易于实现.我们给对象中添加一个引用计数器,当有一个地方引用它时,引用计数器就加一,当引用失效时,计数器减一,当计数器为0时就说明该对象不可能再被引用. 客观的评价,该算法判定效率很高,在很多情况下都是一种不错的算法,但是,至少主流的Java虚拟机并没有采用采用这种算法.原因是该算法无法解决对象之间的循环引用问题. 什么是循环引用呢?笔者认为就是

深入理解Java虚拟机笔记---垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现.Java虚拟机规范中对象垃圾收集器应该如何实现并没有任何规定,因此不同的厂商,不同版本的虚拟机所提供的收集器可能会有很的差别,并且一般会提供参数供用户根据自己的应用特点和要求组合出各个年代所使用的收集器.下面是Sun HotSpot虚拟机1.6版本Update22包含的所有收集器: 上图中,如果两个收集器之间存在连线,就说明它们可以搭配使用. 1.Serial收集器 Serial收集器是最基本.历史最悠久的收集器,曾经(在J

《深入理解Java虚拟机》——垃圾收集器与内存分配策略

GC需要完成: 哪些内存需要回收 什么时候回收 如何回收 如何确定对象不再使用 引用计数算法 给对象添加一个引用计数器,当有一个地方引用它时,计数器值进行加1操作:当引用失效时,计数器值进行减1操作:当计数器值为0,则说明对象不可能再被使用.但是它无法解决循环引用的问题. public class ReferenceCountingGC { public Object instance = null; public static void testGC(){ ReferenceCountingG

深入理解java虚拟机(3)垃圾收集器与内存分配策略

一.根搜索算法: (1)定义:通过一系列名为"GC Roots"的对象作为起点,从这些起点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的时候,则证明此对象不可用 (2)GC Roots对象包括这几种:虚拟机栈中引用的对象:方法区中的类静态属性引用的对象:方法区中常量的引用对象:本地方法中JNI的引用对象 二.垃圾收集算法 1.标记-清楚算法: (1)基本思想:首先标记出要回收的对象,在标记完后回收掉所有被标记的对象 (2)缺点:第一是标记和清楚

深入理解Java虚拟机之垃圾收集二

垃圾收集器 图示为JDK1.7 Update14之后的HotSpot虚拟机所使用的所有垃圾收集器. 如果两个收集器存在连线,那么说明这两个收集器可以搭配使用.收集器所处的区域说明它是新生代收集器还是老年代收集器. Serial收集器 主要看图↑ 上图演示了Serial/Serial Old收集器运行示意图. Serial收集器是一个单线程收集器,该收集器在进行垃圾收集的同时,必须暂停(Stop the World!)其它工作线程(这就好比你妈妈在打扫房间的同时,肯定也会让你乖乖待在一边,如果她一

Java虚拟机学习 - 垃圾收集算法(3)

跟踪收集器 跟踪收集器采用的为集中式的管理方式,全局记录对象之间的引用状态,执行时从一些列GC  Roots的对象做为起点,从这些节点向下开始进行搜索所有的引用链,当一个对象到GC  Roots 没有任         何引用链时,则证明此对象是不可用的. 下图中,对象Object6.Object7.Object8虽然互相引用,但他们的GC Roots是不可到达的,所以它们将会被判定为是可回收的对象. 可作为GC Roots 的对象包括: 虚拟机栈(栈帧中的本地变量表)中的引用对象. 方法区中的

J深入学习ava虚拟机之——垃圾收集算法与垃圾收集器

今天我们将一起学习Java虚拟机使用垃圾收集算法和常见的垃圾收集器.Java虚拟机内存区域的程序计数器.虚拟机栈和本地方法栈3个区域是随线程而生,随线程而灭:栈中的栈帧随着方法的进入和退出出栈和入栈.每一个栈帧中分配多少内存基本上是在类结构确定下来的时候就已知的,因此这个几个区域的内存分配和回收都具备确定性,在这几个区域就不需要过多考虑回收问题,因为方法结束或者线程结束时,内存自然就跟着回收了.而Java堆和方法区就不一样,一个接口中的多个类实现需要的内存可能不一样,一个方法中的多个分支需要的内

Java虚拟机四:垃圾回收算法与垃圾收集器

在Java运行时的几个数据区域中,程序计数器,虚拟机栈,本地方法栈3个区域随着线程而生,随线程而灭,因此这几个区域的内存分配和回收具有确定性,不需要过多考虑垃圾回收问题,因为方法结束或者线程结束时,内存就回收了.但是方法区和堆区不一样,一个接口或者实现类所需要的内存可能不一样,一个方法的多个分支需要的内存也可能不一样,只有程序运行时才能知道创建哪些对象,这部分内存的分配和回收是动态的. 在进行垃圾回收时候,首先需要判断哪些对象需要回收,这就涉及到回收算法的问题. 一.垃圾回收算法 1.标记-清除

《深入理解JAVA虚拟机》JDK的垃圾收集算法

概念 垃圾收集是很多使用JAVA语言的IT从业者了解得比较少的地方. 但是涉及性能时非常重要.大公司面试除了算法,这部分也是会经常考察的地方. <深入理解JAVA虚拟机>一书中讲到JVM的垃圾收集算法和垃圾收集器. 垃圾收集算法分为: 1.标记清除算法 通常用在回收老年代内存. 最早的搜集算法就是标记清除(Mark-Sweep)算法了. 其原理是分为标记和清除两个阶段: 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象. 主要不足两个: 一个是效率问题,标记过程和清除过程效率