GC的一些预备性知识

一般用在性能分析或内存泄露分析中的。

一、GC Root

JAVA虚拟机通过可达性(Reachability)来判断对象是否存活,基本思想是以"GC Roots"的对象作为起始点向下搜索,搜索形成的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(即不可达的),则该对象被判定为可以被回收的对象,反之不能被回收。

GC Roots可以是以下任意对象。

  • 一个在current thread的call stack(调用栈)上的对象(如方法参数和局部变量);
  • 线程自身或system class loader(系统类加载器)加载的类;
  • native code(本地代码)保留的活动对象。

 

1>,Minor GC触发条件:

当JVM无法为一个新的对象分配空间时会触发Minor GC,比如当Eden区满了。所以分配率越高,越频繁执行Minor GC。

内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden和Survivor区进行标记和复制操作,取代经典的标记、扫描、压缩、清理操作。所以Eden和Survivor区不存在内存碎片,写指针总是停留在所使用内存池的顶部。

执行Minor GC操作时,不会影响到永久代。从永久代到年轻代的引用被当成GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。

所有的Minor GC都会触发"全世界的暂停(stop-the-world)",停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就是,大部分Eden区中的对象都能被认为是垃圾,永远也不会被复制到Survivor区或老年代空间。如果正相反,Eden区大部分新生对象不符合GC条件,Minor GC执行时暂停的时间将会长很多。

2>,Full GC触发条件:

1,调用System.gc时,系统建议执行Full GC,但是不必然执行。

2,老年代空间不足。

3,方法区空间不足。

4,通过Minor GC后,进入老年代对象的平均大小大于老年代的可用内存。

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

 

二、引用类型

从最强到最弱,不同的引用(可达性)级别反映了对象的生命周期。

1、Strong Ref(强引用):通常编写的代码都是Strong Ref,与此对应的是强可达性,只有去掉强可达,对象才被回收。

普通的java引用,通过new创建的对象。

StringBuffer buffer = new StringBuffer();

对于集合中的对象,应在不使用的时候移除掉,否则可能会导致内存泄漏。

2、Soft Ref(软引用):对应软可达性,只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象。一般可用来实现缓存,通过java.lang.ref.SoftReference类实现。

3、Weak Ref(弱引用):比Soft Ref更弱,当发现不存在Strong Ref时,立刻回收对象而不必等到内存吃紧的时候。通过java.lang.ref.WeakReference和java.util.WeakHashMap类实现。

4、Phantom Ref(虚引用):根本不会在内存中保持任何对象,你只能使用Phantom Ref本身。一般用于在进入finalize()方法后进行特殊的清理过程,通过java.lang.ref.PhantomReference实现。

要想把heap和perm gen撑破也就很容易了,利用Strong Ref,存储大量数据,直到heap撑破;利用interned strings(或class loader加载大量的类)把perm gen撑破。

三、shallow size

Shallow size就是对象本身占用内存的大小,不包含对其它对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。数组的shallow size由数组元素的类型(对象类型、基本类型)和数组长度决定。

在32位系统上,对象头占用8字节,int占用4字节,不管成员变量(对象或数组)是否引用了其它对象或者赋值为null,它始终占用4字节。对于String对象来说,它有三个int成员(3*4=12字节)、一个char[]成员(1*4=4字节)以及一个对象头(8字节),总共3*4 +1*4+8=24字节。根据这一原则,对String a=”rosen jiang”来说,实例a的shallow size也是24字节。

Retained size是该对象自己的shallow size,加上从该对象能直接或间接访问到的对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。

把内存中的对象看成下图中的节点,并且对象和对象之间互相引用。这里有一个特殊的节点GC Roots,这就是reference chain的起点。

https://img-blog.csdn.net/20130529170040214 https://img-blog.csdn.net/20130529170100608

从obj1入手,上图中蓝色节点代表仅仅只有通过obj1才能直接或间接访问的对象。因为可以通过GC Roots访问,所以左图的obj3不是蓝色节点;而在右图却是蓝色,因为它已经被包含在retained集合内。

所以对于左图,obj1的retained size是obj1、obj2、obj4的shallow size总和;右图的retained size是obj1、obj2、obj3、obj4的shallow size总和。obj2的retained size可以通过相同的方式计算。

四、Heap Dump

heap dump是在特定时间点,java进程的堆内存快照,包含快照被触发时java对象和类在heap中的情况。由于快照只是一瞬间的事情,所以heap dump中无法包含一个对象在何时、何地(哪个方法中)被分配这样的信息。

在获取堆转储文件前,通常要手动触发一次gc,以排除弱引用的干扰。

设置JVM参数,获得dump文件:

-XX:+HeapDumpOnOutOfMemoryError,JVM会在发生内存泄露时抓拍下当时的内存状态。

可以使用jmap获取heap dump。

jmap -dump:format=b,file=<dumpfile> <pid>

解释:format=b-->指定格式为二进制;file=<dumpfile>-->指定文件名称,自定义;<pid> -->进程id

其它参数:

-XX:HeapPath=/usr,转储文件的存放目录。

-XX:+PrintGC,在GC发生时,打印GC的简短信息。

-verbose:gc,相当于"-XX:+PrintGC"。

-XX:+PrintGCDetails,在GC发生时,打印GC的详细信息。

-XX:+PrintGCTimeStamps,在GC发生时,打印相对JVM启动时的时间戳。

-XX:+PrintGCDateStamps,在GC发生时,打印GC的日期时间。

-Xloggc:/usr/headtrace.txt,把GC信息记录在文件中,以替代控制台输出。

-XX:+UseSerialGC,使用序列化GC,而不是并行GC。

 

五、GC日志解读

jvm参数优化的目的是减少GC次数。最基本的3个参数,-Xms1024m -Xmx1024m -Xmn256m。其它参数根据GC日志情况再调整。

用jvm启动参数-XX:+PrintGCDetails和-XX:+PrintGCTimeStamps,可生成以下格式的日志。

157.190: [GC (Allocation Failure) [PSYoungGen: 155648K->19872K(322560K)] 398564K->262796K(832512K), 0.0333650 secs] [Times: user=0.13 sys=0.00, real=0.03 secs]

使用序列化GC时的参数设置。

java -Xms2m -Xmx64m -XX:+UseSerialGC -XX:+PrintGCDetails GCTest

生成的信息如下:

[GC (Allocation Failure)

    0.054: [DefNew: 649K->64K(960K), 0.0008674 secs]

    649K->505K(1984K), 0.0009833 secs]

  [Times: user=0.00 sys=0.00, real=0.00 secs]

1   2031616   697016   1334600

...

"[GC (Allocation Failure) ...]",发生GC时的消息头,包括了引起GC的原因("Allocation Failure")。本例可解读为,"GC发生是由[Allocation Failure]引起的"。

"0.054: [DefNew: 649K->64K(960K), 0.0008674 secs]",这是对年轻代(Young Generation)GC的详述,包括:年轻代GC记录的时间戳(相对于JVM启动的时间),GC前总的对象占内存大小,GC后总的对象占内存大小,当前(年轻代的)总内存大小和(本次年轻代的)GC耗时。本例可解读为,在JVM启动后的第0.054秒,发生了一次年轻代的GC,通过移除死亡对象和晋升老年代(Tenured)对象,共释放了585K的内存空间,年轻代的内存占用从649K降低到64K,执行GC耗时0.0008674秒,本次GC执行时,年轻代的内存是960K。

"649K->505K(1984K),",在堆中的GC汇总信息,包括GC执行前年轻代和老年代的对象占用总和,GC执行后它们的对象占用总和,当前堆中对象的总内存占用大小。本例可解读为,这次GC释放了144K的死对象空间,把总对象占用空间从649K减少到505K。执行GC时,堆内存占用总空间为1984K。

"... 0.0009833 secs]",整个GC执行期间的总耗时,包括年轻代GC,老年代GC,方法区GC。本例可解读为,这次GC共花了0.0009833秒。

"[Times: user=0.00 sys=0.00, real=0.00 secs]",在GC整个执行期间花费的CPU时间统计,包括花在JVM上的CPU时间(user),花在OS内核上的CPU时间(system),以及GC花费的时间(real)。如果使用多核CPU的话(会有多个垃圾收集线程),CPU耗时(user+sys)可能要高于GC花费的时间(real)。本例可解读为,GC花在JVM上的CPU时间为0.00秒,花在OK内核上的CPU时间为0.00秒,整个GC耗时为0.00秒。

"1   2031616   697016   1334600",序号/总内存TotalMemory/可用内存FreeMemory/已用内存UsedMemory。

继续解读其它输出信息。

...

[GC (Allocation Failure)

    1.055: [DefNew: 861K->64K(960K), 0.0008510 secs]

    1.056: [Tenured: 1226K->1279K(1280K), 0.0009817 secs]

    1303K->1290K(2240K),

    [Metaspace: 44K->44K(4480K)],

    0.0019995 secs]

  [Times: user=0.00 sys=0.00, real=0.00 secs]

...

这段信息描述的是在堆上(包括年轻代和老年代)的全量收集操作(Full Collection),也做了方法区的垃圾收集。全量收集的日志比次级收集记录了更多的信息。

"1.056: [Tenured: 1226K->1279K(1280K), 0.0009817 secs]",这是对老年代(Tenured Generation)GC的详述,包括:老年代GC记录的时间戳(相对于JVM启动的时间),GC前总的对象占内存大小,GC后总的对象占内存大小,当前总内存,GC执行耗时。本例可解读为,在JVM启动后的第1.056秒,执行了一次老年代GC,总对象大小增长了53K,从1226K到了1279K,这是从年轻代晋升过来的对象大小。这次GC花费了0.0009817秒,在本次GC时,老年代的内存大小为1280K。

"[Metaspace: 44K->44K(4480K)]",这是对方法区(Method Area,也叫MetaSpace)GC的详述,这个GC和老年代的GC是同时执行的。包括:GC前的总对象大小,GC后的总对象大小,以及当前占用的总内存大小。本例可解读为,方法区GC与老年代GC同时执行,总对象大小为44K,没有变化,因为没有发现死对象。在本次GC时,方法区的总内存是4480K。

下面总结了GC日志消息中包含的不同类型的信息。

[GC (Allocation Failure)

      ^ - GC原因



   1.055: [DefNew: 861K->64K(960K), 0.0008510 secs]

   ^ - 时间戳(从JVM启动时算起)

             ^ - GC类型 - 年轻代GC

                      ^- GC前的总对象大小

                            ^ - GC后的总对象大小

                                 ^ - 年轻代占用的总内存

                                           ^ - 本次GC耗时



   1.056: [Tenured: 1226K->1279K(1280K), 0.0009817 secs]

   ^ - 时间戳(从JVM启动时算起)

             ^ - GC类型 - 老年代GC

                       ^ - GC前的总对象大小

                               ^ - GC后的总对象大小

                                      ^ - 老年代占用的总内存

                                                ^ - 本次GC耗时



   1303K->1290K(2240K),

    ^ - 堆信息汇总

    ^ - GC(3个GC)前对象总大小

            ^ - GC(3个GC)后对象总大小

                   ^ - 堆占用的总内存



   [Metaspace: 44K->44K(4480K)],

     ^ - GC类型 - 方法区(也称Metaspace)GC

                 ^- GC前的总对象大小

                       ^ - GC后的总对象大小(不变)

                            ^ - 方法区占用的总内存



   0.0019995 secs]

    ^ - 整个GC(3个GC)操作花费的总时间



 [Times: user=0.00 sys=0.00, real=0.00 secs]

    ^ - 整个GC期间的CPU时间汇总

            ^ - JVM的CPU耗时

                       ^ - 系统内核的CPU耗时

                                 ^ - 整个GC的CPU耗时

 

猜你喜欢

转载自blog.csdn.net/casepk/article/details/82388853