
目录
1、什么是垃圾回收机制
2、如何判断对象是否存活
引用计数法
根可达算法
3.垃圾回收算法
JVM从入门到精通(上)
JVM从入门到精通(中)
经过前面我们已经熟悉类的加载和运行时数据区
接下来我们要总结下垃圾回收器以及垃圾回收算法;
不定时去堆内存中清理不可达对象。不可达对象并不会马上就直接回收;
垃圾收集器Java程序中执行是自动的、不能强制执行的;
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就 + 1。当引用失效
时,计数器值 -1 。任何时刻计数器为0的对象就是不可能再被使用的(不可达)。但是由于
它很难解决对象之间相互循环引用的问题(A引用B,B引用A)。它们的引用计数则永远不
可能为0
根搜索算法的基本思路是用过一系列名为 "GC ROOTS" 的对象作为起始点,从这些节
点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到 GC ROOTS 没有任何引
用链时,则证明对象是不可达的;
可达性分析算法判定对象是否可回收
其中 GC ROORS 的对象包括下面几种:
1、虚拟机栈(栈帧中的本地变量表)
2、方法区中类静态属性引用的对象
3、方法区中常量引用的对象
4、本地方法栈 JNI (Native方法) 引用的对象
在JVM的垃圾收集器中,有三种常用的垃圾回收算法
分别是:
标记清除算法
标记压缩(整理)算法、
复制算法
1、标记阶段:标记出所有需要回收的对象
2、清除阶段:统一回收所有需要回收的对象
"标记-清除" 算法示意图
不足:
1、效率问题: 标记与清除的两个过程效率都不高
2、标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后再程序运行
过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾
收集动作
为了解决效率问题,一种称为"复制"的收集算法出现了
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块;
当这一块内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空
间一次清理掉;这样使得每次都是对整个半区进行内存回收,内存分配也就不用考虑内存碎片
等复杂情况
复制算法示意图
不足:
1、这种算法的代价是将内存缩小为了原来的一般
2、在对象存活率较高时就要进行较多的复制操作,效率将会变低
标记的过程与"标记-清除"算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所
有存活的对象向一端移动,然后直接清理掉端边界以外的内存
"标记-整理"算法示意图
在讲述完什么是垃圾、怎么定义垃圾、垃圾是有什么方式可以进行回收的这三个概念之后,下面我们将总结垃圾是由谁来收集的,是怎么收集的
介绍之前:
在介绍每个垃圾收集器之前,我们先要明确一个观点:虽然我们是在对各种收集器作比较但
是并不是为了挑选一个最好的收集器。因为到现在为止还没有最好的收集器出现,更没有万
能的收集器;所以我们选择的只是对应具体应用最合适的收集器
HotSpot虚拟机的垃圾收集器
Serial收集器是最基本、发展历史最悠久的收集器。
这个收集器是一个单线程的收集器,但它的 "单线程" 的意义并不仅仅说明它只会使用一个
CPU或一条收集线程去完成垃圾收集工作;更重要的是在它进行垃圾收集时,必须暂停其他
所有的工作线程,直到它收集结束。
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收
集之外,其余行为包括Serial收集器可用的所有控制参数、收集算法、STW、对象分配规
则、回收策略等都与Serial收集器完全一样,在实现上,这两种收集器也共用了相当多的代码
Parallel Scavenge 收集器同样使用复制算法的收集器,又是并行的多线程收集器,看上去与
ParNew都一样,那它有什么特点呢?
1.关注点与其他收集器不同,CMS等收集器的关注点是尽可能缩短垃圾收集
器的用户线程的停顿时间,而 Parallel Scavenge 手机及其的目标是达到一
个可控制的吞吐量(Throughput);所谓吞吐量 = 运行用户代码时间 / ( 运行用
户代码时间 + 垃圾收集时间)
2.该收集器提供了控制最大垃圾收集停顿时间 (-XX:MaxGCPauseMillis) 以及直接设置
吞吐量大小 (-XX:GCTimeRatio)参数用于精准控制吞吐量;
3.除此之外,该收集器还有一个参数 (-XX:+UserAdptiveSizePolicy)开关,当打开这
个参数后,就不需要手工指定新生代的大小、Eden与Survivor区的比例、晋升老年代
对象大小等细节参数,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整
这些参数以提供最合适的停顿时间或者最大的吞吐量;自适应调节策略也是Parallel
Scacenge收集器与ParNew收集器的一个重要区别
术语解释:
并发(Concurrent): 指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交
替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上
并行(Parallel) : 指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态
相同:
1、都采用了复制算法 (Copy)
2、都会造成Stop The World
不同:
1、Serial采用单线程进行垃圾收集;而ParNew与Parallel Scavenge采用多线程进行垃
圾回收。但是Parallel Scavenge更关注吞吐量;
Serial Old 是 Seial收集器的老年代版本,它同样是一个单线程收集器,使用 "标记-整理" 算
法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用。
如果在Server模式下,那么它主要还有两大用途:一种用途是在JDK1.5以及之前的版本中与
Parallel Scavenge收集器搭配使用,另一种用途就是作为CMS收集器的后备预案。
Parallel Old 是Oarallel Scavenge收集器的老年代版本,使用多线程和 "标记-整理"算法。
在JDK1.6前,Parallel Scavenge收集器除了与Serial Old收集器外别无选择,使老年代在服
务端应用性能上的"拖累",知道Parallel Old出现后,"吞吐量优先"收集器终于有了比较名副
其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old收集器;
CMS (Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目的的收集器;
CMS 是基于 "标记-清除" 算法来实现的,它的运作过程相对于前面集中收集器来说更复杂一
些,整个过程分为4个步骤
初始标记 (CMS inital mark)
并发标记 (CMS concurrent mark)
重新标记 (CMS remark)
并发清除 (CMS concurrent mark)
其中,初始标记和重新标记这两个步骤仍然需要 "Stop The World"。初始标记仅仅只是标记
一下GC Roots能直接关联到的对象,速度很快。
并发标记阶就是GC Roots Tracing 的过程
重新标记则是为了修正并发标记期间用户程序继续运作而导致标记产生变动的那一部分对象
的标记记录,这个停顿时间一般会比初始标记稍长一些,但远比并发标记的时间段。
1、吞吐量降低: CMS虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU
资源)导致应用程序变慢,总吞吐量降低
2、无法处理浮动垃圾: 可能出现 "Concurrent Mode Failure" 失败而导致另一次Full GC的
产生。(浮动垃圾为出现在标记个过程之后,产生的垃圾)
3、大量空间碎片: 因为CMS是基础 "标记-清除" 算法实现的收集器。所以在空间碎片过多时
会给大对象分配带来很大麻烦,往往会出现老年代有很大空间,但是无法找到足够大的连续
空间来分配当前对象,不得不提前触发一次 Full GC。
G1是一款面向服务端应用的垃圾收集器,其中G1具备一下特点
充分利用多CPU、多核环境下的硬件有事,使用多个CPU来缩短STW停顿的时间
2、分代收集 分代概念在G1依然保留。虽然G1不需要其他收集器配合就能独立管理整个GC,但它能够采
用不同的方式去处理新创建的对象和已经存活一段时间、熬过多次GC的旧对象以获取更好的
收集效果
与CMS的 "标记-清理" 算法不同,G1从整体来看是基于 "标记-整理" 算法实现的收集器,从局
部的(两个Region之间)上来看是基于 "复制" 算法实现的。无论如何,这两种算法都意味着G1
运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。有利于程序长时间运行,
分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC
这是G1相对于CMS的另一大优势。G1除了追求低停顿外,还能建立可预测的停顿时间模型,
能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N
毫秒
G1之前的其他收集器的收集范围都是整个新生代或者整个老年代,而G1不再是如此;而是将JAVA的堆内存划分为第一份大小相等的独立区域(Region),虽然保留有新生代和老年代的概念,但新生代老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合
所以在某一块Region来说,既可能是年轻代也可能是老年代,但是在某一刻只能属于某个代
在这个区域被垃圾清理后有可能会分配给年轻代,也有可能分配给老年代
G1知识补充G1 收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集;G1跟踪每个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要的时间),在后台维护了一个优先列表,优先回收价值最大的Region,保证G1在有限的时间内可以获取尽可能高的收集效率
在G1收集器中,Regin之间的对象引用,虚拟机都是使用 Remembered Set 来避免全堆扫描的。每个Region都有一个对应的 Remembered Set。
RSet作用
1、记录其他Region中的对象到本Region的引用
2、Rset价值在于是的垃圾收集器不需要扫描整个堆就可以找到谁引用了当前分区的对象
虚拟机发现程序在对引用类型的数据进行写操作时,会产生一个 Write Barrier 暂时中断写操作
检查引用的对象是否处于不同的Region中间(比如老年代中的对象引用了新生代中的对象)。
如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的RSet之中