GC

本文详细介绍了JAVA的垃圾回收机制,包括GC的触发条件、减少GC开销的措施、对象在JVM堆区的状态等内容。并通过对比不同垃圾收集算法的特点,帮助读者理解如何优化JAVA程序的性能。

2.JAVA的垃圾回收机制               GC通过确定对象是否被活动对象引用来确定是否收集该对象。

  2.1 触发GC(Garbage Collector)的条件

    1)GC在优先级最低的线程中运行,一般在应用程序空闲即没有应用线程在运行时被调用。但下面的条件例外。

    2)Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制调用GC线程。若GC一次之后仍不能满足内存分配,JVM会再进行两次GC,若仍无法满足要求,则JVM将报“out of memory”的错误,Java应用将停止。

 

  2.2 两个重要方法

    2.2.1 System.gc()方法

      使用System.gc()可以不管JVM使用的是哪一种垃圾回收的算法,都可以请求Java的垃圾回收。在命令行中有一个参数-verbosegc可以查看Java使用的堆内存的情况,它的格式如下:java -verbosegc classfile    由于这种方法会影响系统性能,不推荐使用,所以不详诉。

 

    2.2.2 finalize()方法

      在JVM垃圾回收器收集一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,Java提供了缺省机制来终止该对象心释放资源,这个方法就是finalize()。它的原型为:protected void finalize() throws Throwable   在finalize()方法返回之后,对象消失,垃圾收集开始执行。原型中的throws Throwable表示它可以抛出任何类型的异常。

      之所以要使用finalize(),是存在着垃圾回收器不能处理的特殊情况。例如:1)由于在分配内存的时候可能采用了类似 C语言的做法,而非JAVA的通常new做法。这种情况主要发生在native method中,比如native method调用了C/C++方法malloc()函数系列来分配存储空间,但是除非调用free()函数,否则这些内存空间将不会得到释放,那么这个时候就可能造成内存泄漏。但是由于free()方法是在C/C++中的函数,所以finalize()中可以用本地方法来调用它。以释放这些“特殊”的内存空间。2)又或者打开的文件资源,这些资源不属于垃圾回收器的回收范围。

  2.3 减少GC开销的措施

    1)不要显式调用System.gc()。此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。大大的影响系统性能。

     2)尽量减少临时对象的使用。临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。

    3)对象不用时最好显式置为Null。一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。

    4)尽量使用StringBuffer,而不用String来累加字符串。由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。

    5)能用基本类型如Int,Long,就不用Integer,Long对象。基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。

    6)尽量少用静态对象变量。静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

    7)分散对象创建或删除的时间。集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

  

  2.4 对象在JVM堆区的状态

    1)可触及状态:程序中还有变量引用,那么此对象为可触及状态。

    2)可复活状态:当程序中已经没有变量引用这个对象,那么此对象由可触及状态转为可复活状态。CG线程将在一定的时间准备调用此对象的finalize方法(finalize方法继承或重写子Object),finalize方法内的代码有可能将对象转为可触及状态,否则对象转化为不可触及状态。

    3)不可触及状态:只有当对象处于不可触及状态时,GC线程才能回收此对象的内存。

Jvm堆区对象状态转换图

 

  2.5 常用垃圾收集器

    1) 标记-清除收集器 Mark-Sweep

    2) 复制收集器        Copying  

    3) 标记-压缩收集器 Mark-Compact

    4) 分代收集器   Generational

 

  2.6 垃圾收集算法介绍

    2.6.1  tracing算法

      基于tracing算法的垃圾收集也称为标记和清除(mark-and-sweep)垃圾收集器.

      这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:

      从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。

     

    2.6.2 Copying算法

      为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示:

      这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。

    2.6.3 compacting算法

      为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:

    2.6.4 Generation算法 

      分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

      目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

      而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。

      

 

      新年代:新创建的对象都存放在这里。因为大多数对象很快变得不可达,所以大多数对象在年轻代中创建,然后消失。当对象从这块内存区域消失时,我们说发生了一次“minor GC”。

      老年代:没有变得不可达,存活下来的年轻代对象被复制到这里。这块内存区域一般大于年轻代。因为它更大的规模,GC发生的次数比在年轻代的少。对象从老年代消失时,我们说“major GC”(或“full GC”)发生了。

      上图中的永久代(permanent generation)也称为“方法区(method area)”,他存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。在这块内存中有可能发生垃圾回收。发生在这里垃圾回收也被称为major GC。

    对于分代算法,我推荐一篇博客给大家:http://blog.jobbole.com/80499/

<think>好的,用户现在想了解Java中的Full GC(Full Garbage Collection)的原因及优化方法。我需要结合之前的对话历史和提供的引用内容来构建回答。首先,回顾用户之前的问题,他们询问了MyBatis的一对多映射配置,现在转向了JVM的垃圾回收机制,特别是Full GC。这可能意味着用户正在处理性能调优的问题,可能在他们的应用中遇到了Full GC频繁发生的情况,导致应用停顿或性能下降。 首先,我需要明确Full GC的定义和触发原因。根据引用[1]、[3]和[4],Full GC涉及整个内存(包括年轻代和老年代)以及方法的回收,是重量级操作,会导致Stop-The-World停顿。用户可能已经知道这些基本概念,但需要深入的原因分析和优化策略。 接下来,我需要列举Full GC的常见原因。引用中提到内存泄漏、对象晋升过快、System.gc()调用、内存设置不当、大对象分配、元空间不足、GC策略不合理等。需要详细解释每个原因,并结合例子帮助用户理解。例如,内存泄漏可能导致老年代逐渐填满,最终触发Full GC,但回收效果差,频繁触发。 然后,优化策略部分需要涵盖内存分析、大小调整、避免手动触发GC、分代优化、选择合适GC算法、大对象处理、元空间监控、代码优化等。引用[2]提到G1垃圾回收器的使用,可以建议用户根据应用场景选择合适的GC算法,比如G1或ZGC来减少停顿时间。 还需要注意用户可能遇到的常见问题,如如何监控Full GC、如何分析内存泄漏等。引用[4]提到监控GC日志和内存使用情况,可以推荐工具如VisualVM、MAT等,帮助用户实际操作。 此外,用户可能对如何具体设置JVM参数感兴趣,比如调整-Xmx、-Xms,或者使用-XX:+UseG1GC等。需要给出具体的参数示例,并解释每个参数的作用,如避免显式GC调用使用-XX:+DisableExplicitGC。 最后,确保回答结构清晰,分点列出原因、优化策略和常见问题,使用用户提供的引用内容作为支持,比如引用[1]说明手动触发Full GC的方法,引用[4]强调监控的重要性。同时,用户可能需要实际案例或步骤指导,因此在优化策略中加入分析步骤,如生成Heap Dump,使用工具分析等。 总结,用户的需求是全面了解Full GC的原因及优化方法,可能实际应用中遇到了性能问题,需要具体的解决方案和调优建议。因此,回答需要详细、结构化,并结合实际案例和工具推荐,帮助用户有效解决问题。</think>### Java Full GC 原因分析及优化策略 --- #### **一、Full GC 核心机制** Full GC(完全垃圾回收)是 JVM 对**整个内存(年轻代+老年代)及方法(元空间)**的全局回收操作,具有以下特点: - **Stop-The-World(STW)**:暂停所有应用线程,导致服务延迟[^1][^3] - **触发条件复杂**:老年代空间不足、方法溢出、显式调用`System.gc()`等[^1][^3] - **性能影响显著**:回收时间与内存大小成正比,通常超过 1 秒即需优化[^4] --- #### **二、Full GC 常见原因** | **原因分类** | **典型场景** | **关联引用** | |----------------------|-----------------------------------------------------------------------------|------------| | **内存泄漏** | 对象长期被错误引用(如静态集合、未关闭的连接池)导致老年代无法回收[^3] | [^1][^3] | | **对象晋升过快** | 年轻代空间过小或 Survivor 配置不合理,对象过早进入老年代 | [^4] | | **显式调用 System.gc** | 代码中直接调用`System.gc()`或通过 RMI 等框架间接触发 | [^1] | | **内存分配不合理** | 老年代空间不足(如`-Xmx`设置过小)或元空间(`-XX:MetaspaceSize`)溢出 | [^2][^4] | | **大对象分配** | 直接进入老年代的大对象(如未压缩的图片、缓存数据)占用过多空间 | [^3] | | **GC 策略不匹配** | 选择低效的垃圾回收器(如 Parallel GC 处理大) | [^2] | --- #### **三、优化策略与实战方案** ##### **1. 内存分析与泄漏排查** - **步骤 1:生成 Heap Dump** ```bash # 通过 jmap 导出内存快照 jmap -dump:format=b,file=heapdump.hprof <pid> ``` - **步骤 2:使用工具分析** 推荐工具:Eclipse MAT、VisualVM - **Dominator Tree**:定位内存占用最高的对象 - **Leak Suspects Report**:自动检测潜在泄漏点[^3] ##### **2. 内存配置优化** - **调整年轻代与老年代比例**(适用于 CMS/G1) ```bash # 年轻代占比(默认 1/3) -XX:NewRatio=2 # Eden 与 Survivor 比例(默认 8:1:1) -XX:SurvivorRatio=8 ``` - **避免元空间动态扩展** ```bash # 固定元空间大小 -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M ``` ##### **3. 分代回收策略优化** - **年轻代调优** ```bash # 增大年轻代空间(降低对象晋升频率) -Xmn512m # 设置晋升阈值(默认 15) -XX:MaxTenuringThreshold=10 ``` - **大对象直接进入老年代** ```bash # 对象超过 4MB 直接分配至老年代(默认 0,表示由 G1 自动管理) -XX:PretenureSizeThreshold=4194304 ``` ##### **4. 选择合适的垃圾回收器** | **GC 类型** | **适用场景** | **配置示例** | |------------|-------------------------------|---------------------------------------| | **G1** | 大(>4GB)、低延迟需求 | `-XX:+UseG1GC -XX:MaxGCPauseMillis=200` | | **ZGC** | 超大(TB 级)、极低停顿 | `-XX:+UseZGC -Xmx16g` | | **Shenandoah** | 平衡吞吐量与延迟 | `-XX:+UseShenandoahGC` | ##### **5. 其他关键配置** - **禁止显式触发 Full GC** ```bash -XX:+DisableExplicitGC # 屏蔽 System.gc() ``` - **Full GC 日志监控** ```bash -XX:+PrintGCDetails -Xloggc:/path/to/gc.log ``` --- #### **四、高频问题解答** 1. **如何判断 Full GC 是否频繁?** - 监控`jstat -gcutil <pid>`中的 **FGCT(Full GC 总耗时)** - 日志中搜索关键字 **`Full GC`** 统计频率 2. **Full GC 后内存未释放怎么办?** - 检查内存泄漏(参考 MAT 分析) - 增大内存或优化对象生命周期管理 3. **G1 如何减少 Full GC?** - 启用并发标记:`-XX:+G1ConcurrentMark` - 避免 Mixed GC 失败:调整`-XX:InitiatingHeapOccupancyPercent`(默认 45%) --- #### **五、应用场景与案例** - **电商大促**:通过增大年轻代和调整 G1 的`MaxGCPauseMillis`,将 Full GC 频率从 10 次/分钟降至 0.5 次/小时[^4] - **实时交易系统**:使用 ZGC 将 Full GC 停顿时间从 2 秒缩短至 10ms 以内 --- ### 相关问题 1. 如何通过 JVM 参数限制年轻代对象晋升到老年代的速度?[^1][^4] 2. G1 垃圾回收器的 Mixed GC 与 Full GC 有什么别?[^2] 3. 哪些工具可以实时监控 Java 应用的 Full GC 状态?[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值