GCTree
JVM 垃圾收集技术研究
根搜索算法
对象存活判断 1)引用计数法 每个对象有一个引用的计数属性,新增一个引用时计数加1,引用释放的时候计数减一, 计数为0的时候可以回收,此方法简单,但是无法解决对象的相互循环引用的问题。 2)根搜索算法 也叫可达性分析算法。 从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有 任何引用链时,则证明这个对象是不可用的。不可达对象。 在Java中,GC Roots包括: 1)虚拟机栈中引用的对象。 2)方法区中类静态属性实体引用的对象。 3)方法区中常量引用的对象。 5)本地方法栈中JNI引用的对象。
无论通过引用计数法判断对象的引用数量,还是通过根搜索算法判断对象的引用链是否可达,判断对象 是否存活都与“引用”有关。 Java中将引用分为 1)强引用 就是在程序代码中普遍存在的,例如Object obj = new Object(); 只要强引用还在,垃圾收集器就永远不会回收。 2)软引用 软引用用来描述一些还有用,但并不是必须的对象。对于软引用关联着的对象,在系统 将要发生内存溢出之前,将会把这些对象列进回收范围之中并进行第二次回收,如果这次 回收还是没有足够的内存,才会跑出内存溢出。 3)弱引用 弱引用也是用来描述非必须对象的,但是它的强度比弱引用更弱一些,被弱引用关联 的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否 足够,都会回收掉弱引用关联的对象。 5)虚引用 虚引用也称为幽灵引用,它是最弱的一种引用,一个对象是否有虚引用的存在,完全不 会对其生存环境构成影响,为一个对象设置虚引用关联的唯一目的就是希望能在这个对象 被收集器回收的时候收到一个系统通知。
垃圾收集算法
标记清除算法 优点:基于最基础的可达性分析算法,它是最基础的收集算法。 缺点: 1)效率问题 标记和清除两个过程的效率都不高。 2)空间问题 这会导致分配大内存对象时,无法找到足够的连续内存,从而提前触发一次垃圾收集。
标记复制算法 思路: 1)把内存划分为大小相等的两块,每次只使用其中一块。 2)当一块内存用完了,就将还存货的额对象复制到另一块上,而后使用这一块。 3)再把已使用过的那块内存空间一次清理掉,而后重复步骤2 优点: 1)这使得每次都是只对半个区进行内存回收。 2)内存分配时不用考虑内存碎片问题。 3)实现简单,运行高效。 缺点: 1)空间浪费。 2)效率随对象存活率升高而变低 当对象存活率较高时,需要进行较多的复制操作,效率将会变低。
标记整理算法 步骤: 1)标记阶段 标记阶段与标记清除算法一样。 2)整理 但后续不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动; 然后直接清理掉端边界意外的内存。 优点: 1)效率随存活对象升高而降低。 2)不会产生内存碎片。
分代收集算法 算法思路: 根据对象存活周期的不同将内存划分成几块; 这样可以根据各个年代的特点进行最适当的手机算法 1)新生代: 每次垃圾收集都有大批对象死去,只有少量存活。 采用标记复制算法。 2)老年代: 对象存活率高,没有额外的空间可以分配担保 采用标记清理算法或者标记整理算法。
垃圾收集器
Serial 串行收集器 采用标记复制算法 单线程,只会使用一个CPU或一条线程去完成垃圾收集工作。 另一方面也意味着它进行垃圾收集时,必须暂定其他所有的工作线程,知道它收集结束为止 这个过程也称为Stop The World。 应用: 桌面应用场景。单个CPU等。
ParNew 并行收集器 Serial收集器的多线程版本; 是许多运行在Server模式下的虚拟机中首选的新生代收集器,很重要的原因是:除 了Serial收集器外,只有ParNew收集器能与CMS收集器配合工作。它默认开启你的手机线程与 CPU数据相同。
Parallel Scavenge 并行清除收集器 新生代手机算法,使用标记复制算法,也是并行的多线程收集器。 Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。 CMS收集器关注的是尽可能缩短垃圾收集用户线程的停顿时间。 与ParNew收集器的重要区别就是它具有自适应调节策略 应用场景: Parallel Scavenge收集器是虚拟机运行在Server模式下的默认垃圾收集器。 停顿时间短适合需要与用户交互的程序,良好的响应速度能提升用户体验;高吞吐量则可以 高效率的利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务。 该收集器以高吞吐量为目标,就是减少垃圾收集时间,从而让用户代码获得更长的运行 时间,所以适合运行在多个CPU上,并且专注于后台计算的应用程序,例如:批量执行处理任务, 订单处理,工资支付,科学计算等。
Serial Old收集器 Serial收集器的老年版本,它同样是一个单线程收集器,使用标记-整理算法。
Parallel Old收集器 Parallel收集器的老年代版本,使用多线程和标记整理算法
CMS收集器 CMS(Concurrent Mark Sweep)收集器:基于标记清除算法实现,不会进行压缩,会 产生内存碎片,特点是:并发收集,低停顿。 应用场景: 与用户交互多的场景。 CMS收集器是一种以获得最短回收停顿时间为目标的收集器。 运作过程: 1)初始标记 初始标记仅仅只是标记一下GC ROOTS能直接关联到的对象,速度很快,但需要Stop The World; 2) 并发标记 并发标记阶段即使进行GC ROOTS Tracing的过程,刚才产生的集合中标记出存活 对象;应用程序也在运行,并不能保证可以标记处所有的存活对象。 3)重新标记 为了修正在并发标记期间因用户程序运行而导致标记产生变动的那一部分对象的标 记记录,然然需要Stop The World,这个阶段的停顿时间一般会比初始标记阶段 稍长一些,但远比并发标记的实际端。 5)并发清除 并发清除阶段会清除对象,回收所有的垃圾对象。 缺点: CMS收集器对CPU资源非常敏感。 CMS默认启动的线程数 = (CPU数量 + 3) / 4,当CPU不足4个时,CMS收集器对用户 的影响就可能变得更大,可能会无法接受 CMS收集器无法处理浮动垃圾 浮动垃圾: 由于CMS在并发清理阶段用户线程还在运行着,伴随程序自然就会有新的垃圾不断产生 ,这一部分垃圾出现在标记过程之后,CMS无法在档次垃圾中处理掉他们,只好留待下一次 GC是在清理掉。 由于在垃圾收集阶段用户线程还需要运行,那就需要预留足够的内存空间给用户线程,因此 CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了才进行垃圾收集,也可以认 为CMS所需要的内存空间比其他垃圾收集器要大: CMS收集器会产生大量的内存碎片 CMS收集器是一款基于标记,清除算法实现的收集器,清除后不进行压缩操作,这意味 着收集结束时会有大量的空间碎片产生。空间碎片过多时将会给大对象分配带来很大麻烦, 往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象, 不得不提前触发一次Full Gc. 解决办法: (1)、"-XX:+UseCMSCompactAtFullCollection" 使得CMS出现上面这种情况时不进行Full GC,而开启内存碎片的合并整理 过程; 但合并整理过程无法并发,停顿时间会变长; 默认开启(但不会进 行,结合下面的CMSFullGCsBeforeCompaction); (2)、"-XX:+CMSFullGCsBeforeCompaction" 设置执行多少次不压缩的Full GC后,来一次压缩整理; 为减少合并整理过 程的停顿时间; 默认为0,也就是说每次都执行Full GC,不会进行压缩整理;
G1 Garbage First收集器
触发GC的时机: 1)年轻代或者老年代慢了,JAVA虚拟机无法再为新的对象分配内存空间了,那么Java虚拟机就 会触发一次GC去回收掉那些已经不会再被使用到的对象。 2)手动调用System.gc()方法,通常这样会触发一次Full GC以及至少一次Minor GC.
JVM内存分配策略
GC Roots循环引用 可达性分析算法
FullGC的原因猜测: 1)CMSGC失败导致 2)大对象分配时,空间不够导致 3)内存碎片导致 设置打印FullGC的原因配置 jinfo -flag +PrintGCReason 总结: 1,问题可能出现的原因,要尽快动手去验证,不要只停留在思考的层面; 2,出现fullgc的时候,可以通过加上PrintGCReason,查看具体GC原因。