一、垃圾回收概述
垃圾:运行程序中任何指针指向的对象
内存泄露:不用的对象任然有指针指向无法回收。(数据库连接、IO流没有关闭,静态集合类-List、Map等等)
二、垃圾回收相关算法
1.垃圾标记阶段的算法——引用计数算法
没有jvm使用这种算法,因为无法解决循环引用的情况,会出现内存泄露
2.垃圾标记阶段的算法——可达性分析算法(根搜索算法)
有效的解决了引用计数算法中循环引用的问题,防止了内存泄露
GC Roots包含以下对象
除了以上固定的GC Roots以外,根据用户选用的垃圾收集器和当前回收的内存区域不同,还可以有其他对象临时加入,比如:分带收集和局部回收——当对新生代进行gc的时候,老年代的应用也可以当做gc roots
注意
对象的finalization机制
finalization方法用于在对象被回收时调用,一般用来做一些回收资源的操作
注意
对象的三种情况
判断对象是否可回收,至少要经历两次标记过程
3.垃圾清除阶段的算法——标记清除算法
这里的回收不是真正的删除数据,而是将地址加到空闲列表中,下次分配对象空间的时候,如果空间足够就直接覆盖。缺点就是会产生大量的内存碎片,需要维护一个空闲列表。
在gc的时候相对于标记整理算法要移动对象,垃圾清除速度跟快(延时低),但是分配内存时需要维护空闲列表,内存分配更复杂(综合来说吞吐量降低了)。
CMS使用标记清除算法
CMS这类关注于低延时的gc器就是使用的标记清除算法回收Old区,平时使用标记清除算法,容忍一部分内存碎片;当碎片化程度大到影响对象分配的时候,就会使用标记整理算法,获得规整的空间
4.垃圾清除阶段的算法——复制算法(新生代)
核心思想:将活着的内存分为两个部分from区和to区,每次gc的时候,把可达的对象从from区放到to区中,将from区全部清空(s1和s2区的gc就是采用这种算法)
当堆中的存活对象比较多的时候,就不理想了(会复制大量的对象),适用于垃圾产生比较频繁的场景(存活对象比较少,垃圾对象比较多)。所以在新生代中比较适合复制算法,因此开辟from区和to区。
4.垃圾清除阶段的算法——标记压缩算法
对标记清除算法的改进,用在老年代的gc中。对内存中的碎片进行了整理(将存活的对象统一放到内存一端)
Parallel Scavenge使用标记整理算法
Parallel Old使用标记整理算法回收Old区,gc的时候会移动大量的对象,所以垃圾收集的停顿时间会比较长(延时高),但是不会产生内存碎片,所以内存分配的快,综合来说吞吐量更高
Parallel Scavenge(复制算法收集年轻代)+Parallel Old=>高吞吐组合
三种垃圾清理算法的对比
分带收集算法
不同对象的生命周期不一样,因此,不同生命周期的对象可以采用不同的收集方式。一般java堆分为新生代和老年代,这样可以根据各个年代的特点使用不同的回收算法,以提高垃圾回收的效率。
1.新生代
特点:区域相对老年代较小,对象生命周期短,存活率低,回收频繁。
这种情况下复制算法的回收整理速度最快,复制算法的效率只和当前存活的对象大小有关,因此很适合用于年轻代的回收。而复制算法内存使用率不高的缺点,通过hotspot中的两个survivor的设计得到了缓解(8:1:1)
2.老年代
特点:区域较大,对象生命周期长、存活率高,回收不及新生代频繁
这种情况下存在大量的存货对象,复制算法明显不适合。一般由标记-清除或者标记-清除与标记-整理的混合实现。
Mark阶段的开销与存活对象的数目成正比
Sweep阶段的开销与管理区域的大小成正比
Compact阶段的开销与存活对象的数量成正比
三、垃圾回收相关概念
1.System.gc()的理解
System.gc()提醒垃圾收集器执行gc,不一定会立即触发垃圾收集器
2.内存溢出与内存泄露
内存泄露:对象不会再被程序用到了,但是gc又不能回收他们的情况。
实际情况很多时候一些不太好的时间会导致对象的生命周期很长甚至OOM,也可以叫做宽泛意义上的“内存泄露”。
eg:某些可以定义在方法中的变量定义成了成员变量甚至类变量;Web应用中将一些不必要的变量放到session域中;数据库连接、网络连接、IO连接没有关闭。
3.STOP THE WORLD
STW:gc事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,像卡死一样。
4.垃圾回收的并行与并发
5.安全点与安全区域
安全点
将安全点设置在运行时间较长的指令上
主动式中断
安全区域
当线程处于Sleep状态或Blocked状态,线程无法响应JVM的中断请求,运行到安全点再挂起,JVM也不太可能等待线程被唤醒。对于这种情况,就需要安全区域来解决
安全区域:一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始gc都是安全的。
OopMap——记录栈上引用的位置
OopMap 记录了栈上本地变量到堆上对象的引用关系。
gc的时候,gc线程会对栈上的局部变量表进行遍历,找出那些是Reference类型的变量(gc roots)。但是栈上的本地变量表里面只有一部分数据是 Reference 类型的(它们是我们所需要的),那些非 Reference 类型的数据对我们而言毫无用处,但我们还是不得不对整个栈全部扫描一遍,这是对时间和资源的一种浪费。通过OopMap记录下栈上代表引用的位置。
gc 发生时,程序首先运行到最近的一个安全点停下来,然后更新自己的 OopMap ,记下栈上哪些位置代表着引用。枚举根节点时,递归遍历每个栈帧的 OopMap ,通过栈中记录的被引用对象的内存地址,即可找到这些对象( GC Roots )。
记忆集和卡表
分代收集中为了解决对象跨代引用的问题,使用记忆集记录那些区域存在夸代引用,避免全代扫描。
在进行YGC的时候,可能会有Old代对象引用Young代中的对象,这时候为了避免把Old代全部加入扫描范围,就使用了记忆集的方法记录那些区域存在跨代引用。
卡表是记忆集的具体实现,hotspot中以卡精度为记忆精度,卡表中的一位对应Old区中512字节大小。如果这个区域有跨代引用,卡表对应位置设为1。这样就可以轻易的知道哪些区域存在跨代引用,gc的时候需要加入到gc roots中一起扫描。
卡表的更新
通过在引用字段赋值之后加入一个写后屏障来完成卡表状态的更新。
伪共享问题:高并发场景下,多个卡表元素(1字节)共享一个缓存行(64字节),即使卡表元素之间修改相互独立,但是由于在同一个缓存行中,每次修改都会强制刷新缓存行的全部内容,导致性能低下。
可以在更新卡表状态前先判断卡表元素是否已经更新过,这样可以降低伪共享问题。通过参数-XX:+UseCondMark开启。
6.强引用、软引用、弱引用、虚引用、终结器引用
强引用
任何情况下,只要有强引用关系还在,垃圾回收器就永远不会回收掉被引用的对象
软引用
系统即将要发生内存溢出之前,将软引用的对象进行回收(缓存数据)
当内存足够时,不会回收软引用的可达对象,内存不够时再回收(一般用于内存的缓存,当内存不够的时候就会被自动清除,既保证了空间也保证了效率)
弱引用
被弱引用的对象只能生成到下一次垃圾收集之前。垃圾收集器工作时,无论内存是否足够都会被回收(发现既回收)
weakhashmap内部的键值对entry就继承了弱引用,当gc的时候就会被回收。
ThreadLocal中的ThreadLocalMap的key就是用弱引用指向当前的ThreadLocal,防止ThreadLocal内存泄漏。
虚引用
用于管理堆外内存(NIO)
DirectByteBuffer是通过虚引用(Phantom Reference)来实现堆外内存的释放的。 虚引用不能用来取得目标对象,目标对象被回收前,它的引用会被放入一个 ReferenceQueue 对象中,从而达到跟踪对象垃圾回收的作用。虚引用主要被用来 跟踪对象被垃圾回收的状态,通过查看引用队列中是否包含对象所对应的虚引用来判断它是否 即将被垃圾回收,从而采取行动
四、垃圾回收器
1.GC分类与性能指标
GC分类
串行垃圾回收器和并行垃圾回收器
GC评估性能指标
主要两个指标:吞吐量、暂停时间
吞吐量VS暂停时间
暂停时间短,gc频率就会上升
2.不同的垃圾回收器概述
7种经典垃圾回收器
垃圾收集器的组合关系
3.Serial回收器:串行回收(单核场景下使用)
4.ParNew回收器:并行回收
5.Parallel Scavenge回收器:吞吐量优先
5.CMS回收器:低延迟(标记清除)
使用并发的标记清除,延时低。会产生内存碎片,当内存碎片化对内存分配影响比较大的时候,会使用标记整理算法gc,使内存空间规整
由于用户线程和gc线程并发进行,所以需要预留足够的空间给用户线程,CMS默认Old代达到92%的时候进行gc
由于用户线程和gc线程并发进行,所有可能发生内存溢出。如果gc期间发生内存,就会启用Serial Old收集器来重新进行老年代的垃圾收集。
CMS收集器使用标记-清除算法,所以会产生大量的内存碎片。所以CMS在为新对象分配内存空间时,无法使用指针碰撞技术,只能够选择空闲列表分配内存。
设置 -XX:CMSFullGCsBeforeCompaction参数,当CMS在使用多次标记清除算法进行gc之后,使用标记整理算法进行一次gc,是内存规整。
由于CMS是并发清除,用户线程会使用内存,所以不能使用标记-压缩算法(内存地址会改变)
6 G1回收器:区域化分代式、延时可控
分区Region:化整为零
G1垃圾回收过程
RememberedSet
G1回收过程——年轻代GC
G1回收过程二——并发标记过程
G1回收过程三——混合回收
7 MinorGC MajorGC FullGC 区别
JVM在进行GC时,可能针对三个区域进行垃圾回收分别是新生代、老年代、方法区,大部分时候回收的都是新生代。GC类型主要有以下四种类型。
- 新生代收集(Minor GC/Young GC):只针对新生代的垃圾收集。具体点的是Eden区满时触发GC。 Survivor满不会触发Minor GC 。
- 老年代收集(Major GC/Old GC):只针对 老年代的垃圾收集。 目前,只有CMS收集器会有单独收集老年代的行为。
- 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。 目前只有G1收集器会有这种行为。
- 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。
8 经典垃圾回收期总结
面试
9 GC日志分析
日志补充说明
##### 什么对象会进入老年代?
1、gc次数大于15次:不同gc不一样,但最大就15次,因为对象头中的gc带年龄只有4位。
2、内存分配担保:YGC的时候,如果to区无法放下gc存活下来的对象,这些对象会被担保进入Old区。
3、动态年龄判断:Survivor区中相同年龄的对象总和大于Survivor一半,则将年龄大于等于该年龄的对象放入老年区中。
4、大对象:较大的对象直接被分配到老年代
空间分配担保:
当发生YGC的时候,Old区会查看当前连续空间是否大于young区对象的总和,如果不成立,当- XX:HandlePromotionFailure参数开启的时候,继续判断Old区中连续空间是否大于历次gc晋升到Old对象的平均值,如果不成立则直接进行FullGC。