文章目录
一、JAVA垃圾收集(GC)
Java垃圾收集需要完成以下三件事情:
哪些内存需要回收
Java堆中存放着几乎所有的对象实例,垃圾收集器在进行回收时,先要确认哪些对象还存活着,哪些对象已经死去。
1. 引用计数法
给对象添加一个引用计数器,每当有一个地方引用计数器加一,引用失效后计数器减一。任何时候计数器为0时候对象不可能再被使用
优点:算法简单判定效率高
缺点:无法解决对象相互引用问题
2. 可达性分析算法
通过一系列的称为"GC Roots"的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连(GC Root到这个对象不可达)时,则证明此对象是不可用的。
在JAVA虚拟机中,以下几种可作为GC Roots对象
- 虚拟机栈(栈帧中引用的局部变量表)中引用的对象
- 方法区中类静态属性引用表
- 方法区中常量引用的对象
- 本地方法栈(Native方法中)中引用的对象
3. Java引用
- 强引用:Object ob = new Object()
- 软引用(SofReference): 系统将要发生内存溢出时,把这些对象进行二次回收,如果这次回收还没有足够内存,才抛出OOM
- 弱引用(WeakReference): 被弱引用关联的对象只能生存到下一次垃圾收集之前
- 虚引用(PhantomReference):一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过一个虚引用获得对象的实例。为对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
4. 对象finalize()方法
如果对象在经过可达性分析后发现没有与GC Roots对象存在引用链,将会判断对象是否覆盖了finalize方法且方法有没有被执行过(此方法仅会执行一次)。当对象覆盖了finalize且未被执行过,虚拟机将会创建一个低优先级Finalizer线程执行finalize方法。finalize方法是对象逃脱死亡命运的最后一次机会。
尽量避免使用finalize方法
什么时候回收
Java安全点
可达性分析算法必须在一个能确保在一致性的快照中进行,不可以出现在分析过程中对象引用关系不断变化的情况。这就是GC时候必须停止所有用户线程(Stop-The-World)的原因。
目前主流虚拟机采用准确式GC,使用一组称为OopMap数据结构存储对象引用关系。能导致OopMap变更的指令非常多,如果每个指令都生成对应OopMap,这样成本太高。实际上虚拟机只会在特定的”安全点“才会记录OomMap数据结构。当GC需要中断线程的时候,设置一个标记,线程执行到安全点的时候,主动去轮询这个标记,主动中断线程执行。
如何回收
1. 标记-清除算法
首先标记出需要回收的对象,在标记完成后统一回收所有被标记的对象。它是最基础的回收算法。
- 标记和清除效率都不高
- 产生大量不连续内存碎片
2. 复制算法
将可用内存划分为大小相等的两块,每次只使用一块。当这块内存用完了,将存活的对象复制到另一块上面,然后把已使用过的内存一次性清理掉。
- 每次都是对整个半区进行回收,内存分配也无需考虑碎片问题,实现简单,运行效率高
- 算法代价是将内存缩小为原来的一半,代价太大。
现在的虚拟机一般都采用这种算法来回收新生代。根据研究表明,新生代的对象98%都是朝生夕死,所以并不需要按照1:1比例划分内存空间。
3. 标记整理算法
标记过程和“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后清理掉边界以外的内存。
4. 分代收集算法
根据对象存活周期的不同,将内存划分为几块。一般把java堆分为新生代和老年代。这样可以根据各个年代的特点采用不同的收集算法回收内存。新生代采用”复制“算法,老年代采用“标记-清除”或者“标记-整理”算法
二、JAVA垃圾收集器
Serial收集器 – 新生代收集器
单线程收集器,使用”复制“算法在进行垃圾回收时候必须暂停所有工作线程(Stop the world)。它是虚拟机运行在client模式下的默认收集器
简单高效,对于单个CPU来说,没有线程交互的开销
Stop the world
Serial Old收集器 – 老年代收集器
一个单线程收集器,使用”标记-整理“算法。主要在Client模式下使用。在Server模式下主要两大用途:一种是JDK1.5及以前与Parallel Scavenge搭配使用,另一种是作为CMS收集器的后备预案(在收集器发送Concurrent Mode Failure时候使用)
ParNew收集器–新生代收集器
ParNew收集器其实就是Serial收集器的多线程版本,使用”复制“算法。除了使用多线程外,其他和Serial收集器几乎一致。它是许多运行在Server模式下新生代的首选收集器。
Stop the world
-XX:PretenureSizeThreshold: 对象超过多大是直接在旧生代分配(单位字节);
-XX:SurvivorRatio: Eden区与Survivor区的大小比值;设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
Parallel Scavenge收集器 – 新生代收集器
使用”复制“算法的收集器,又是并行的多线程收集器(和ParNew收集器类似)。Parallel Scavenge收集器最大的特点是它关注点与其他收集器不同,其他收集器关注的是尽可能的缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量【吐量=允许用户代码时间/(运行用户代码时间+垃圾收集器时间)】。又被成为“吞吐量优先”收集器。
停顿时间越短越适合与用户交互的程序,而高吞吐量则可以高效率的利用CPU时间,尽快的完成任务。
1. -XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间
2. -XX:GCTimeRatio 设置吞吐量
3. -XX:+UseAdaptiveSizePolicy 开关参数,打开之后就不需要手工指定新生代大小(-Xmn)、End与Survivor区比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretunureSizeThreshold)等细节参数,虚拟机会自动调整。
Parallel Old收集器 – 老年代收集器
是Parallel Scavenge收集器的老年代版本,使用多线程和”标记-整理“算法。它在1.6版本才提供。
CMS收集器 – 老年代收集器
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。它非常适合重视服务响应速度,希望系统停顿时间最短的应用。基于”标记-清除“算法实现。
- 初始标记
需要Stop the world,仅仅只是标记以下GC Roots能直接关联到的对象,速度很快。 - 并发标记
进行GC Roots Tracing的过程,时间较长 - 重新标记
需要Stop the world,为了修正并发标记过程期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。这个时间停顿比初始标记会稍长点 - 并发清除
在垃圾收集阶段需要预留一部分的内存空间给用户线程使用(-XX:CMSInitiatingOccupancyFraction设置使用比例,默认92%,预留8%),要是CMS运行期间预留内存不足,就会出现"Concurrent Mode Failure"失败,将会临时启用Serial Old收集老年代。
- 是一款优秀的收集器,并发收集,低停顿
- 对CPU资源非常敏感【默认回收线程=不少于25%CPU资源】
- 无法处理”浮动垃圾“,
- 使用”标记-清除“算法,存在内存碎片【-XX:+UseCMSCompactAcFullCollection 开启内存整理】【-XX:CMSFullGCsBeforeCompaction 设置间隔多少次执行整理内存】
G1收集器 – 新生代和老年代收集器
G1是一款面向服务端应用的垃圾收集器。能充分利用多CPU、多核环境下的硬件优势来缩短Stop-The-World停顿时间。
- 初始标记:标记GC Roots直接关联对象
- 并发标记:从GC Root开始进行可达性分析,找出存活对象
- 最终标记:修正并发标记期间用户程序运行而导致的标记变动
- 筛选回收
三、收集器参数总结
参数 | 描述 |
---|---|
UseSerialGc | 使用Serial + Serial Old收集器组合进行内存回收(虚拟机运行在Client模式下默认值) |
UseParNewGc | 使用ParNew + Serial Old收集器组合进行内存回收 |
UseComcMarkSweepGC | 使用ParNew + CMS + Serial Old 收集器组合进行内存回收 |
UseParallelGC | 使用Parallel Scavenge + Serial Old (PS MarkSweep)收集器组合进行内存回收(虚拟机运行在Server模式下默认值) |
UseParallelOldGC | 使用Parallel Scavenge + Serial Old收集器组合进行内存回收 |
SurvivorRatio | 新生代Eden区域和Survivor区域的容量比值 |
PretenureSizeThreshold | 直接晋升到老年代的对象大小 |
MaxtenureThreshold | 晋升到老年代的对象年龄 |
UseAdaptiveSizePolicy | 动态调整Java堆中各个区域大小及进入老年代的年龄 |
handlePromotionFailure | 是否允许分配担保,即老年代的剩余空间不足以应付新生代整个Eden和Survivor区域的所有对象都存活的极端情况 |
ParalleGCThread | 设置并行GC时进行内存回收的线程数 |
GCTimeRatio | GC时间占用的比率,默认99 |
MaxGCPauseMillis | 设置GC最大停顿时间 |
CMSInitiatingOccupancyFraction | 设置CMS收集器在老年代空间被使用多少后触发垃圾收集,默认68%,仅在CMS收集器时生效 |
UseCMSCompactAtFullCollection | 设置CMS收集器完成后是否进行一次内存碎片整理 |
CMSFullGCsBeforeCompaction | 设置CMS收集器内存碎片整理间隔次数 |