JVM垃圾回收算法

确定垃圾对象的方法

在Java堆上分配一个内存给实例对象时,此时在虚拟机栈上引用型变量就会存放这个实例对象的起始地址。

Object obj = new Object(); 

如果将变量赋值为null,堆中对象失去了引用。

obj = null;
  • 引用计数法

对象中添加一个引用计数器,如果引用计数器为0则表示没有其它地方在引用它。如果有一个地方引用就+1,引用失效时就-1。引用和对象是有关联的。如果要操作对象则必须用引用进行,通过引用计数来判断一个对象是否可以回收。简单说,一个对象如果没有任何与之关联的引用,则说明对象不太可能再被用到,那么这个对象就是可回收对象。这种方式即是引用计数法。这种方式的问题是无法解决循环引用的问题。

object1和object2的内存块都不能再被访问到,
但他们的引用计数都不为0,使他们永远不会被清除。
public static void main(String[] args){
    Object object1=new Object();
    Object object2=new Object();
    object1.object=object2;
    object2.object=object1;
    object1=null;
    object2=null;
}
  • 可达性分析

为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。通过一系列的GC roots对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

可达性分析

活跃引用包括:

  • 所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;既当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
  • VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。
  • JNI handles,包括global handles和local handles。
  • 所有当前被加载的Java类。
  • Java类的引用类型静态变量。
  • Java类的运行时常量池里的引用类型常量(String或Class类型)。
  • String常量池(StringTable)里的引用。

类及对象无效判定条件

类视为无用的三个条件:
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
加载该类的 ClassLoader实例已经被回收。
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

对象视为可回收对象:

  • 显式地将对象的唯一强引用指向新的对象。
  • 显式地将对象的唯一强引用赋值为Null。
  • 局部引用所指向的对象(如,方法内对象)。

下述代码中,每次循环结束,只有弱引用与其关联的对象object都会被视为可回收对象。

void fun() {
    for (int i=0;i<10;i++) {
        Object obj = new Object();
        // 只有弱引用与其关联的对象
        System.out.println(obj.getClass());
    }   
}

典型的垃圾回收算法

在JVM规范中并没有明确GC的运作方式,各个厂商可以采用不同的方式去实现垃圾回收器。这里讨论几种常见的GC算法。


垃圾回收算法
  • 标记-清除算法(Mark-Sweep)

最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。

image

从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。

  • 复制算法(Copying)

为了解决Mark-Sweep算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉,如图:

image

这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,Copying算法的效率会大大降低。

  • 标记-整理算法(Mark-Compact)

结合了以上两个算法,为了避免缺陷而提出。标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:

image
  • 分代收集算法(Generational Collection)

JVM区域总体分两类heap区非heap区。这样划分为了使 JVM 能够更好管理堆内存中的对象,包括内存的分配以及回收。

  • heap区又分: Eden Space(伊甸园)、Survivor Space(幸存者区)、Tenured Gen(老年代-养老区)。
  • 非heap区又分: Perm Gen(永久代)、Code Cache(代码缓存区)、Jvm Stack(java虚拟机栈)、Local Method Statck(本地方法栈)、方法参数、局域变量等的引用,方法执行顺序按照栈的先入后出方式。
JVM内存模型

Eden Space (heap)
内存最初从这个线程池分配给大部分对象。

Survivor Space (heap)
用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。

Tenured Generation (heap)
用于保持已经在survivor space内存池中存在了一段时间的对象。

Permanent Generation (non-heap)
保存虚拟机自己的静态(reflective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的。

Code Cache (non-heap)
HotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)。


目前大部分JVM所采用分代收集法,对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。当新生代的Eden Space和From Space空间不足时就会发生一次GC,进行GC后,Eden Space和From Space区的存活对象会被挪到To Space,然后将Eden Space和From Space进行清理。如果To Space无法足够存储某个对象,则将这个对象存储到老生代。在进行GC后,使用的便是Eden Space和To Space了,如此反复循环。当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄到达15的对象会被移到老生代中。

对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。老生代因为每次只回收少量对象,因而采用Mark-Compact算法

处于方法区的永生代(Permanet Generation)。它用来存储class类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。

典型垃圾收集器

垃圾收集算法是垃圾收集器的理论基础,而垃圾收集器就是其具体实现。下面介绍HotSpot虚拟机提供的几种垃圾收集器。

并行和并发概念

并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个CPU上。

  • Serial

最古老的新生代单线程收集器,采用Copying算法。垃圾回收时必须暂停所有用户线程(Stop The World),运行在Client模式下的虚拟机可以选择。优点是简单高效,缺点是需要暂停用户线程

Serial
  • Serial Old

Serial Old是老生代串行收集器,采用Mark-Compact算法。作为CMS收集器的后备方案,在CMS发生Concurrent Mode Failure后使用。

Serial Old
  • ParNew

Seral的并行版本,使用多个线程进行垃圾收集。ParNew为新生代收集器,可以和老年代的CMS搭配使用。在单CPU的硬件环境下,性能并不会比Serial高,在多CPU的情况下,默认开启的收集线程数与CPU数量相同。在CPU数量非常多的情况下,可以使用-XX:ParalleGCThreads来修改线程数。

ParNew
  • Parallel Scavenge

新生代并行收集器,回收时停顿时间可以控制,采用复制算法。该收集器与前两个收集器不同在于垃圾自适应调节策略,主要为了达到一个可控的吞吐量

吞吐量=代码运行时间/(代码运行时间+GC时间)
GC时间通过参数控制:
-XX:MaxGCPauseMillis 单位毫秒,收集器会尽可能保证收集时间不超过设定值,设置太小并不一定能够提高吞吐量,1分钟1次每次10MS变成30S一次每次7MS。
-XX:GCTimeRatio 值0到100,默认值99。垃圾回收时间占用总时间的比例,1 / (1 + 99) 即 1% 的时间。
-XX:+UseAdaptiveSizePolicy打开该参数后不需要手动设定新生代大小,Eden与Survivor比例,晋升老年代的年龄。虚拟机会根据运行情况收集监控信息,自适应的改变这些参数,配合前面俩个参数可以自动进行调节。

Parallel Scavenge
  • Parallel Old

Parallel Scavenge的老生代版本,采用PS MarkSweep并行收集器来进行老年代收集,与Serial Old的实现非常接近。适合吞吐量优先和CPU资源敏感的场景。

Parallel Old
  • CMS

Current Mark Sweep基于标记-清除老年代收集器,是第一款并发收集器。CMS关注用户线程的停顿时间即提高用户体验。CMS的收集过程和用户线程一起并发执行,对CPU资源非常敏感,默认收集线程数为(CPU数量+3)/4,当CPU数量在4个以上时,垃圾回收线程占用不少于25%的CPU资源,随着CPU数量的增加而下降,CPU数量少于2个的时候,不建议使用。

清理步骤

1. 初始标记(CMS initial mark): 暂停所有的其他线程,并记录下直接与GC root相连的对象,速度很快。
2. 并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
3. 重新标记(CMS remark):重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。
4. 并发清除(CMS concurrent sweep):开启用户线程,同时GC线程开始对为标记的区域做清扫。

CMS
  • G1

G1适用于服务器端、大内存、多CPU情景的垃圾收集器,主要目标是在维持高效率回收(high thoughput)的同时,提供软实时中断特性。用户可以指定一个时间上限,如果垃圾回收导致的程序暂停超过了用户设定的时间上限,会打断垃圾回收,恢复程序执行。
G1的主要目标是维持高回收率的同时,减少停顿。因为这个特定,并不是最快的,Parallel 目前是最快的。

G1
推荐使用G1案例

G1为大容量内存和较小GC延迟的应用程序提供解决方案。堆大小设置在6GB以上,确定的、可以预测的暂停时间在0.5秒以内的应用程序。如果应用程序符合以下一项或者多项特征,那么从CMS或者ParallelOld收集器切换到G1可能更合适。

  • 活动对象占据了超过50%的Java堆空间。
  • 对象分配率或者提升率波动明显。
  • 不希望有长时间的垃圾收集暂停时间(超过0.5秒或1秒)。
G1特点如下
  • 并行与并发: G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
  • 分代收集:G1不需要其他收集器配合就能独立管理整个GC堆,还是保留分代概念。
  • 空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于标记整理算法实现的收集器;局部看基于复制算法实现。
  • 可预测停顿:G1相对于CMS的另一个大优势,除了追求低停顿外,还能建立可预测的停顿时间模型,可指定在一个长度为M毫秒的时间片段内。
  • 空间整合:G1收集器有利于程序长时间运行,分配大对象时不会无法得到连续的空间而提前触发一次GC。

G1区域概念

在G1中,heap被平均分成若干个大小相等的区域(region)。每个region都有一个关联的Remembered set (RS),RS的数据结构是hash table,里面的数据是card table (heap中每512byte映射在card table 1byte)。简单的说RS里面存在的是region中live objects的指针。当region中数据发生变化时,首先反映到card table中的一个或多个card上,RS通过扫描内部的card table得知region中内存使用情况和存活对象。

关于内存分配:

由于G1主要关注于多CPU多线程,所以内存分配采用 thread-local allocation buffers (TLABs)技术。每个分配线程都有一个自己的buffers用来分配对象,当buffers用完或者不够的时候,去重新申请一块内存放在自己的thread-local里面。这样对象的内存分配被最小化到私有的buffers里面,缓解了并发分配内存的压力。
当region被填满后,分配内存的线程会重新选择一个新的region。空region被组织到一个linked list里面,这样可以快速找到新的region。
对于大对象的分配不是在TLABs进行的,而是在TLABs之外。当一个对象的大小超过region的3/4的时候,这个对象被认为是巨大的(humongous )。巨大的对象被分配到特殊的区域(heap regions )。这些区域只包含巨大对象(humongous object )。

执行过程:

G1的前几个步骤的运作过程和CMS有很多相似之处。

  • 初始标记阶段(Initial Marking)
    这个阶段是STW(Stop the World )的,所有mutator threads将被停止,标记GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短。
  • 并发标记阶段(Concurrent Marking)
    从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。而最终标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面。
  • 最终标记阶段(Final Marking)
    需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。
  • 筛选回收阶段(Live Data Counting and Cleanup)
    首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。

G1与CMS对比有一下不同:

1.分代: CMS中,堆被分为PermGen,YoungGen,OldGen;而YoungGen又分了两个survivo区域。在G1中,堆被平均分成几个区域(region),在每个区域中,虽然也保留了新老代的概念,但是收集器是以整个区域为单位收集的。

2.算法: 相对于CMS的“标记-清理”算法,G1会使用压缩算法,保证不产生多余的碎片。收集阶段,G1会将某个区域存活的对象拷贝的其他区域,然后将整个区域整个回收。

3.停顿时间可控: 为了缩短停顿时间,G1建立可预存停顿模型,这样在用户设置的停顿时间范围内,G1会选择适当的区域进行收集,确保停顿时间不超过用户指定时间。

何时触发GC

  • Minor GC触发条件:
    当Eden区满时,触发Minor GC。

  • Full GC触发条件:
    (1)调用System.gc,建议系统执行Full GC,但不一定执行。
    (2)老年代空间不足。
    (3)方法去空间不足。
    (4)通过Minor GC后进入老年代的 对象大小 > 老年代可用内存。
    (5)Eden、From Space区向To Space区复制时,对象大小 > To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。

JVM调思路

如果CPU使用率较高,GC频繁且GC时间长,需要JVM调优。基本思路就是让每一次GC都回收尽可能多的对象。

  • 对于CMS来说,要合理设置年轻代和年老代的大小。可以先采用JVM的默认值,然后通过压测分析GC日志。
    如果看年轻代的内存使用率处在高位,导致频繁的Minor GC,而频繁GC的效率又不高,说明对象没那么快能被回收,这时年轻代可以适当调大一点。
    如果看年老代的内存使用率处在高位,导致频繁的Full GC,这样分两种情况:如果每次Full GC后年老代的内存占用率没有下来,可以怀疑是内存泄漏;如果Full GC后年老代的内存占用率下来了,说明不是内存泄漏,要考虑调大年老代。
  • 对于G1收集器来说,可以适当调大Java堆,因为G1收集器采用了局部区域收集策略,单次垃圾收集的时间可控,可以管理较大的Java堆。
  1. 优先调整堆的大小让服务器自己来选择。
  2. 如果内存小于100m,使用串行收集器。
  3. 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择 。
  4. 如果允许停顿时间超过1秒,选择并行或者JVM自己选。
  5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器。
    官方推荐ZGC性能高。
垃圾回收器组合方式

JVM参数的含义

查看JVM配置:java -XX:+PrintCommandLineFlags -version
查看GC情况:java -XX:+PrintGCDetails -version

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;
 
public class x {
    public static void main(String args[]) {
        List l = 
              ManagementFactory.getGarbageCollectorMXBeans();
        for(GarbageCollectorMXBean b : l) {
            System.out.println(b.getName());
        }
    }

[GC[PSYoungGen 表示用的是年轻代使用Parallel Scavenge收集器。
[GC[ParNew 表示使用的是年轻代使用ParNew收集器(Parallel New收集器)。
[GC[DefNew 表示用的是年轻代使用Serial收集器(Serial New收集器)。
[GC[PSOldGen 表示用的是老年代使用的Parallel Old收集器。

  1. 堆大小设置 JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
    典型设置:
    • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
      -****Xmx3550m:设置JVM最大可用内存为3550M。
      -Xms3550m:设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
      -Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
      -Xss128k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
    • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
      -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
      -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
      -XX:MaxPermSize=16m:设置持久代大小为16m。
      -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
  2. 回收器选择 JVM给了三种选择:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行判断。
    1. 吞吐量优先的并行收集器
      如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
      典型配置
      • java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
        -XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。 -XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC -XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0支持对年老代并行收集。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:MaxGCPauseMillis=100****:设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
        -XX:+UseAdaptiveSizePolicy
        :设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
    2. 响应时间优先的并发收集器
      如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
      典型配置
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
        -XX:+UseParNewGC:设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。
      • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
        -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。 -XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
  3. 辅助信息 JVM提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:
    • -XX:+PrintGC 输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]

      ** [Full GC 121376K->10414K(130112K), 0.0650971 secs]**

    • -XX:+PrintGCDetails 输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]

      ** [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]**

    • -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可与上面两个混合使用
      输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]

    • -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
      输出形式:Application time: 0.5291524 seconds

    • -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
      输出形式:Total time for which application threads were stopped: 0.0468229 seconds

    • -XX:PrintHeapAtGC:打印GC前后的详细堆栈信息
      输出形式:
      34.702: [GC {Heap before gc invocations=7:
      def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
      eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
      from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)
      to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
      tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
      the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
      compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
      the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
      ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
      rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
      34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8: def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
      eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
      from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
      to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)
      tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
      **the space 69632K, 4% used **[0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
      compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
      the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
      ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
      rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
      }
      , 0.0757599 secs]

    • -Xloggc:filename:与上面几个配合使用,把相关日志信息记录到文件以便分析。

  4. 常见配置汇总
    1. 堆设置
      • -Xms:初始堆大小
      • -Xmx:最大堆大小
      • -XX:NewSize=n:设置年轻代大小
      • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
      • -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
      • -XX:MaxPermSize=n:设置持久代大小
    2. 收集器设置
      • -XX:+UseSerialGC:设置串行收集器
      • -XX:+UseParallelGC:设置并行收集器
      • -XX:+UseParalledlOldGC:设置并行年老代收集器
      • -XX:+UseConcMarkSweepGC:设置并发收集器
    3. 垃圾回收统计信息
      • -XX:+PrintGC
      • -XX:+PrintGCDetails
      • -XX:+PrintGCTimeStamps
      • -Xloggc:filename
    4. 并行收集器设置
      • -XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
      • -XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
      • -XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
    5. 并发收集器设置
      • -XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
      • -XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

四、调优总结

  1. 年轻代大小选择
    • 响应时间优先的应用尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
    • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
  2. 年老代大小选择
    • 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
      • 并发垃圾收集信息
      • 持久代并发收集次数
      • 传统GC信息
      • 花在年轻代和年老代回收上的时间比例减少年轻代和年老代花费的时间,一般会提高应用的效率
    • 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
  3. 较小堆引起的碎片问题 因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:
    • -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
    • -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩。

你可能感兴趣的:(JVM垃圾回收算法)