`
heiren3821
  • 浏览: 6532 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

[转载]JVM的GC简介和实例

 
阅读更多

 

原文:http://www.searchtb.com/2013/07/jvm-gc-introduction-examples.html?spm=0.0.0.0.waKEDN

本文是一次内部分享中总结了jvm gc的分类和一些实例, 内容是introduction级别的,供初学人士参考.
成文仓促,难免有些错误,如果有大牛发现,请留言,我一定及时更正,谢谢!
JVM内存布局主要包含下面几个部分:

  1. Java Virtual Machine Stack: 也就是我们常见的局部变量栈,线程私有,保存线程执行的局部变量表、操作栈、动态连接等。
  2. Java Heap:我们最常打交道的内存区域,几乎所有对象的实例都在这个区域分配。所谓的GC基本上也就是跟这个区域打交道。
  3. Method Area:包含被虚拟机加载的类、常量、静态变量等数据。

Hotspot虚拟机使用分代收集算法,将Java Heap根据对象的存活周期分为多个区域:新生代、老生代和永生代。

新生代和老生代位于Java heap中,是垃圾收集器主要处理的内存区域。

永生代则基本上等价于Method Area,也就是说其中包含的数据在jvm进程存活期间会一直存在,一般不会发生变化。

java堆内存的布局如下图所示:

jvm堆布局

使用jstat可以查看某个java进程的内存状况:

1
2
3
chendeMacBook-Air:~ eleforest$ jstat -gc 16136
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
1024.0 1024.0  0.0    0.0    8192.0   2867.9   10240.0      0.0     21248.0 2637.2      0    0.000   0      0.000    0.000

其中各个指标介绍如下:(单位为KB)

  • S0C,S1C,S0U,S1U: 0/1幸存区(survivor)容量(C:Capacity)/使用量(U:Used)。
  • EC,EU: Eden(伊甸)区容量/用量。Eden和survivor两个区域位于新生代,由于新生代GC一般是使用复制算法进行清理,因此按照复制算法的原理将新生代分成了3个区域:Eden、Survivor0、Survivor1。Hotspot虚拟机的3个空间缺省配比为:8:1:1,jvm只会使用eden和1个survivor作为新生代空间.当新生代空间不足时发生minor gc,此时根据复制算法, jvm会首先 1)将eden和from survivor中存活的对象拷贝到to survior中,然后2)释放eden和from中的所有需要回收对象,最后3)调换from/to survior,jvm将eden和新的from survior作为新生代。当然上述minor gc顺利执行还取决于很多因素,这里只描述了最理想化的状态。
  • OC,OU: Old(老生代)容量/用量。老生代常用的垃圾收集器有CMS、Serial Old、Parallel Old等
  • PC,PU: Perm(永生代)容量/用量。
  • YGC/YGCT: Young GC次数和总耗费时间。Young GC也就是Minor GC,新生代中内存不够时触发,通常采用复制算法进行,回收速度较快,对系统的影响较小。
  • FGC/FGCT:Full GC次数和总耗费时间。Full GC是在java heap空间不足(包括New和Old区域)时触发,会分别清理新生代、老生代,通常耗时较长,对系统有较大影响,应该尽量避免。
  • GCT:GC总耗时。

常用的垃圾收集器包括下面几个

  • Serial:最基本,历史最悠久的收集器,单线程收集垃圾内存,在新生代采用复制算法,在老生代使用标记-整理算法
  • ParNew:Serial的多线程版本,主要用于新生代收集。与CMS收集器配合成为现在最常用的server收集器
  • Parallel Scavenge:也是一个并行收集器,使用与ParNew完全不同的收集策略,具体的差别还在研究中
  • CMS:Concurrent Mark Sweep收集器,大名鼎鼎,其目标是获取最短回收停顿时间,是server模式下最常用的收集器
  • G1:最新的收集器,木有用过啊

下面将会用一段简单的程序演示jvm在配置使用不同的收集器情况下,GC行为的不同点,通过GC的行为能够了解到不同收集器的收集策略和行为。代码非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//jvm basic args:-Xmx20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
public class Main {
    public static void main(String[] args) throws Exception {
    byte[] alloc1,alloc2,alloc3,alloc4;
    alloc1 = new byte[2*1024*1024];
    Thread.sleep(2000);
    alloc2 = new byte[2*1024*1024];
    Thread.sleep(2000);
    alloc3 = new byte[2*1024*1024];
    Thread.sleep(2000);
    alloc4 = new byte[2*1024*1024];
    Thread.sleep(2000);
    }
}

其中上例中的jvm参数解释如下:

Xmx 最大堆容量,包含了新生代和老生代的堆容量
Xms 最小堆容量,此时配置与Xmx一样,避免了申请空间时的堆扩展
Xmn 新生代容量,包含eden,survivor1,survivor2三个区域
PrintGCDetails 让jvm在每次发生gc的时候打印日志,利于分析gc的原因和状况
SurvivorRatio 新生代中eden的比例,如果设置为8,意味着新生代中eden占据80%的空间,两个survivor分别占据10%

测试环境为mac os 10.8,jdk版本如下:

1
2
3
4
chendeMacBook-Air:~ eleforest$ java -version
java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)
  • 示例1:让jvm自动选择收集器

直接运行上述代码,用jstat观察gc情况如下:

1
2
3
4
5
6
7
8
9
10
11
chendeMacBook-Air:~ eleforest$ jstat -gc 21729 1000
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
1024.0 1024.0  0.0    0.0    8192.0   819.9    10240.0      0.0     21248.0 2637.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   2867.9   10240.0      0.0     21248.0 2637.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   2867.9   10240.0      0.0     21248.0 2637.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   4915.9   10240.0      0.0     21248.0 2637.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   4915.9   10240.0      0.0     21248.0 2637.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   6963.9   10240.0      0.0     21248.0 2637.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   6963.9   10240.0      0.0     21248.0 2637.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0   292.9   8192.0   2375.9   10240.0     6144.0   21248.0 2640.3      1    0.007   0      0.000    0.007
1024.0 1024.0  0.0   292.9   8192.0   2375.9   10240.0     6144.0   21248.0 2640.3      1    0.007   0      0.000    0.007

由上述的结果可见,程序启动时,Eden使用了819.9K的空间(我现在还不知道819k是什么东西的开销),S1、S2、老生代均没有占用,永生代则使用了2.6MB空间,其中包含了包含被虚拟机加载的类、常量、静态变量等数据。
随后连续三次申请了2MB的空间,这些数据都被放到了Eden区域,这就是jvm内存分配的第一个原则:对象优先在Eden分配,这个原则只在Eden空间足够,且申请的内存小于jvm参数PretenureSizeThreshold设置值时生效(根据采用的收集器不同,还会有很多不同情况)
注意看第四次申请2MB空间,此时由于Eden空间无法容纳新的数组,因此发生了一次Minor GC,具体的GC log如下所示:

1
2
3
4
5
6
7
8
9
10
11
[GC [DefNew: 6963K->292K(9216K), 0.0065350 secs] 6963K->6436K(19456K), 0.0065940 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
 def new generation   total 9216K, used 2832K [0x0000000112230000, 0x0000000112c30000, 0x0000000112c30000)
  eden space 8192K,  31% used [0x0000000112230000, 0x00000001124aaf60, 0x0000000112a30000)
  from space 1024K,  28% used [0x0000000112b30000, 0x0000000112b793b0, 0x0000000112c30000)
  to   space 1024K,   0% used [0x0000000112a30000, 0x0000000112a30000, 0x0000000112b30000)
 tenured generation   total 10240K, used 6144K [0x0000000112c30000, 0x0000000113630000, 0x0000000113630000)
   the space 10240K,  60% used [0x0000000112c30000, 0x0000000113230030, 0x0000000113230200, 0x0000000113630000)
 compacting perm gen  total 21248K, used 2647K [0x0000000113630000, 0x0000000114af0000, 0x0000000118830000)
   the space 21248K,  12% used [0x0000000113630000, 0x00000001138c5ec0, 0x00000001138c6000, 0x0000000114af0000)
No shared spaces configured.

其中第一行中的"DefNew"代表使用的收集器是Serial收集器,这次Minor GC使用copy算法,做了下面几件事情:

  • 检索heap中的对象,将还能通过GC roots能够遍历到的对象copy到to区中
  • 如果需要copy的对象没法进入from区中,则将其晋升到老年代,本例中即发生了这种情况,3个2MB的数组全部晋升到老生代(OU:6144)
  • 清理eden和from中无用的垃圾
  • 互换from和to空间

比较有意思的是,在我的机器上重新再跑一次示例程序,发生了不一致的gc行为:

1
2
3
4
5
6
7
8
9
10
11
[GC [PSYoungGen: 6963K->384K(9216K)] 6963K->6528K(19456K), 0.0052500 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[Full GC [PSYoungGen: 384K->0K(9216K)] [ParOldGen: 6144K->6436K(10240K)] 6528K->6436K(19456K) [PSPermGen: 2637K->2635K(21248K)], 0.0157270 secs] [Times: user=0.04 sys=0.00, real=0.02 secs]
HeapA
 PSYoungGen      total 9216K, used 2539K [0x00000001106d0000, 0x00000001110d0000, 0x00000001110d0000)
  eden space 8192K, 31% used [0x00000001106d0000,0x000000011094af60,0x0000000110ed0000)
  from space 1024K, 0% used [0x0000000110ed0000,0x0000000110ed0000,0x0000000110fd0000)
  to   space 1024K, 0% used [0x0000000110fd0000,0x0000000110fd0000,0x00000001110d0000)
 ParOldGen       total 10240K, used 6436K [0x000000010fcd0000, 0x00000001106d0000, 0x00000001106d0000)
  object space 10240K, 62% used [0x000000010fcd0000,0x0000000110319278,0x00000001106d0000)
 PSPermGen       total 21248K, used 2645K [0x000000010aad0000, 0x000000010bf90000, 0x000000010fcd0000)
  object space 21248K, 12% used [0x000000010aad0000,0x000000010ad65688,0x000000010bf90000)

GC log第一行的PSYoungGen意味着这次运行中jvm自动选择了Parallel Scavenge收集器,GC行为发生了变化,同样的内存请求,PS收集器除了一次Minor GC以外,还发生了一次Full GC。PS收集器的实现与serial不一致,其行为模式还需要进一步研究.
比较吊诡的是jvm的自动选择行为,我阅读了openjdk的源码,版本为:openjdk-7-fcs-src-b147-27_jun_2011
其中关于jvm自动选择gc的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (os::is_server_class_machine() && !force_client_mode ) {
  // If no other collector is requested explicitly,
  // let the VM select the collector based on
  // machine class and automatic selection policy.
  if (!UseSerialGC &&
      !UseConcMarkSweepGC &&
      !UseG1GC &&
      !UseParNewGC &&
      !DumpSharedSpaces &&
      FLAG_IS_DEFAULT(UseParallelGC)) {
    if (should_auto_select_low_pause_collector()) {//如果需要低时延收集器,选择cms
      FLAG_SET_ERGO(bool, UseConcMarkSweepGC, true);
    } else {//否则缺省使用ps收集器
      FLAG_SET_ERGO(bool, UseParallelGC, true);
    }
    no_shared_spaces();
  }
}

如上所示,jvm在没有明确设置gc时会采用parallel scavenge作为缺省收集器。因此我机器上jvm自动选择gc的行为还需要进一步研究。

  • 示例2:使用ParNew收集器

调整jvm的参数,添加-XX:+UseParNewGC,告诉jvm选择使用ParNew收集器,此时执行的结果与示例1中使用serial收集器的行为完全一样。这里不再赘述

  • 示例3:使用CMS收集器

调整jvm参数为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-Xmx20M -Xms20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC</pre>
此时启动示例程序,我们会看到如下的结果:
<pre class="brush:shell">chendeMacBook-Air:~ eleforest$ jstat -gc 21729 1000
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
1024.0 1024.0  0.0    0.0    8192.0   820.2     8192.0      0.0     21248.0 2638.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   2868.2    8192.0      0.0     21248.0 2638.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   2868.2    8192.0      0.0     21248.0 2638.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   4916.3    8192.0      0.0     21248.0 2638.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   4916.3    8192.0      0.0     21248.0 2638.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   6964.3    8192.0      0.0     21248.0 2638.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   6964.3    8192.0      0.0     21248.0 2638.2      0    0.000   0      0.000    0.000
1024.0 1024.0  0.0   320.1   8192.0   2375.9    8192.0     6146.1   21248.0 2641.2      1    0.009   2      0.001    0.010
1024.0 1024.0  0.0   320.1   8192.0   2375.9    8192.0     6146.1   21248.0 2641.2      1    0.009   2      0.001    0.010
1024.0 1024.0  0.0   320.1   8192.0   2375.9    8192.0     6146.1   21248.0 2641.2      1    0.009   4      0.002    0.011
1024.0 1024.0  0.0   320.1   8192.0   2375.9    8192.0     6146.1   21248.0 2641.2      1    0.009   4      0.002    0.011
1024.0 1024.0  0.0   320.1   8192.0   2375.9    8192.0     6146.1   21248.0 2641.2      1    0.009   6      0.003    0.012

也就是说到第四个2MB申请,老生代里使用6MB的数据之后,jvm还进行了6次full gc,这是由于cms特殊性导致的:cms为了保证进行gc时应用的低时延,要求在老生代中剩余充足的空间以备应用使用。这个特性可以用下列参数进行调整和限制

1
-XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly

其中CMSInitiatingOccupancyFraction的缺省为68%。在我们的示例中,OU已经超过了这个限制,jvm试图去清理老生代,因此发生了多次full gc。
通过修改CMSInitiatingOccupancyFraction为80或者更高值,再次执行示例程序后不会再发生fullGC。
为了使应用平顺,CMS收集器的使用需要小心的调整堆空间的大小,太小的老生代可能会起到相反的效果,过高的CMSInitiatingOccupancyFraction也会导致回收数据时使应用无法正常工作。

以上便是我在这篇博客中想要分享的内容,做一些记录,也分享出来。
但是如分享中所说的,还有以下问题还没有搞清楚:

  • PS收集器的行为,触发full gc的条件
  • jvm自动选择收集器的策略
  • G1收集器的使用
分享到:
评论

相关推荐

    mac mat jvm gc 内存分析

    JVM(Java Virtual Machine)的垃圾收集器(GC,Garbage Collector)扮演着核心角色,负责自动管理应用程序的内存,防止内存泄漏和性能问题。MAT(Memory Analyzer Tool)是由Eclipse基金会提供的一个强大的分析工具...

    实战Hot Spot JVM GC

    总之,熟悉JVM内存划分和GC机制,以及掌握相关监控工具和参数配置,对于避免内存泄漏、提升Java程序的性能和稳定性至关重要。通过实战操作,分析和优化HotSpot JVM GC的工作,是每个Java开发者必须面对和解决的问题...

    JVM_GC调优

    通过对JVM_GC调优的深入探讨,我们可以看出JVM内存管理和垃圾回收机制的复杂性。合理的GC策略和参数调整对于提高Java应用的性能至关重要。理解不同GC算法的特点和适用场景,可以帮助开发者选择最适合特定应用场景的...

    IBM JVM GC 技术文档

    - **对象**:IBM JVM中的对象由类实例组成,每个对象都有自己的属性和方法。 - **堆**:堆是IBM JVM中用于存储对象的区域,是GC的主要操作对象。 - 设置堆大小:可以通过命令行参数来设置初始堆大小和最大堆大小,...

    JVM体系结构与GC调优

    通过深入学习JVM体系结构和GC调优,开发者可以更好地理解和控制Java应用的内存使用,减少垃圾收集的开销,提升系统性能。这份PPT将帮助我们系统地掌握这些关键点,使我们能够应对实际开发中的各种挑战。

    深入理解JVM & G1 GC

    《深入理解JVM & G1 GC》这篇文章和相关压缩包文件主要聚焦于Java虚拟机(JVM)的内存管理,特别是垃圾收集器(GC)的优化,特别是G1(Garbage-First)垃圾收集器的深度解析。下面将详细阐述JVM、GC的基本概念,...

    思维导图-详细了解JVM和GC过程

    理解JVM内存模型和GC机制是Java开发人员的必备技能,通过合理配置JVM参数和选择合适的垃圾收集器,可以有效地提升应用程序的性能和稳定性。在实际项目中,结合监控工具(如VisualVM、JConsole等)进行调优,是解决...

    JVM、GC详解及调优_jvm_JVM、GC详解及调优_

    《JVM、GC详解及调优》是一份深入解析Java虚拟机(JVM)和垃圾收集(Garbage Collection,简称GC)的详细资料。本文将根据提供的信息,深入阐述JVM的工作原理,GC的机制以及如何进行JVM的性能调优。 首先,JVM是...

    JVM垃圾回收机制与GC性能调优

    本文主要探讨JVM堆内存的结构和GC的工作原理,以及如何进行性能调优。 JVM堆是Java应用程序的主要内存区域,用于存储所有类实例和数组。它分为三个主要区域:新域(Young Generation)、旧域(Old Generation)和...

    Sun Hotspot V1.6.0 JVM GC PPT.pdf

    #### 二、JVM垃圾收集器(GC)简介 垃圾收集器(GC)的主要职责不仅仅是回收内存,还包括内存的分配策略。JVM提供了多种不同的垃圾收集器,以满足不同场景的需求。 ##### 2.1 新生代GC 新生代是指JVM堆中的一部分...

    深入JVM内核—原理、诊断与优化视频教程-4.GC算法与种类

    本教程——“深入JVM内核—原理、诊断与优化视频教程”着重讲解了JVM的内部机制,特别是关于垃圾收集(Garbage Collection, GC)的算法和种类,这对于理解和提升Java应用性能至关重要。 一、JVM内存模型 首先,...

    JVM&amp;g1gc;带书签,完整版本

    4. **预测停顿时间模型**:G1允许用户预设停顿时间,它会根据当前堆状态和预期停顿时间动态调整GC策略,以尽可能满足停顿时间目标。 **G1的工作流程** G1的垃圾收集主要分为四个阶段:初始标记、并发标记、最终...

    JVM-GC全面知识系统详解

    JVM提供了多种垃圾回收策略,如Minor GC(针对新生代)、Major GC(针对老年代)和Full GC(整个堆和方法区)。GC通过可达性分析算法判断对象是否存活,然后进行垃圾清理。常见的垃圾收集器有Serial、Parallel、CMS...

    jvm_gc.rar_jvm_垃圾回收

    5. **GC日志分析**:通过分析JVM产生的GC日志,可以了解垃圾回收的效率和内存使用情况,从而调整参数以优化性能。 6. **内存泄漏检测**:关注长期未被释放的对象,可能暗示存在内存泄漏问题。开发者可以通过工具如...

    生产环境jvm调优的实例代码-jvm.zip

    6. **JVM性能日志与分析**:通过`-XX:+PrintFlagsFinal`查看默认JVM配置,`-XX:+PrintGC`和`-XX:+PrintGCDetails`记录GC日志,`-XX:+HeapDumpOnOutOfMemoryError`在内存溢出时生成堆转储文件,便于后期分析。...

    JVM性能调优-JVM内存整理及GC回收

    这份文档详细阐述了JVM性能调优的关键概念,包括JVM内存模型、垃圾回收(Garbage Collection, GC)的原理以及各种垃圾回收算法,这些都是JAVA程序员在日常工作中需要理解和掌握的核心技术。 首先,JVM内存模型是...

    深入JVM内核 - 原理、诊断与优化

    熟悉和掌握JVM平台有着重要的实用价值和意义。 在本课程中个,将详细介绍JVM的基本原理、组成以及工作方式,并配合实际案例,介绍相关的调优技巧。 课程大纲: 第一课 初识JVM JVM分类 Java语言规范 JVM规范 ...

    JVM Memory Model and GC.pdf

    垃圾收集的主要类型有四种,虽然在内容中没有具体提到这四种类型,但在JVM中常见的垃圾收集器包括Serial GC、Parallel GC、CMS(Concurrent Mark Sweep)GC和G1(Garbage-First)GC等。这些垃圾收集器有不同的特性,...

Global site tag (gtag.js) - Google Analytics