精华内容
下载资源
问答
  • GC

    2020-03-23 09:45:21
    在面试中我们会经常被问到full Gc相关的问题,比如什么情况下会发生full gc,如何去排查频繁发生full Gc的问题等。要想轻松自如的回答这些问题,我们就必须充分的去理解gc的触发条件,gc回收的内容,以及gc具体的执行...

    在面试中我们会经常被问到full Gc相关的问题,比如什么情况下会发生full gc,如何去排查频繁发生full Gc的问题等。要想轻松自如的回答这些问题,我们就必须充分的去理解gc的触发条件,gc回收的内容,以及gc具体的执行过程。掌握了这3个要点,full gc相关的问题就易如反掌了。

    一、gc的定义

    GC,即就是Java垃圾回收机制。目前主流的JVM(HotSpot)采用的是分代收集算法。与C++不同的是,Java采用的是类似于树形结构的可达性分析法来判断对象是否还存在引用。即:从gcroot开始,把所有可以搜索得到的对象标记为存活对象。

    二、gc的基础知识准备

    要了解GC的触发条件,就要先对 JVM的内存结构有一定的了解。我们通常所说的GC主要是针对运行的数据区的。作为程序员要关注的区域主要有5块,分别是方法区(Method Area),Java栈(Java stack),本地方法栈(Native Method Stack),堆(Heap),程序计数器(Program Counter Register)。实际jvm在管理内存的时候,比这个分的更细致,只不过做应用程序开发,我们只需要关注这5块就可以了。
    在这里插入图片描述

    堆(Heap),是Jvm管理的内存中最大的一块。程序的主要数据也都是存放在堆内存中的,这一块区域被所有的线程所共享,通常出现线程安全问题的一般都是这个区域的数据出现的问题。

    方法区(Method Area),与Heap一样,也是各个线程共享的内存域,这块区域主要是用来存储类加载器加载的类信息,常量,静态变量,通俗的讲就是编译后的class文件信息。

    Jvm栈,与程序计数器一样,它是每个线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

    本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

    程序计数器,个人感觉的他就是为多线程准备的,程序计数器是每个线程独有的,所以是线程安全的。它主要用于记录每个线程的执行情况。

    通常我们所说的gc主要是针对java heap这块区域的。下面来了解一下heap区。
    在这里插入图片描述

    从图中我们可以看出jvm heap区域是分代的,分为年轻代,老年代和持久代。 JVM的堆区对象分配的一般规则:

    1. 对象优先在Eden区分配

    2. 大对象直接进入老年代(-XX:PretenureSizeThreshold=3145728 这个参数来定义多大的对象直接进入老年代)

    3. 长期存活的对象将进入老年代(在JDK8中测试,-XX:MaxTenuringThreshold=1的阀值设定根本没用)

    4. 动态对象年龄判定(虚拟机并不会永远地要求对象的年龄都必须达到MaxTenuringThreshold才能晋升老年代,如果Survivor空间中相同年龄的所有对象的大小总和大于Survivor的一半,年龄大于或等于该年龄的对象就可以直接进入老年代)

    5. 空间分配担保

    6. 只要老年代的连续空间大于(新生代所有对象的总大小或者历次晋升的平均大小)就会进行minor GC,否则会进行full GC

    三、gc的触发条件

    充分了解了jvm的内存结构之后,下面我们就来说说什么情况下会触发gc。触发full gc的情况主要有这几种:

    (1)System.gc()方法的调用。此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。

    (2)旧生代空间不足。旧生代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出错误:java.lang.OutOfMemoryError: Java heap space 。为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

    (3)Permanet Generation空间满了。Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出错误信息:java.lang.OutOfMemoryError: PermGen space 。为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

    (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC。

    (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

    四、gc回收的内容

    知道了gc触发的条件之后,我们就能知道gc主要回收什么了?gc的主要作用是回收堆中的对象。通过可达性分析一个对象的引用是否存在,如果不存在,就可以被回收了。

    四、gc的具体过程

    那么,gc的是如何实现的,这个主要看是用的哪一种回收算法以及用的什么垃圾回收集了。回收算法主要有:

    标记-清除
    复制算法
    标记-整理(Mark-Compat)算法
    分代收集(Generational Collection)算法
    这里针对不同的代,可以使用一些相对合适的算法。

    新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;

    老年代中,其存活率较高、没有额外空间对它进行分配担保,就应该使用“标记-整理”或“标记-清理”算法进行回收。

    常用的垃圾回收器:

    Serial收集器
    ParNew收集器
    Parallel Scavenge收集器
    CMS(Concurrent Mark Sweep)收集器
    G1(Garbage First)收集器(从JDK1.7 Update 14之后的HotSpot虚拟机正式提供了商用的G1收集器)
    关于垃圾回收器的使用,这里也有一个组合建议共大家参考:

    在这里插入图片描述

    展开全文
  • Gc

    2016-01-26 17:32:51
    一、为什么需要GC 应用程序对资源操作,通常简单分为以下几个步骤: 1、为对应的资源分配内存 2、初始化内存 3、使用资源 4、清理资源 5、释放内存 应用程序对资源(内存使用)管理的方式,常见的一般有如下...

    一、为什么需要GC

    应用程序对资源操作,通常简单分为以下几个步骤:

    1、为对应的资源分配内存

    2、初始化内存

    3、使用资源

    4、清理资源

    5、释放内存

    应用程序对资源(内存使用)管理的方式,常见的一般有如下几种:

    1、手动管理:C,C++

    2、计数管理:COM

    3、自动管理:.NET,Java,PHP,GO…

    但是,手动管理和计数管理的复杂性很容易产生以下典型问题:

    1.程序员忘记去释放内存

    2.应用程序访问已经释放的内存

    产生的后果很严重,常见的如内存泄露、数据内容乱码,而且大部分时候,程序的行为会变得怪异而不可预测,还有Access Violation等。

    .NET、Java等给出的解决方案,就是通过自动垃圾回收机制GC进行内存管理。这样,问题1自然得到解决,问题2也没有存在的基础。

    总结:无法自动化的内存管理方式极容易产生bug,影响系统稳定性,尤其是线上多服务器的集群环境,程序出现执行时bug必须定位到某台服务器然后dump内存再分析bug所在,极其打击开发人员编程积极性,而且源源不断的类似bug让人厌恶。

     

    二、GC是如何工作的

    GC的工作流程主要分为如下几个步骤:

    1、标记(Mark)

    2、计划(Plan)

    3、清理(Sweep)

    4、引用更新(Relocate)

    5、压缩(Compact)

    GC

    (一)、标记

    目标:找出所有引用不为0(live)的实例

    方法:找到所有的GC的根结点(GC Root), 将他们放到队列里,然后依次递归地遍历所有的根结点以及引用的所有子节点和子子节点,将所有被遍历到的结点标记成live。弱引用不会被考虑在内

    (二)、计划和清理

    1、计划

    目标:判断是否需要压缩

    方法:遍历当前所有的generation上所有的标记(Live),根据特定算法作出决策

    2、清理

    目标:回收所有的free空间

    方法:遍历当前所有的generation上所有的标记(Live or Dead),把所有处在Live实例中间的内存块加入到可用内存链表中去

    (三)、引用更新和压缩

    1、引用更新

    目标: 将所有引用的地址进行更新

    方法:计算出压缩后每个实例对应的新地址,找到所有的GC的根结点(GC Root), 将他们放到队列里,然后依次递归地遍历所有的根结点以及引用的所有子节点和子子节点,将所有被遍历到的结点中引用的地址进行更新,包括弱引用。

    2、压缩

    目标:减少内存碎片

    方法:根据计算出来的新地址,把实例移动到相应的位置。

     

    三、GC的根节点

    本文反复出现的GC的根节点也即GC Root是个什么东西呢?

    每个应用程序都包含一组根(root)。每个根都是一个存储位置,其中包含指向引用类型对象的一个指针。该指针要么引用托管堆中的一个对象,要么为null。

    在应用程序中,只要某对象变得不可达,也就是没有根(root)引用该对象,这个对象就会成为垃圾回收器的目标。

    用一句简洁的英文描述就是:GC roots are not objects in themselves but are instead references to objects.而且,Any object referenced by a GC root will automatically survive the next garbage collection. 

    .NET中可以当作GC Root的对象有如下几种:

    1、全局变量

    2、静态变量

    3、栈上的所有局部变量(JIT)

    4、栈上传入的参数变量

    5、寄存器中的变量

    注意,只有引用类型的变量才被认为是根,值类型的变量永远不被认为是根。只有深刻理解引用类型和值类型的内存分配和管理的不同,才能知道为什么root只能是引用类型。

    顺带提一下JAVA,在Java中,可以当做GC Root的对象有以下几种:

    1、虚拟机(JVM)栈中的引用的对象

    2、方法区中的类静态属性引用的对象

    3、方法区中的常量引用的对象(主要指声明为final的常量值)

    4、本地方法栈中JNI的引用的对象

     

    四、什么时候发生GC

    1、当应用程序分配新的对象,GC的代的预算大小已经达到阈值,比如GC的第0代已满

    2、代码主动显式调用System.GC.Collect()

    3、其他特殊情况,比如,windows报告内存不足、CLR卸载AppDomain、CLR关闭,甚至某些极端情况下系统参数设置改变也可能导致GC回收

     

    五、GC中的代

    代(Generation)引入的原因主要是为了提高性能(Performance),以避免收集整个堆(Heap)。一个基于代的垃圾回收器做出了如下几点假设:

    1、对象越新,生存期越短

    2、对象越老,生存期越长

    3、回收堆的一部分,速度快于回收整个堆

    .NET的垃圾收集器将对象分为三代(Generation0,Generation1,Generation2)。不同的代里面的内容如下:

    1、G0 小对象(Size<85000Byte)

    2、G1:在GC中幸存下来的G0对象

    3、G2:大对象(Size>=85000Byte);在GC中幸存下来的G1对象

      object o = new Byte[85000]; //large object
      Console.WriteLine(GC.GetGeneration(o)); //output is 2,not 0

    ps,这里必须知道,CLR要求所有的资源都从托管堆(managed heap)分配,CLR会管理两种类型的堆,小对象堆(small object heap,SOH)和大对象堆(large object heap,LOH),其中所有大于85000byte的内存分配都会在LOH上进行。一个有趣的问题是为什么是85000字节?

    代收集规则:当一个代N被收集以后,在这个代里的幸存下来的对象会被标记为N+1代的对象。GC对不同代的对象执行不同的检查策略以优化性能。每个GC周期都会检查第0代对象。大约1/10的GC周期检查第0代和第1代对象。大约1/100的GC周期检查所有的对象。

     

    六、谨慎显式调用GC

    GC的开销通常很大,而且它的运行具有不确定性,微软的编程规范里是强烈建议你不要显式调用GC。但你的代码中还是可以使用framework中GC的某些方法进行手动回收,前提是你必须要深刻理解GC的回收原理,否则手动调用GC在特定场景下很容易干扰到GC的正常回收甚至引入不可预知的错误。

    比如如下代码:

    复制代码
            void SomeMethod()
            {
                object o1 = new Object();
                object o2 = new Object();
    
                o1.ToString();
                GC.Collect(); // this forces o2 into Gen1, because it's still referenced
                o2.ToString();
            }
    复制代码

    如果没有GC.Collect(),o1和o2都将在下一次垃圾自动回收中进入Gen0,但是加上GC.Collect(),o2将被标记为Gen1,也就是0代回收没有释放o2占据的内存

    还有的情况是编程不规范可能导致死锁,比如流传很广的一段代码:

      MyClass

    通过如下代码进行调用:

    复制代码
               var instance = new MyClass();
    
                Monitor.Enter(instance);
                instance = null;
    
                GC.Collect();
                GC.WaitForPendingFinalizers();
              
                Console.WriteLine("instance is gabage collected");
    复制代码

    上述代码将会导致死锁。原因分析如下:

    1、客户端主线程调用代码Monitor.Enter(instance)代码段lock住了instance实例

    2、接着手动执行GC回收,主(Finalizer)线程会执行MyClass析构函数

    3、在MyClass析构函数内部,使用了lock (this)代码,而主(Finalizer)线程还没有释放instance(也即这里的this),此时主线程只能等待

    虽然严格来说,上述代码并不是GC的错,和多线程操作似乎也无关,而是Lock使用不正确造成的。

    同时请注意,GC的某些行为在Debug和Release模式下完全不同(Jeffrey Richter在<<CLR Via C#>>举过一个Timer的例子说明这个问题)。比如上述代码,在Debug模式下你可能发现它是正常运行的,而Release模式下则会死锁。

     

    七、当GC遇到多线程

    这一段主要参考<<CLR Via C#>>的线程劫持一节。

    前面讨论的垃圾回收算法有一个很大的前提就是:只在一个线程运行。而在现实开发中,经常会出现多个线程同时访问托管堆的情况,或至少会有多个线程同时操作堆中的对象。一个线程引发垃圾回收时,其它线程绝对不能访问任何线程,因为垃圾回收器可能移动这些对象,更改它们的内存位置。CLR想要进行垃圾回收时,会立即挂起执行托管代码中的所有线程,正在执行非托管代码的线程不会挂起。然后,CLR检查每个线程的指令指针,判断线程指向到哪里。接着,指令指针与JIT生成的表进行比较,判断线程正在执行什么代码。

    如果线程的指令指针恰好在一个表中标记好的偏移位置,就说明该线程抵达了一个安全点。线程可在安全点安全地挂起,直至垃圾回收结束。如果线程指令指针不在表中标记的偏移位置,则表明该线程不在安全点,CLR也就不会开始垃圾回收。在这种情况下,CLR就会劫持该线程。也就是说,CLR会修改该线程栈,使该线程指向一个CLR内部的一个特殊函数。然后,线程恢复执行。当前的方法执行完后,他就会执行这个特殊函数,这个特殊函数会将该线程安全地挂起。然而,线程有时长时间执行当前所在方法。所以,当线程恢复执行后,大约有250毫秒的时间尝试劫持线程。过了这个时间,CLR会再次挂起线程,并检查该线程的指令指针。如果线程已抵达一个安全点,垃圾回收就可以开始了。但是,如果线程还没有抵达一个安全点,CLR就检查是否调用了另一个方法。如果是,CLR再一次修改线程栈,以便从最近执行的一个方法返回之后劫持线程。然后,CLR恢复线程,进行下一次劫持尝试。所有线程都抵达安全点或被劫持之后,垃圾回收才能使用。垃圾回收完之后,所有线程都会恢复,应用程序继续运行,被劫持的线程返回最初调用它们的方法。

    实际应用中,CLR大多数时候都是通过劫持线程来挂起线程,而不是根据JIT生成的表来判断线程是否到达了一个安全点。之所以如此,原因是JIT生成表需要大量内存,会增大工作集,进而严重影响性能。

    概念叙述到此结束,手都抄软了^_^,这书卖的贵和书里面的理论水平一样有道理。

    这里再说一个真实案例。某web应用程序中大量使用Task,后在生产环境发生莫名其妙的现象,程序时灵时不灵,根据数据库日志(其实还可以根据Windows事件跟踪(ETW)、IIS日志以及dump文件),发现了Task执行过程中有不规律的未处理的异常,分析后怀疑是CLR垃圾回收导致,当然这种情况也只有在高并发条件下才会暴露出来。

     

    八、开发中的一些建议和意见

    由于GC的代价很大,平时开发中注意一些良好的编程习惯有可能对GC有积极正面的影响,否则有可能产生不良效果。

    1、尽量不要new很大的object,大对象(>=85000Byte)直接归为G2代,GC回收算法从来不对大对象堆(LOH)进行内存压缩整理,因为在堆中下移85000字节或更大的内存块会浪费太多CPU时间

    2、不要频繁的new生命周期很短object,这样频繁垃圾回收频繁压缩有可能会导致很多内存碎片,可以使用设计良好稳定运行的对象池(ObjectPool)技术来规避这种问题

    3、使用更好的编程技巧,比如更好的算法、更优的数据结构、更佳的解决策略等等

    update:.NET4.5.1及其以上版本已经支持压缩大对象堆,可通过System.Runtime.GCSettings.LargeObjectHeapCompactionMode进行控制实现需要压缩LOH。可参考这里

    根据经验,有时候编程思想里的空间换时间真不能乱用,用的不好,不但系统性能不能保证,说不定就会导致内存溢出(Out Of Memory),关于OOM,可以参考我之前写过的一篇文章有效预防.NET应用程序OOM的经验备忘

    之前在维护一个系统的时候,发现有很多大数据量的处理逻辑,但竟然都没有批量和分页处理,随着数据量的不断膨胀,隐藏的问题会不断暴露。然后我在重写的时候,都按照批量多次的思路设计实现,有了多线程、多进程和分布式集群技术,再大的数据量也能很好处理,而且性能不会下降,系统也会变得更加稳定可靠。

     

    九、GC线程和Finalizer线程

    GC在一个独立的线程中运行来删除不再被引用的内存。

    Finalizer则由另一个独立(高优先级CLR)线程来执行Finalizer的对象的内存回收。

    对象的Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间,并非和C++中一样在对象超出生命周期时立即执行析构函数。

    GC把每一个需要执行Finalizer的对象放到一个队列(从终结列表移至freachable队列)中去,然后启动另一个线程而不是在GC执行的线程来执行所有这些Finalizer,GC线程继续去删除其他待回收的对象。

    在下一个GC周期,这些执行完Finalizer的对象的内存才会被回收。也就是说一个实现了Finalize方法的对象必需等两次GC才能被完全释放。这也表明有Finalize的方法(Object默认的不算)的对象会在GC中自动“延长”生存周期。

    特别注意:负责调用Finalize的线程并不保证各个对象的Finalize的调用顺序,这可能会带来微妙的依赖性问题(见<<CLR Via C#>>一个有趣的依赖性问题)。

    最后感慨一下,反复看一本好书远远比看十本二十本不那么靠谱的书收获更多。

    参考:

    <<CLR Via C#>>

    <<深入理解Java虚拟机>>

    <<C# In Depth>>

    <<Think In Java>>

    https://msdn.microsoft.com/en-us/library/ms979205.aspx

    http://msdn.microsoft.com/zh-cn/magazine/cc188793%28en-us%29.aspx

     


    作者:Jeff Wong 
    出处:http://jeffwongishandsome.cnblogs.com/ 
    本文版权归作者和博客园共有,欢迎围观转载。转载时请您务必在文章明显位置给出原文链接,谢谢您的合作。 

    展开全文
  • Minor GC与Full GC

    2020-01-04 22:02:05
    Minor GC与Full GC Minor GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存 Major GC:对老年代GC。 Full GC:是对整个堆来说的,在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了...

    Minor GC与Full GC

    Minor GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存
    Major GC:对老年代GC。
    Full GC:是对整个堆来说的,在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的。
    补充:堆=新生代+老年代,不包括永久代(方法区)。

    Full GC触发的条件

    1、System.gc()方法的调用

    此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。

    2、老年代代空间不足

    老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
    java.lang.OutOfMemoryError: Java heap space
    为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

    3、永生区空间不足

    JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
    java.lang.OutOfMemoryError: PermGen space
    为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

    4、CMS GC时出现promotion failed和concurrent mode failure
    参考博文

    对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能

    会触发Full GC。
    promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;多数是由于老年带有足够的空闲空间,但是由于碎片较多,这时如果新生代要转移到老年带的对象比较大,所以,必须尽可能提早触发老年代的CMS回收来避免这个问题
    解决:
     解决这个问题的办法就是可以让CMS在进行一定次数的Full GC(标记清除)的时候进行一次标记整理算法,CMS提供了以下参数来控制:

    -XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5
    

    也就是CMS在进行5次Full GC(标记清除)之后进行一次标记整理算法,从而可以控制老年带的碎片在一定的数量以内,甚至可以配置CMS在每次Full GC的时候都进行内存的整理。
    concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。
    对措施为:增大survivor space、老年代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕
    解决:
     解决这个问题的通用方法是调低触发CMS GC执行的阀值,CMS GC触发主要由CMSInitiatingOccupancyFraction值决定,默认情况是当旧生代已用空间为68%时,即触发CMS GC,在出现concurrent mode failure的情况下,可考虑调小这个值,提前CMS GC的触发,以保证旧生代有足够的空间。

    5、统计得到的Minor GC晋升到老生代的平均大小大于老年代的剩余空间(这种情况是允许空间担保机制的情况

    这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之

    前统计所得到的Minor GC晋升到老生代的平均大小大于老生代的剩余空间,那么就直接触发Full GC。
    例如程序第一次触发Minor GC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,

    则执行Full GC。
    当新生代采用PS GC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否

    大于6MB,如小于,则触发对旧生代的回收。
    除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- java -

    Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。

    6、堆中分配很大的对象

    所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。

    为了解决这个问题,CMS垃圾收集器提供了一个可配置的参数,即-XX:+UseCMSCompactAtFullCollection开关参数,用于在“享受”完Full GC服务之后额外免费赠送一个碎片整理的过程,内存整理的过程无法并发的,空间碎片问题没有了,但提顿时间不得不变长了,JVM设计者们还提供了另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。

    永久代

    永久代回收主要是:废弃常量和无用的类。在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间。
    判断一个常量是废弃常量

    • 判断该对象是否被引用

    判断一个类是否是无用的类

    • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
    • 加载该类的 ClassLoader 已经被回收。
    • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
    展开全文
  • GC优化

    2017-03-29 19:21:04
    在第一篇《成为JavaGC专家Part I — 深入浅出Java垃圾回收机制》中我们学习了不同GC算法的执行过程,GC是如何工作的,什么是新生代和老年代,你应该了解的JDK7中的5种GC类型,以及这5种类型对于应用性能的影响。...

      本文是成为Java GC专家系列文章的第三篇。在第一篇《成为JavaGC专家Part I — 深入浅出Java垃圾回收机制》中我们学习了不同GC算法的执行过程,GC是如何工作的,什么是新生代和老年代,你应该了解的JDK7中的5种GC类型,以及这5种类型对于应用性能的影响。

      在第二篇《成为JavaGC专家Part II — 如何监控Java垃圾回收机制》,我解释了JVM实际上是如何执行垃圾回收的,我们如何监控GC,以及那哪些具可以让我们的工作更快,更高效。在第三篇文章中,我们会基于实际的例子来解释一些优化GC的最佳实践。我认为在阅读本篇文章之前,你已经很好地理解了之前的文章,因此,为了你能够更好地学习本文,如果你还没有读过之前的两篇文章话,请先阅读。


    为什么需要优化GC


      或者说的更确切一些,对于基于Java的服务,是否有必要优化GC?应该说,对于所有的基于Java的服务,并不总是需要进行GC优化,但前提是所运行的基于Java的系统,包含了如下参数或行为:

    • 已经通过 -Xms 和–Xmx 设置了内存大小
    • 包含了 -server 参数
    • 系统中没有超时日志等错误日志

      换句话说,如果你没有设定内存的大小,并且系统充斥着大量的超时日志时,你就需要在你的系统中进行GC优化了。

      但是,你需要时刻铭记一条:GC优化永远是最后一项任务。

      想一下进行GC优化的最根本原因,垃圾收集器清除在Java程序中创建的对象,GC执行的次数即需要被垃圾收集器清理的对象个数,与创建对象的数量成正比,因此,首先你应该减少创建对象的数量。

      俗话说的好,“冰冻三尺非一日之寒”。我们应该从小事做起,否则日积月累就会很难管理。

    • 我们需要使用StringBuilder 或者StringBuffer 来替代String
    • 应该尽量少的输出日志

      但是,我们知道有些情况会让我们束手无策,我们眼睁睁的看着XML以及JSON解析占用了大量的内存。即便我们已经尽可能少的使用String以及尽量少的输出日志,大量的临时内存被用于XML或者JSON解析,例如10-100MB。但是,舍弃XML和JSON是很难的。我们只要知道,他会占用很多内存。

      如果应用内存使用量经过几次重复调整之后有所改善,你就可以开始GC优化了。

      我为GC优化归纳了两个目的:

    1. 一个是将转移到老年代的对象数量降到最少
    2. 另一个是减少Full GC的执行时间

    将转移到老年代的对象数量降到最少

      按代的GC机制由Oracle JVM提供,不包括可以在JDK7以及更高版本中使用的G1 GC。换句话说,对象被创建在伊甸园空间,而后转化到幸存者空间,最终剩余的对象被送到老年代。某些比较大的对象会在被创建在伊甸园空间后,直接转移到老年代空间。老年代空间上的GC处理会新生代花费更多的时间。因此,减少被移到老年代对象的数据可以显著地减少Full GC的频率。减少被移到老年代空间的对象的数量,可能被误解为将对象留在新生代。但是,这是不可能的。取而代之,你可以调整新生代空间的大小。

    减少Full GC执行时间

      Full GC的执行时间比Minor GC要长很多。因此,如果Full GC花费了太多的时间(超过1秒),一些连接的部分可能会发生超时错误。

    • 如果你试图通过消减老年代空间来减少Full GC的执行时间,可能会导致OutOfMemoryError 或者 Full GC执行的次数会增加。
    • 与之相反,如果你试图通过增加老年代空间来减少Full GC执行次数,执行时间会增加。

      因此,你需要将老年代空间设定为一个“合适”的值。


    影响GC性能的参数


      正如我们在第二篇文章结尾提到的,不要幻想“某个人设定了GC参数后性能得到极大的提高,我们为什么不和他用一样的参数?”,因为不同的Web服务所创建对象的大小和他们的生命周期都不尽相同。

      简单来说,如果一个任务的执行条件是A,B,C,D和E,同样的任务执行条件换为A和B,你会觉得哪个更快?从一般人的直觉来看,在A和B条件下执行的任务会更快。

      Java GC参数也是相同的道理,设定一些参数不但没有提高GC执行速度,反而可能导致他更慢。GC优化的最基本原则是将不同的GC参数用于2台或者多台服务器,并进行对比,并将那些被证明提高了性能或者减少了GC执行时间的参数应用于服务器。请谨记这一点。

      下面这个表格列出了GC参数中与内存大小相关的,可以影响性能的参数。

    表1:GC优化需要考虑的Java参数

    定义

    参数

    描述

    堆内存空间

    -Xms

    Heap area size when starting JVM

    启动JVM时的堆内存空间。

     

    -Xmx

    Maximum heap area size

    堆内存最大限制

    新生代空间

    -XX:NewRatio

    Ratio of New area and Old area

    新生代和老年代的占比

     

    -XX:NewSize

    New area size

    新生代空间

     

    -XX:SurvivorRatio

    Ratio of Eden area and Survivor area

    伊甸园空间和幸存者空间的占比

      我在进行GC优化时经常使用-Xms,-Xmx和-XX:NewRatio。-Xms和-Xmx是必须的。你如何设定NewRatio 会对GC性能产生十分显著的影响。有些人可能会问如何设定Perm区域的大小?你可以通过-XX:PermSize 和-XX:MaxPermSize参数来设定,但只有在OutOfMemoryError 错误发生并且是由于Perm空间不足导致时才设置。另一个可能影响GC性能的参数是GC类型。下表列出了所有可选的GC类型(基于JDK6.0)。

    表2:GC类型可选参数

    分类

    参数

    备注

    Serial GC

    -XX:+UseSerialGC

     

    Parallel GC

    -XX:+UseParallelGC
    -XX:ParallelGCThreads=value

     

    Parallel Compacting GC

    -XX:+UseParallelOldGC

     

    CMS GC

    -XX:+UseConcMarkSweepGC
    -XX:+UseParNewGC
    -XX:+CMSParallelRemarkEnabled
    -XX:CMSInitiatingOccupancyFraction=value
    -XX:+UseCMSInitiatingOccupancyOnly

     

    G1

    -XX:+UnlockExperimentalVMOptions
    -XX:+UseG1GC

    在JDK6中这两个参数必须同时使用

      除了G1 GC,可以通过每种类型第一行的参数来切换GC类型。最常用的GC类型是Serial GC。他专门针对客户端系统进行了优化。

      影响GC性能的参数有很多,但是上面提到的参数会带来最显著的效果。请牢记,设定过多的参数不一定会减少GC执行时间。


    GC优化过程


      GC优化的过程与大多数性能改善的过程及其类似。下面是我使用的GC优化过程。

    1. 监控GC状态

      首先你需要监控GC来检查在系统执行过程中GC的各种状态。请参考前一篇文章中提到的监控方法 成为JavaGC专家Part II — 如何监控Java垃圾回收机制

    2. 在分析监控结果后,决定是否进行GC优化

      在检查GC状态的过程中,你应该分析监控结果以便决定是否进行GC优化,如果分析结果表明执行GC的时间只有0.1-0.3秒,那你就没必要浪费时间去进行GC优化。但是,如果GC的执行时间是1-3秒,或者超过10秒,GC优化将势在必行。

      但是,如果你已经为Java分配了10GB的内存,并且不能再减少内存大小,你将无法再对GC进行优化。在进行GC优化之前,你必须想清楚你为什么要分配如此大的内存空间。假如当你分1 GB 或 2 GB内存时出现OutOfMemoryError ,你应该执行堆内存转储(heap dump),并消除隐患。

      注意:

      堆内存转储是一个用来检查Java内存中的对象和数据的文件。该文件可以通过执行JDK中的jmap命令来创建。在创建文件的过程中,Java程序会暂停,因此不要在系统执行过程中创建该文件。

      你可以在互联网上搜索堆内存[s1] 转储的详细说明。对于韩国的读者,可以参考我去年发布的书: The story of troubleshooting for Java developers and system operators (Sangmin Lee, Hanbit Media, 2011, 416 pages)。

    3. 调整GC类型/内存空间

      如果你已经决定要进行GC优化,那么就要选择GC类型和设定内存空间。在这时,如果你有几台不同服务器,请时刻牢记,检查每一台服务器的GC参数,并进行有针对性的优化。

    4. 分析结果

      在调整了GC参数并持续收集24小时之后,开始对结果进行分析,如果你幸运的话,你就找到那些最适合系统的GC参数。反之,你需要通过分析日志来检查内存是如何被分配的。然后你需要通过不断的调整GC类型和内存空间大小以便找到最佳的参数。

    5. 如果结果令人满意,你可以将该参数应用于所有的服务器,并停止GC优化

      有过GC优化结果令人满意,你可以应用于所有的服务器,下面的章节中,我们将看到每个步骤的具体任务。


    监控GC状态及分析结果


      查看运行中的Web Application Server (WAS)的GC状态的最佳方法是通过jstat命令,在第二篇文章成为JavaGC专家Part II — 如何监控Java垃圾回收机制中我已经详细解释过jstat命令,因此本篇文章我将重点描述数据部分。

      下面这个例子展现了某个JVM在进行GC优化之前的状态(不过这不是一个运营服务器)。

    1. $ jstat -gcutil 21719 1s  
    2. S0    S1    E    O    P    YGC    YGCT    FGC    FGCT GCT  
    3. 48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673  
    4. 48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673  
    $ jstat -gcutil 21719 1s
    S0    S1    E    O    P    YGC    YGCT    FGC    FGCT GCT
    48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673
    48.66 0.00 48.10 49.70 77.45 3428 172.623 3 59.050 231.673

      如上表,我们先看一下YGC 和YGCT,计算YGCT/ YGC得到0.050秒(50毫秒)。这意味着新生代空间上的GC操作平均花费50毫秒。在这种情况,你大可不必担心新生代空间上执行的GC操作。
      接下来,我们来看一下FGCT 和FGC。,计算FGCT/ FGC得到19.68秒,这意味着GC的平均执行时间为19.68秒,可能是每次花费19.68秒执行了三次,也可能是其中的两次执行了1秒而另一次执行了58秒。不论哪种情况,都需要进行GC优化。

      通过jstat 命令可以很轻易地查看GC状态,但是,分析GC的最佳方式是通过–verbosegc参数来生成日志,在之前的文章中我已经解释了如何分析这些日志,HPJMeter 是我个人最喜欢的用于分析-verbosegc 日志的工具。他很易于使用和分析结果。通过HPJmeter你可以很轻易查看GC执行时间以及GC发生频率。如果GC执行时间满足下面所有的条件,就意味着无需进行GC优化了。

    • Minor GC执行的很快(小于50ms)
    • Minor GC执行的并不频繁(大概10秒一次)
    • Full GC执行的很快(小于1s)
    • Full GC执行的并不频繁(10分钟一次)

      上面提到的数字并不是绝对的;他们根据服务状态的不同而有所区别,某些服务可能满足于Full GC每次0.9秒的速度,但另一些可能不是。因此,针对不同的服务设定不同的值以决定是否进行GC优化。

      在查看GC状态的时候有件事你需要特别注意,那就是不要只关注Minor GC 和Full GC的执行时间。还要关注GC执行的次数。例如,当新生代空间较小时,Minor GC会过于频繁的执行(有时每秒超过1次)。另外,转移到老年代的对象数增多,则会导致Full GC执行次数增多。因此,别忘了加上–gccapacity参数来查看具体占用了多少空间。


    设定GC类型/内存空间大小


    • 设定GC类型

    Oracle JVM有5种GC类型,但是在JDK7之前的版本中,只能在Parallel GC, Parallel Compacting GC 和CMS GC之中选择一个,对于选择哪个没有明确的原则和规则。

    这样的话,我们该如何选择呢?强烈建议三者都选,但是,有一点是很明确的:CMS GC比Parallel GCs更快。如果真的如此,那么就选CMS GC了。但是,CMS GC也不总是更快。整体来看,CMS GC模式下的Full GC执行更快,不过,一旦出现并行模式失败,他将比Parallel GC更慢。

    并发模式失败

    我们来详细讲解一下并发模式失败。

    Parallel GC 和 CMS GC 最大的不同来自于压缩任务。压缩任务是通过删除已分配内存空间中的空白空间以便压缩内存,清理内存碎片。

    在Parallel GC模式下,压缩工作在Full GC执行时进行,这会费很多时间,但是,在执行完Full GC之后,由于能够顺序地分配空间,随后的内存能够被更快的分配。

    与之相反的,CMS GC并不进行压缩处理,因此,CMS GC执行的更快。但是,由于没有压缩,在进行磁盘清理之前,内存中会有很多空白空间。这就是说,可能没有足够的空间存储大的对象,例如,虽然老年代空间还有300MB空间,但是一些10MB的对象无法被顺序的存储。在这种情况下,会出现“并行模式失败”警告,并执行压缩处理。在CMS GC模式下,压缩处理的执行时间要比Parallel GCs长很多。另外,这还将导致另外一个问题。关于并发模式失败的详细说明,可以参考Oracle工程师撰写的Understanding CMS GC Logs

    综上所述,你需要找到最适合你的系统的GC类型。

    每个系统都有最适合他的GC类型等着你去寻找,如果你有6台服务器。我建议你每两台设置相同的参数。并添加 –verbosegc参数,分析结果。

    • 设定内存空间大小

    下表展示了内存空间大小,GC执行次数以及GC执行时间三者间的关系。

      • 大内存空间
        • 减小GC执行次数
        • 增加GC执行时间
      • 小内存空间
        • 减小GC执行时间
        • 增加GC执行次数

    关于如何设置内存空间的大小,没有唯一的标准答案。如果服务器资源足够,而且Full GC也可能在1秒内完成,设置为10GB当然可行。但绝大多数服务器并不是这样,当内存设为10GB时,可能要花费10~30秒来执行Full GC。当然,执行时间会随对象的大小而改变。

    鉴于如此,我们应该如何设定内存空间大小呢?一般来说,我建议为500MB。不过请注意这不是让你将WAS的内存参数设置为–Xms500m 和–Xmx500m。根据优化GC之前的状态,如果Full GC执行之后内存空间剩余300MB,那么最好将内存设置为1GB(300MB(默认程序占用)+ 500MB(老年代最小空间)+200MB(空闲内存))。也就是说你要为老年代额外设置500MB。因此,如果你有三个执行服务器,内存分别设置为1GB,1.5GB,2GB,并且检查结果。

    理论上来讲,GC执行速度应该遵循1GB> 1.5GB> 2GB,因此1GB执行GC速度最快。但是并不说明1GB空间的Full GC会花费1秒而2GB空间会花费2秒。时间取决于服务器的性能和对象的大小。因此,最佳的方式是建立尽可能多的衡量指标来监控他们。

    对于内存空间大小,你应该额外设定NewRatio参数。NewRatio参数是新生代和老年代空间的比例,即XX:NewRatio=1意味着新生代与老年代之比为1:1。对于1GB来说就是新生代和老年代各500MB。如果NewRatio为2,意味着新生代老年代之比为1:2,因此该值越大,老年代空间越大,新生代空间越小。

    这看似一件不是很重要的事情,但NewRatio参数会显著地影响整个GC的性能。如果新生代空间很小,会用更多的对象被转移到老年代空间,这样导致频繁的Full GC,增加暂停时间。

    你可以简单的认为NewRatio为1是最佳的选择,但是,有时可能设置为2或3更好,我就见过很多这样的例子。

    如何最快的完成GC优化?对比性能测试的结果应该是最快地方法,为每一台服务器设置不同的参数并监控他们的状态,强烈建议至少监控1或2天的数据。但是,当你对GC优化是,你要确保每次执行相同的负载。并且请求的比率,例如URL都应该是一致的。不过,即便对于专业测试人员要想精确的控制负载也是很难的,并要花费大量的时间准备。因此,相对来说比较方便和容易的方法是调整参数,之后花费较长的时间收集结果。


    分析GC优化结果


      在设置了GC参数以及-verbosegc参数之后,通过tail命令确保日志被正确的生成。如果参数设置的不正确或者日志没有生成,你将白白浪费你的时间。如果日志正确的话,持续收集1到2天。随后最好将日志下载到本地PC并用HPJMeter来分析。

    • Full GC 执行时间
    • Minor GC执行时间
    • Full GC 执行间隔
    • Minor GC 执行间隔
    • Entire Full GC 执行时间
    • Entire Minor GC 执行时间
    • Entire GC 执行时间
    • Full GC 执行时间
    • Minor GC 执行时间

      找到最佳的GC参数是件非常幸运的事情,然而在大多数场合,我们并不会得到幸运之神的眷顾,在进行GC优化时要尽量小心谨慎,想一步完成优化往往会导致OutOfMemoryError 。


    优化示例


      好了,我们一直在纸上谈兵,现在我们看一些实际的GC优化的例子。


    示例1


    下面这个例子针对Service S的优化,对于最近被部署的 Service S,Full GC花费了太长的时间。

    请看 jstat –gcutil的执行结果。

    1. S0 S1 E O P YGC YGCT FGC FGCT GCT  
    2. 12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993  
    S0 S1 E O P YGC YGCT FGC FGCT GCT
    12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993
    最左边的 Perm 空间对于最初的GC优化不是很重要,这一次YGC参数的值更加有用。

    Minor GC和Full GC的平均值如下表所示

    表3:Service S的Minor GC 和Full GC的平均执行时间

    GC 类型

    GC 执行次数

    GC 执行时间

    平均

    Minor GC

    54

    2.047

    37 ms

    Full GC

    5

    6.946

    1,389 s

    37ms对Minor GC来说并不差。但是1.389s的Full GC意味着在DB超时时间设为1秒的系统中,当GC发生时可能会出现频繁的超时。因此,该系统需要进行GC优化。

    首先,你需要检查怎么使用内存的。使用jstat -gccapacity选项来检测内存使用情况。结果如下:

    1. NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC  
    2. 212992.0 212992.0 212992.0 21248.0 21248.0 170496.0 1884160.0 1884160.0 1884160.0 1884160.0 262144.0 262144.0 262144.0 262144.0 54 5  
    NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
    212992.0 212992.0 212992.0 21248.0 21248.0 170496.0 1884160.0 1884160.0 1884160.0 1884160.0 262144.0 262144.0 262144.0 262144.0 54 5
      最重要的是下面两个数据:

    • 新生代实际使用空间:212,992 KB
    • 老年代实际使用空间:1,884,160 KB

    因此,总的内存空间为2GB,不算Perm空间的话,新生代与老年代之比为1:9。通过jstat和-verbosegc 日志进行数据收集,并把三台服务器按照如下方式设置。

    • NewRatio=2
    • NewRatio=3
    • NewRatio=4

    一天之后,检查系统的GC日志后发现,在设置了NewRatio参数后很幸运的没有发生Full GC。

    为什么?这是因为系统中创建的大部分对象立刻就被销毁了,它们没有转移到老年代空间,而是在新生代空间中立刻被回收。这种情况下没必要修改其他的选项值,只要选择最合适的NewRatio值。那怎样确定最合适的NewRatio值呢?只要对每个NewRatio分析Minor GC的平均响应时间即可。

    下面是每个选项的Minor GC平均响应时间:

    • NewRatio=2: 45 ms
    • NewRatio=3: 34 ms
    • NewRatio=4: 30 ms

    我们看到NewRatio=4 是最佳的参数,虽然它的新生代空间最小,但GC时间确最短。设定这个参数之后,系统没有执行过Full GC。

    为了说明这个问题,下面是应用服务的JVM运行一段时间后执行jstat –gcutil的结果:

    1. S0 S1 E O P YGC YGCT FGC FGCT GCT  
    2. 8.61 0.00 30.67 24.62 22.38 2424 30.219 0 0.000 30.219  
    S0 S1 E O P YGC YGCT FGC FGCT GCT
    8.61 0.00 30.67 24.62 22.38 2424 30.219 0 0.000 30.219
    你可能会认为因为服务器接受的请求少才导致的GC执行频率下降。实际上,虽然Full GC没有执行,但是Minor GC被执行了 2,424次。


    示例2


    这是一个针对Service A的例子,我们通过公司内部的应用性能管理系统(APM)发现JVM暂停了相当长的时间(超过8秒),因此我们进行了GC优化。我们找到了Full GC执行时间过长的原因,并着手解决。

    进行GC优化的第一步,就是我们添加了-verbosegc参数,并得到如下结果。


    图1:进行GC优化之前的STW时间

    如上图所示,由HPJMeter自动生成的图片之一。X坐标表示JVM执行的时间。Y坐标表示每次GC的时间。CMS绿点,表示Full GC结果。Parallel Scavenge蓝点,表示Minor GC结果。

    之前我曾经说过CMS GC是最快的,但是上面的的结果显示出于某种原因,它最多花费了15秒。是什么导致这个结果?是否想起我之前提过的,CMS在进行内存清理时,会变慢。与此同时,服务的内存被设定为 –Xms1g和–Xmx4g ,且实际分配了4GB内存。

    因此,我将GC类型从CMS改为Parallel GC。并且将内存改为2GB,设定NewRatio 为3。几小时之后我使用 jstat –gcutil得到如下结果:

    1. S0 S1 E O P YGC YGCT FGC FGCT GCT  
    2. 0.00 30.48 3.31 26.54 37.01 226 11.131 4 11.758 22.890  
    S0 S1 E O P YGC YGCT FGC FGCT GCT
    0.00 30.48 3.31 26.54 37.01 226 11.131 4 11.758 22.890
      相对于4GB时的15秒,Full GC变成了平均每次3秒。但是3秒一样比较慢,因此我设计了如下6种场景:

    • Case 1: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=2
    • Case 2: -XX:+UseParallelGC -Xms1536m -Xmx1536m -XX:NewRatio=3
    • Case 3: -XX:+UseParallelGC -Xms1g -Xmx1g -XX:NewRatio=3
    • Case 4: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=2
    • Case 5: -XX:+UseParallelOldGC -Xms1536m -Xmx1536m -XX:NewRatio=3
    • Case 6: -XX:+UseParallelOldGC -Xms1g -Xmx1g -XX:NewRatio=3

    哪一个最快呢?结果显示,内存越小,结果越好。下图展示了Case 6的结果。这是GC的性能最好。最长的响应时间只有1.7秒。平均时间在1秒之内。


    图2:Case 6的时间图表

    基于以上结果。我们按照Case 6调整了GC参数。但是,这导致了每天晚上都会发生OutOfMemoryError。在这里很难解释具体的原因。简单来说,批处理程序导致了内存泄漏。相关的问题已经被解决。

    如果对GC日志只分析很短的时间就贸然对所有服务器进行优化是非常危险的。请时刻牢记,你必须同时分析GC日志和应用程序。

    我们回顾了两个关于GC优化的例子,正如我之前提到的,例子中提到的GC参数,可以设置在相同的服务器之上,但前提是他们具有相同的CPU,操作系统,JDK版本以及运行着相同的服务。但是不要直接把我用过的参数用到你的服务至上,它们未必能很好的工作。


    结论


      我凭借经验进行GC优化,而没有执行堆转储并分析内存的详细内容。精确地分析内存可以得到更好的优化效果。但是,这种分析一般适用于内存使用量相对固定的场合。不过,如果服务严重过载并占用的大量的内存,强力建议根据之前的经验进行GC优化。

      我已经在一些服务上设置了G1 GC参数,并进行过性能测试。但还没有应用与正式环境,G1 GC参数的速度要快于其他任何GC类型。但是,你必须要升级到JDK7。另外,他的稳定性也暂时没有保障,没人知道是否会出现致命的错误。因此还不到将其正式应用的时候。

      在未来的某一天,等到JDK7真正稳定了(这不是说他现在不稳定),并且WAS针对JDK7进行优化后,G1 GC最终能够按照预期的那样工作了,我们可能就不需要在进行GC优化了。

      想了解GC优化的更多内容,请登录Slideshare.com 查看关联资源。强烈推荐Everything I Ever Learned About JVM Performance Tuning @Twitter。作者Attila Szegedi,一位Twitter工程师。请花些时间阅读。

      作者:Sangmin Lee,就职于NHN性能工程研究院


    英文原文: http://www.cubrid.org/blog/dev-platform/how-to-tune-java-garbage-collection/

    中文参考:http://www.importnew.com/3146.html

    展开全文
  • 原文: Minor GC vs Major GC vs Full GC 在Plumbr进行GC暂停检测功能的工作时, 我不得不阅读大量与此相关的文章,书籍和报告。在研究过程中, 对于Minor, Major和Full GC时间我一再的困惑,这也就导致本博文的...
  • Minor GC Young GC Full GC Major GC https://blog.csdn.net/chenleixing/article/details/46706039 内存划分为 Eden、Survivor 和 Tenured/Old 空间,如下图所示: 从年轻代空间(包括 Eden 和 ...
  • 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC; 对老年代GC称为Major GC; 而Full GC是对整个堆来说的; 在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),...
  • 关于GC

    千次阅读 2016-08-25 18:08:18
    关于GC,始终都有些茫然,看了这篇文章,觉得不错,mark在下面。 原文:http://www.cnblogs.com/jeffwongishandsome/p/talk-about-GC-and-how-to-use-GC-better.html 一、为什么需要GC 应用程序对...
  • 7. GC 调优(实战篇) - GC参考手册

    万次阅读 多人点赞 2017-03-13 19:12:17
    JVM上运行的程序多种多样, 启动参数也有上百个, 其中有很多会影响到 GC, 所以调优GC性能的方法也有很多种。 还是那句话, 没有真正的银弹, 能满足所有的性能调优指标。 我们能做的只是介绍一些常见的/和不常见的示例...
  • JVM GC

    2019-05-02 16:47:00
    要知道JVM的GC是如何进行的,首先来了解一下JVM内存结构 一 JVM内存结构 1.1 运行时数据区 首先我们看看Java虚拟机规范是怎么说的: Java虚拟机规范规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区,如下...
  • GC原理详解

    2020-10-30 10:56:25
    GC就是Java的垃圾回收机制,要了解什么情况下会发生GC(即GC得触发条件),我们需要先了解JVM的内存模型结构,之前一篇文章已经详细讲解了Jvm的内存模型结构,而通常来说,GC主要针对的是堆(java heap)区。 而java ...
  • 再谈GC1:GC简介,分代与回收算法

    千次阅读 2018-07-19 15:13:07
    说明: 在本文中,Garbage Collection翻译为 “垃圾收集”,...Minor GC翻译为:小型GC; 而不是次要GC Major GC翻译为:大型GC; 而不是主要GC 原因在于,大部分情况下, 发生在年轻代的Minor GC次数会很多,翻译为次...
  • 内存管理简介 内存管理的职责为分配内存,回收内存。 没有自动内存管理的语言/平台容易发生错误。 典型的问题包括悬挂指针问题,一个指针... GC简介 因此引入了Garbage Collector机制,由运行时环境来自动管...
  • 目录: GC之一--GC 的算法分析、垃圾收集器、...gc之四--Minor GC、Major GC和Full GC之间的区别 GC之六--SystemGC完全解读 《垃圾收集器之:G1收集器》 《垃圾收集器之:CMS收集器》 1、System.gc()方法的调用...
  • JVM GC触发条件

    2019-04-05 16:38:42
    Minor GC ,Full GC 触发条件 * 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC; * 对老年代GC称为Major GC; * 而Full GC是对整个堆来说的; 在最近几个版本的JDK里默认包括了对永生带即方法...
  • 08 GC日志

    2019-11-18 15:10:52
    GC日志是一个很重要的工具,它准确记录了每一次的GC的执行时间和执行结果,通过分析GC日志可以优化堆设置和GC设置,或者改进应用程序的对象分配模式。-XX:+PrintGC参数-XX:+PrintGC(或者-verbose:gc)开启了简单GC...
  • Android GC日志

    2019-12-31 16:35:53
    GC日志分为两种情况,Dalvik虚拟机下的GC日志和ART虚拟机下的GC日志。 1、Dalvik Dalvik虚拟机下的GC格式日志如下: D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_...
  • MinnorGC和FullGC触发时机

    千次阅读 2018-09-19 23:41:32
    原 触发JVM进行Full GC的情况及应对策略 置顶 2015年07月01日 08:54:00 Java我人生 阅读数:58394 &lt;span class="tags-box ...
  • 说明: Allocation Rate, 翻译为分配速率, 而不是分配率; 因为不是百分比,而是单位时间内分配的量; 同理, Promotion Rate ... GC 算法(基础篇) - GC参考手册 GC 算法(实现篇) - GC参考手册 GC 调优(基础篇) -...
  • 简介:FullGC与MinorGC讲解 Minor GC触发条件 当Eden区满时,触发Minor GC FullGC触发条件 ...因此强烈建议能不使⽤此⽅法就不要使⽤,让虚拟机⾃⼰去管理它的内存。可通过 -XX:+ DisableExplicitGC 来禁⽌ ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 10,901
精华内容 4,360
关键字:

强烈gc