目录
一、背景
二、三者之间的区分
1、Minor GC
2、Major GC
(1)老年代空间不足:
(2)晋升(Promotion)失败:
(3)空间分配担保失败:
(4)显式调用:
3、Full GC
三、总结
四、JVM调优参数分享
一、背景
在我们工作中,虽然我们更多的是写代码的,但是Java的内存机制我们其实是无时无刻都在打交道,项目运行要多少内存评估,上线后是否能抗住流量,Java内存中新生代、老年代的比例设置比例是多少等等。这次主要是讲讲我们常接触的GC中的Minor GC、Major GC、FullGC到底有什么区别以及在工作中应该如何注意减少GC的频率
二、三者之间的区分
1、Minor GC
Minor GC,它的全称是(Young Generation Garbage Collection)是指对年轻代(Young Generation)进行的垃圾回收操作。所以说白了其实就是对年轻代的区域进行回收
在Java虚拟机中,堆内存被划分为不同的区域,其中年轻代是对象分配的主要区域。Minor GC主要负责回收年轻代的垃圾对象。
年轻代通常分为三个区域:Eden区和两个Survivor区(一般称为From区和To区)。当对象被创建时,它们会被分配到Eden区。在年轻代的垃圾回收过程中,首先会对Eden区进行垃圾回收,将存活的对象复制到一个空闲的Survivor区中(通常是To区),同时清空Eden区。如果Survivor区无法容纳所有存活的对象,一部分对象会被直接晋升到老年代(Tenured Generation)。
所以我们的Minor GC 在我们的程序运行的过程中其实是很频繁的,因为我们的代码如一些接口、消费者类,定时任务等都会很频繁的创建对象和销毁对象,这些对象大部分在新生代里面,有朝生夕死的特点在多次Minor GC后,存活时间较长的对象会逐渐被移到Survivor区,并经过多次复制和清理的过程。当对象经历了一定次数的复制后,会被认为是长时间存活的对象,最终会被晋升到老年代。
总结:Minor GC通常是并行或并发执行的,意味着在垃圾回收期间,应用程序的执行可能会暂停或降低速度。为了减少这种停顿时间,一些垃圾回收器,如并行垃圾回收器(Parallel GC)和G1垃圾回收器(Garbage-First GC),采用了并发标记和清理的方式。
总之,Minor GC是一种针对年轻代进行的垃圾回收操作,主要目的是回收年轻代的垃圾对象,以保证堆内存的有效利用和应用程序的性能。所以总而来说Minor GC在我们的应用程序的运行过程中,确实是比较频繁的操作,大家如果看到MinorGC 很频繁,也不必过分惊慌。
2、Major GC
什么是Major GC?
Major GC的全称是(Major Garbage Collection)是指对Java虚拟机中的老年代(Tenured Generation)进行的垃圾回收操作。与Minor GC主要关注年轻代的回收不同,Major GC专注于回收老年代中的垃圾对象。
所以其实你可以理解为和Minor GC的区别是前者是专门用于年轻代对象回收的机制,后者是用于老年代的对象回收。
在Java堆内存中,老年代用于存放生命周期较长的对象或者经过多次Minor GC后仍然存活的对象。随着时间的推移,老年代中的垃圾对象会逐渐增加,因此需要进行周期性的垃圾回收来释放这些占用的内存空间。
Major GC的触发条件通常是由JVM自动管理的,具体条件可能因不同的JVM实现和垃圾回收器而有所不同。它可能在以下情况下触发:
1. 老年代空间不足:当老年代无法容纳新对象或晋升对象时,会触发Major GC来回收老年代的垃圾对象。
2、永久代垃圾回收:如果使用的是传统的垃圾回收器(如Parallel GC、CMS等),那么Major GC也会包含对永久代(Permanent Generation)的垃圾回收操作,用于清理无效的类定义、常量等。
3、JVM显式调用:通过System.gc()或Runtime.getRuntime().gc()等方式显式调用垃圾回收,可能会触发Major GC。
Major GC的执行时间一般比Minor GC更长,因为它需要处理较多的对象和进行更复杂的内存整理操作。在Major GC期间,应用程序的执行将会暂停,直到垃圾回收操作完成。了解Major GC对于应用程序的性能分析和调优非常重要,适当配置堆大小、调整垃圾回收器参数等可以减少Major GC的频率和停顿时间,以提高应用程序的吞吐量和响应性能。
2、再完整总结下,什么情况下会触发Major GC?
Major GC(Major Garbage Collection)在Java虚拟机中会在以下情况下被触发:
(1)老年代空间不足:
当老年代(Tenured Generation)无法容纳新对象或晋升对象时,会触发Major GC来回收老年代的垃圾对象。这种情况通常发生在频繁创建大对象或者持久对象导致老年代空间快要满了的情况下。(这个应该是比较常见的一种现象,随着我们的程序运行的越来越久,生成的老年代的对象越来越多,可能就会触发Major GC来进行空间回收)
(2)晋升(Promotion)失败:
在年轻代(Young Generation)中的对象经过多次Minor GC后仍然存活并且达到了晋升的条件,但是老年代空间不足以容纳它们时,也会触发MajorGC。这种情况可能是因为年轻代中的对象生命周期较长,导致垃圾对象聚集在老年代中。(如果发生这种情况,我们应该注意,说明是不是我们程序是否有一些高频的创建对象的行为,并且是较大的对象,导致大量大对象往老年代迁移,具体的体现行为例如有:接口流量激增,定时任务查询的数据库数据过多,大数据量导出等,观察项目的运行情况有利于我们找到频繁Marjor GC的原因)
(3)空间分配担保失败:
在进行Minor GC时,如果老年代的连续内存空间不足以容纳晋升对象,JVM会尝试进行一次Minor GC并且通过移动对象来释放更多的连续空间。如果这个过程之后仍然无法满足空间需求,那么会触发Major GC。
(4)显式调用:
通过System.gc()或Runtime.getRuntime().gc()等方式显式调用垃圾回收,也有可能触发Major GC。不过请注意,Java虚拟机对于显式调用垃圾回收的处理是可选的,因此并不保证一定会触发Major GC。
总结:具体的Major GC触发条件可能因不同的JVM实现和垃圾回收器而有所不同。此外,Major GC的具体行为和执行策略也会受到所使用的垃圾回收器的影响。因此,在实际应用中,可以通过调整堆大小、调整垃圾回收器参数等方式来影响Major GC的触发频率和行为,以优化应用程序的性能。
3、Full GC
Full GC(Full Garbage Collection)是Java虚拟机(JVM)中的一种垃圾回收操作。它是指对整个堆内存进行回收,包括新生代和老年代。如果一旦遇到Full GC那一定得重视起来,因为可能程序Full GC的频率很高就会容易导致程序不稳定甚至崩溃。
在Java中,垃圾回收器通常会将堆内存划分为不同的区域,如新生代和老年代。当新生代空间不足时,会触发Minor GC,只清理新生代内存。而当老年代空间不足或者为了整理碎片化的内存,就有可能会触发Full GC,对整个堆内存进行回收。同时永久代(元空间)不足也有可能触发Full GC。本人就有一次在公司遇到频繁Full GC,就是因为元空间不足导致,调大了元空间的初始化内存即可进行优化。
Full GC 可能会导致较长的停顿时间,因为它需要扫描整个堆内存,标记可回收对象,并进行内存整理。这意味着在 Full GC 过程中,应用程序的执行会被暂停。
Full GC 的频率会受多种因素影响,如堆内存的大小、JVM配置参数、对象分配速度等。如果 Full GC 发生过于频繁或耗时过长,可能会导致应用程序的性能下降。所以一旦遇到频繁的Full GC一定要重视起来
为了减少 Full GC 的频率和时间,我们可以采取以下策略:
- 调整堆内存大小:适当设置堆内存大小,避免过小的情况。可以在一开始的时候就设置好初始化内存和最大内存一致,防止内存抖动导致应用程序不稳定。
- 设置堆内存
-
初始堆大小 (
-Xms
): 指定JVM启动时初始分配的堆内存大小。 -
最大堆大小 (
-Xmx
): 指定JVM可以使用的最大堆内存大小。
-
- 设置堆内存
- 优化对象分配:在代码里面能复用的对象尽量复用,不过频繁的创建对象,这样会导致程序不稳定以及触发Full GC。
- 尽可能使用较新的垃圾回收器,有条件用G1的就用G1,或者选择CMS,过老的垃圾回收器性能较差。
- 进行代码优化:减少内存泄漏和不必要的对象引用,使垃圾回收更高效。
三、总结
Minor GC(Young Generation Garbage Collection)和Major GC(Major Garbage Collection)、Full GC(Full Garbage Collection)都是Java虚拟机中的垃圾回收操作,但它们的执行对象和目的略有不同。
Minor GC主要针对年轻代(Young Generation)进行垃圾回收,即对Eden区和Survivor区进行清理。它的目的是回收年轻代的垃圾对象,以保证堆内存的有效利用和应用程序的性能。Minor GC通常频繁发生,但每次垃圾回收的停顿时间较短。
而Full GC是对整个堆内存进行垃圾回收,包括年轻代和老年代(Tenured Generation)。Full GC的触发条件相对复杂,通常情况下会在以下几种情况下触发:当年轻代无法容纳对象、永久代(Permanent Generation)满了。Full GC的目的是回收整个堆内存中的垃圾对象,并进行一些更为耗时的操作,如处理永久代中的无效类、对堆内存进行碎片整理等。因此,Full GC通常会导致较长的停顿时间,对应用程序的性能会产生比较大的影响。
四、JVM调优参数分享
这里分享几个JVM调优的参数,都是基于实际的场景并且经过验证、现在生产环境就是这样配置
-XX:+UseContainerSupport 基于容器化启动,参数允许JVM自动根据容器的资源限制来调整其内部资源分配,这样可以更好地利用容器的资源,同时避免因资源分配不当导致的性能问题。 容器化启动必备
-XX:MaxRAMPercentage=40.0 容器化JVM内存分配比例,假设容器是4g,则将JVM内存设置为1.6g
-XX:InitialRAMPercentage=40.0 同上
-XX:MinRAMPercentage=40.0 同上
-XX:MetaspaceSize=512m 元空间内存大小
-XX:MaxMetaspaceSize=512m 最大之所以设置和初始化一样,就是防止元空间自增导致的系统的抖动
-XX:+UseG1GC 使用G1垃圾回收器
-XX:+PrintGCDetails 打印GC日志,也是相当实用的一个配置
-XX:+PrintHeapAtGC 在GC的时候打印堆信息,用于在GC的时候辅助分析问题,也是比较实用
-XX:+PrintGCDateStamps 输出gc时间
-Xloggc:/app/logs/myapplication-%t.gclog gc日志位置
-XX:+HeapDumpOnOutOfMemoryError 用于当虚拟机出现内存溢出异常时,Dump出当前的内存堆转存快照以便事后分析
-XX:+ExitOnOutOfMemoryError JVM将在抛出OutOfMemoryError时立即退出,用于内存溢出时退出应用,以便容器重启应用,相当实用的一个配置
-XX:HeapDumpPath=/app/dump/myapplications.hprof dump文件地址
其实可以看到这里并没有太多的参数调优,实际情况下,JVM已经把垃圾回收器做得足够好了,很多时候它的默认参数已经是比较好的调优了,这里只是设置了一些堆的大小,包括记录一些内存溢出或者GC时的一些信息,方便分析问题。