判断对象生死
引用计数法:给每一个对象添加一个计数器,若对象被引用则计数器加一,当引用失效则计数器减一,若对象的计数器为零说明对象不再被引用,可以进行回收。但是引用计数法有一个问题,就是当两个对象循环引用时,那么这两个对象的计数器不能能为零,也就永远无法回收。
可达性分析法:以一系列称为GC roots”的根对象为起始节点集,从这些节点开始根据引用向下搜索,搜索过程走过的路径称为“引用链”,如果一个对象和起始节点之间没有引用链相连,那么这个对象就不可能在被使用了,应该被回收掉。
GC roots选取:
- 虚拟机栈中引用的对象。
- 方法区的静态变量。
- 常量池中的常量对象。
- 本地方法栈中引用的对象。
- 被同步锁持有的对象
- 虚拟机内部的引用,包括基本类型对应的Class对象,异常对象,系统类加载器。
垃圾收集算法
-
分代收集理论
1、弱分代假说:大部分对象的生命周期都是极短的。
2、强分代假说:熬过了多次垃圾收集的对象就越难消亡。
3、跨代引用假说:跨代引用相对于同代引用只占极少数。(如果存在跨代引用可以在新生代中加入记忆集,记忆集将老年代划分为多个小块,标识出存在跨代引用的老年代对象)。
-
标记-清除算法
最基础的垃圾收集算法,首先将需要回收的对象标记起来,然后统一回收标记的对象。
这样具有两个严重的缺点:
1、如果需要回收的对象比较多,那么标记和清除操作的工作量很大,导致垃圾收集效率低,特别是在新生代中,大多数对象都是需要被回收的。
2、在垃圾收集完成后,会产生很多的空间随便,以后在给大的对象分配空间时容易出现内存不足的问题。
-
标记-复制算法(适合Minor GC/young GC)
对于新生代来说,存活的对象较少,我们将内存分为大小相等的两块,现在A区存储对象,B区空着。在垃圾收集时将A区存活的对象标记起来然后复制到B区,然后清除A区的所有对象。下次分配内存时就分配到B区,就这样轮换。
缺点:将空间分为大小相等的两块,可用来存储对象的空间缩小了一般比较浪费。
改进办法:Appel式回收,Serial、ParNew等新生代垃圾收集器采用的就是这种策略。其实现原理是将内存分为一个
-
标记-整理算法(适合MajorGC/old GC)
经典垃圾收集器
-
Serial收集器
最基础最早的新生代收集器,是一个单线程收集器,在serial执行垃圾收集时,必须停止其他所有的工作线程。在垃圾收集时暂停其他线程的工作会提供十分不好的体验,但是serial收起是最简单和高效的收集器,占用最小的内存,适合客户端模式下的虚拟机。
-
ParNew收集器
serial收集器的多线程并发版本。
在JDK7之前服务器端模式下hotspot虚拟机首选的新生代收集器。
这是因为作为老年代收集器的CMS只能选择和serial收集器或ParNew收集器配合使用,因此ParNew收集器是激活CMS收集器后的默认新生代收集器。也可以使用-XX:=/-UseParNewGC选择使用。(随着G1的发展,JDK9后取消使用了)。
-XX:ParallelGCThreads可以限制收集垃圾的线程数。
-
Parallel Scavenge
新生代收集器,多线程,基于标志-复制算法实现。
其侧重点在于达到一个可控制的吞吐量(Throughput)。
吞吐量=用户线程时间/用户线程时间+垃圾回收线程时间。
-XX:MaxGCPauseMills:最大垃圾收集停顿时间
(垃圾收集时间要缩短,就得减少新生代空间,可能导致垃圾回收频繁,吞吐量降低)
-XX:GCTimeRatio:直接设置吞吐量(垃圾收集时间所占比率,取值为(0,100),默认为99,1/99+1)
-XX:+/-UseAdaptiveSizePolicy:开启这个参数,虚拟机会自动帮我们设置新生代大小(-Xmn)、Eden和Survivor区的比例(-XX:SurvivorRatio),晋升老年代对象大小(-XX:PretenureSizeThreshold)这些参数。自适应的调节策略是Parallel Scavenge收集器的一大特性。
-
Serial Old收集器
老年代、单线程收集器,基于标志-整理算法实现。
一般情况下供客户端模式下的虚拟机使用。
可以搭配Parallel Scavenge使用,或者作为CMS失败后的预案使用。
-
Parallel Old收集器
Parallel Scavenge收集器的老年代版本。
老年代收集器,支持多线程收集,基于标志-整理算法实现。
-
CMS收集器
Concurrent Mark Sweep以最短停顿时间为目标的收集器。
基于标记-清除算法实现,其过程可以分为4个步骤:
1、初始标记
只标记和GC Roots直接相连的对象,需要stop the world。
2、并发标记
根据引用链搜索标记所有对象,此过程是和其他用户线程并发进行的。
3、重新标记
修正并发标记期间因用户线程的继续运作而改变的标记对象。
4、并发清除
使用标记-清除算法清除被标记的对象。
CMS收集器的三个显著缺点:
1、占用处理器资源导致应用程序运行变慢,吞吐量下降。
2、无法收集浮动垃圾,在并发标记和并发清除时,有新的垃圾产生只能等到下次GC时回收,而且需要预留空间给用户进程创建新对象,因此不能像其他的垃圾回收器一样在老年代内存满了再回收,CMS收集器启动阈值为92%(JDK6),可以设置参数-XX:CMSInitiatingOccupancyFraction。
3、标记-清除算法的内存碎片问题。
-
G1
开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
G1再收集垃圾不再是基于整个新生代或整个老年代进行收集,它可以面向堆内存任何部分来组成回收集,衡量标准不再是基于它属于哪个分代,而是哪块内存中存放的垃圾多,回收收益最大,因此G1是Mixed GC模式。
G1实现:G1不再坚持固定大小和固定数量的分代区域划分,而是将连续的java堆划分为多个大小相等的独立区域(Region),每个Region根据需要扮演新生代的Eden空间、survivor空间或者是老年代空间。Region中有一类特殊的Humongous区域用来专门存储大对象,G1 认为只要对象的大小超过Region区域大小的一半即可认为是大对象(Region区域大小可以通过-XX:G1HeapRegionSize设定,取值范围为1~32M,且为2的N次幂)。G1中收集垃圾是以Region为基本单位的,因此G1会维护一个优先列表记录Region区域回收的价值,再用户允许的收集停顿时间会优先收集价值大的Region区域(-XX:MaxGCPauseMills指定停顿时间,默认为200ms)。