JVM垃圾回收
1.JVM中判断对象存活的方法
- 引用计数法
每个对象都有一个与之关联的引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1。
优点:高效、简单
缺点:无法解决循环引用问题 - 可达性分析算法
目前主流的商用程序语言(包括Java)主要采用的算法,它通过一系列的"GC Roots"作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到"GC Roots"没有任何引用链相连(即从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
GC Roots含有以下对象:
虚拟机栈中引用的对象(栈帧中的本地变量表)
方法区中静态属性引用的对象
方法区中常量引用的对象
本地方法栈内JNI(native)引用的对象
2.GC算法
- 标记-清除法
垃圾回收器首先标记出所有从根集合(如线程栈、静态域等)开始遍历能够到达的对象,这些为存活对象。标记完成后,清除阶段将回收掉所有未被标记的对象
缺点:效率不高,标记和清除都需要时间;标记清除后产生大量内存碎片 - 标记-整理法
标记-整理算法在标记-清除算法的基础上改进。首先标记所有存活的对象,然后,再让所有存活的对象都向一端移动,接着清理掉端边界以外的内存
优点:解决了标记-清除算法中内存碎片过多的问题
缺点:移动对象需要更多的时间和处理能力 - 复制算法
将可用内存分为两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次清理掉。
优点: 没有内存碎片,回收效率较高。
缺点: 直接明显的是内存利用率降低为原来的一半 - 增量收集
为了防止在GC期间长时间的停顿,增量收集算法将垃圾收集工作分成多个小步骤,分散到应用程序的执行中间。
优点: 减少单次垃圾收集时的停顿时间。
缺点: 相对提高了回收的总体开销,可能影响系统吞吐量。 - 分代收集算法
根据对象存活的时间将内存划分为几个区域,通常是新生代、老年代和永生代。新生代采用复制算法,老年代采用标记-清除或标记-整理算法。因为大多数对象生命周期短,这样可以使得大部分的内存回收都在新生代完成,提高回收效率。
优点: 提高了回收效率,减少了GC的停顿时间,更加适应对象生命周期规律。
缺点: 设计和实现相对复杂。
3.CMS
并发标记清除(Concurrent Mark-Sweep,简称CMS)垃圾回收器主要使用的算法是“并发标记-清除”算法。它旨在减少应用程序停顿的时间,特别适用于对停顿时间敏感的多线程应用。他的回收过程如下:
- 初始标记(stop-the-world)
这个阶段标记所有直接与GC根相连的对象。这个阶段很快,但是需要停止应用程序所有的工作线程 - 并发标记
在这个阶段,GC遍历堆中对象图,标记所有从GC根开始可达的对象。此阶段是并发执行的,不需要暂停应用线程 - 预清理
这个阶段的目的是处理自从并发标记阶段开始后,在应用线程运行期间产生的一些变动,由于此阶段并发执行,因此会有多次小的暂停 - 重新标记(stop-the-world)
重新标记阶段用来完成最终的标记,并处理自并发标记以来发生的任何变化。这一阶段通常使用算法(如增量更新或快照算法)来减少停顿时间,并且这里还包含了一次短暂的全线程暂停。 - 并发清除
在这个并发清除阶段,GC将清理那些在标记阶段确认为死亡的对象。此阶段同样并发执行,不停止应用线程。 - 并发重置
完成垃圾收集后,为下一个GC循环准备工作,这包括清除内部数据结构,为下次回收工作重置状态等。这一阶段也是并发执行的。
使用场景说明
- 低延迟要求
对于需要快速响应,不能承受长时间GC停顿的应用(如交互式网站、在线游戏、交易系统等),CMS因其具有较短的停顿时间而更为合适。 - 多核CPU环境
CMS可以充分利用多核CPU环境,因为它在进行垃圾收集时可以并发地执行,不会阻塞用户线程。 - 稳定负载的服务器
适用于服务负载相对平稳,具有足够处理能力来同时处理应用负载和垃圾收集任务的服务器
缺点:
内存碎片:CMS可能会导致较多的内存碎片,所以在内存分配大对象时可能会提前触发Full GC,相对增加了系统的不确定性。
资源占用:为了达到快速响应的目的,CMS通常会占用更多的CPU资源,如果在CPU负载已经很高的系统中使用CMS,可能会影响应用的整体性能。
预留内存问题:由于CMS不进行压缩,因此要求必须设置一定的预留内存(-XX:CMSInitiatingOccupancyFraction),以避免过频繁地执行Full GC。
基于上述缺点,如果你的应用未对停顿时间有特别需求,或者运行在资源较紧张的环境下,可能就不适合使用CMS。
4.G1
G1(Garbage-First)垃圾回收器使用的是一种结合了分代收集和分区(Region-based)的垃圾回收算法。与传统的分代收集器不同,G1将整个Java堆划分为多个大小相等的独立区域(Region),每个区域都可能扮演新生代、老年代或者是幸存者区(Survivor space)的角色。G1的目标是提供一个即可以处理大堆场景也能保持稳定停顿时间的收集器。
G1的工作过程如下
- 初始标记(stop-the-world)
此阶段标记所有从GC根直接可达的对象。这个阶段需要停止应用线程,但是停顿时间较短。 - 并发标记
在这个阶段中,G1收集器遍历堆中的对象图,标记所有存活对象。此阶段并发执行,不会停止应用线程。 - 最终标记 (stop-the-world)
G1在这个阶段完成存活对象的最终标记,它使用一种称为“快照-在-开始(Snapshot-At-The-Beginning, SATB)”的标记算法来记录并发标记阶段发生的引用变化。为了减少停顿时间,G1会使用多线程和一些算法优化,如记忆集(Remembered Set)清理和卡表(Card Table)扫描。最终标记阶段需要短暂停止应用线程。 - 筛选回收(stop-the-world)
在此阶段发生真正的垃圾收集行为,G1会选择一些区域进行清理,首先是完全空闲的区域,然后是混合区域(含有一定比例垃圾的老年代区域)。存活对象会被复制到其他区域,而被清理的区域会被完全回收。这个阶段需要停止应用线程,但G1的目标是使这个停顿时间变得可以预测和配置
使用场景说明
-
大内存应用:G1垃圾回收器特别适合需要大堆内存(Heap)的应用。由于G1通过将堆分割成多个区域(Region)来管理内存,这使得它能够更高效地处理大量数据,是处理大内存堆的理想选择。
-
对停顿时间敏感的应用:G1设计目标是为了提供可预测的停顿时间,通过允许用户设定期望的停顿时间(比如50毫秒),G1能够通过控制每次垃圾收集的工作量来满足这个目标。因此,对于需要低延迟、对停顿时间敏感的应用(如金融交易系统、大型在线游戏等),G1是一个很好的选择。
-
需要平滑的吞吐量的应用:对于那些需要同时保证较低的停顿时间和较高吞吐量的应用,G1提供了一种平衡方案。通过自适应的调整垃圾收集的策略,G1旨在最小化对应用吞吐量的影响。
-
需要灵活配置和调优的应用:G1提供了较为丰富的配置参数,允许开发者和调优专家根据具体应用特点进行详细的调优,以期达到最佳的性能表现。
CMS和G1优缺点对比
G1 垃圾回收器的优点
-
可预测停顿时间:G1提供了更可预测的停顿时间模型,允许用户指定期望的停顿时间目标。
-
改善内存碎片问题:通过在并发回收期间对堆进行增量压缩动作,G1可以减少内存碎片。
-
大堆优化:G1专为大内存堆设计,非常适合多核服务器环境,并能有效管理大规模的内存。
-
平衡吞吐量和停顿时间:G1综合考虑吞吐量和停顿时间,通过分区并行回收可以获得更好的总体性能。
-
灵活的堆划分:G1将堆划分成多个区域,可以细致地控制回收过程,优化GC的过程和暂停时间。
G1 垃圾回收器的缺点
-
资源占用:由于G1复杂的算法和并发性,它可能在某些情况下要比CMS消耗更多的CPU资源。
-
初始调优:G1在默认设置下可能不会立即提供最优性能,可能需要一些额外调优以适应特定应用。
过渡期的性能波动:G1在取代CMS过程中,在某些特定场景下可能会出现相对CMS的性能回退。
CMS 垃圾回收器的优点
-
低延迟:CMS设计目标是减少应用程序停顿时间。对于依赖较低延迟的应用程序,CMS仍然是一个不错选择。
-
成熟稳定:CMS已经存在多年,它在许多应用和环境中表现出了成熟和稳定。
CMS 垃圾回收器的缺点
-
内存碎片:CMS不能压缩堆,可能导致内存碎片,这在长时间运行的应用中会逐渐成为问题。
-
预留空间和并发失败:CMS需要较大的预留内存以避免并发模式失败导致的频繁Full GC,这限制了其在大内存应用中的效率。
-
停顿时间不可预测:CMS在某些情况下可能会有不可预测的长停顿时间,特别是在做Full GC的时候。
5.Safepoint
当发生 GC 时,用户线程必须全部停下来,才可以进行垃圾回收,这个状态我们可以认为 JVM 是安全的(safe),整个堆的状态是稳定的。如果在 GC 前,有线程迟迟进入不了 safepoint,那么整个 JVM 都在等待这个阻塞的线程,造成了整体 GC 的时间变长。
如何到达Safepoint
JVM利用两种主要的方式来使线程到达Safepoint:
主动式请求(Polling):这种方法下,JVM在执行字节码指令的过程中插入Safepoint检查点。这意味着线程将定期检查是否需要到达Safepoint。如果是,线程会在下一个Safepoint检查处主动停下来。
被动式等待:在这种策略下,如果一个线程因为执行某些系统调用或者等待I/O操作而被阻塞,它可以被视为已经处于Safepoint状态,因为这种情况下线程的状态是已知且控制的。
Safepoint的作用
达到Safepoint是垃圾回收和其他影响整个JVM状态的操作能够安全执行的前提条件。这个机制确保了在进行这些操作时,没有正在执行的线程会更改JVM的内存状态,这对于垃圾回收器正确识别可达对象、重新编译方法或执行其他需要全局视角的操作是十分重要的。
对性能的影响
尽管Safepoint是JVM正常操作的必要部分,但是当JVM频繁尝试到达Safepoints时,可能会对应用程序的性能产生影响。在这种情况下,应用的线程可能会因为需要等待到达Safepoint而经常停顿。这种停顿通常称为“Safepoint停顿”或“Safepoint同步时间”。根据JVM的设计和配置,以及应用程序的特性,这些停顿的影响会有所不同。
总的来说,Safepoint是JVM保证内存一致性和执行特定操作安全性的关键机制,尽管其实现和使用需要在性能和安全性之间做出平衡。
6.动态对象年龄判定
为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到阈值才能进入老年代。如果在 Survivor 区中相同年龄的所有对象的空间总和大于 Survivor 区空间的一半,则年龄大于或等于该年龄的对象直接进入老年代。
7.空间分配担保
在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的空间总和,如果这个条件成立,那么 Minor GC 可以确保是安全的。如果不成立则进行 Full GC。
8. JVM性能调优常用参数
jps:查看java进程及相关信息
jps -l 输出jar包路径,类全名
jps -m 输出main参数
jps -v 输出JVM参数
jinfo:查看JVM参数
jinfo 11666
jinfo -flags 11666
Xmx、Xms、Xmn、MetaspaceSize
jstat:查看JVM运行时的状态信息,包括内存状态、垃圾回收
jstat [option] LVMID [interval] [count]
常用的jstat选项
以下是一些jstat中常用的选项及其描述:
-class: 显示类装载情况。
-gc: 显示与垃圾回收(GC)相关的堆信息。
-gccapacity: 显示各个代(Young, Old Generations)的容量及其相应的空间。
-gccause: 显示GC统计信息(和-gcutil一起显示)以及最后一次或正在进行的GC事件的原因。
-gcutil: 显示GC的总结信息,例如堆各部分的使用百分比。
-gcnew: 显示新代的行为(即Young Generation)。
-gcnewcapacity: 显示新代(Young Generation)的各部分大小。
-gcold: 显示老代的行为(即Old Generation)。
-gcoldcapacity: 显示老代(Old Generation)的大小。
-gcmetacapacity: 显示元数据区的容量。
jstack:查看JVM线程快照,jstack命令可以定位线程出现长时间卡顿的原因,例如死锁,死循环
jstack [option] <java process id or core file name or remote debug server>