理解JVM垃圾回收

  理解java虚拟机的垃圾回收机制对于做java开发的人来说极为重要,它会帮你在写代码的时候就写出优质的、GC友好的代码,从而避免日后的一些问题,你也会发现原来JVM调优并不难。

GC基础理论

JVM的垃圾回收线程只会扫描可到达的对象,所谓可到达的对象就是从roots(global, stack)节点开始进行深度优先遍历能够到达的对象节点。

对于JVM的性能调优通常是在以下三者间进行权衡:

Throughput

  计算公式是 1- (gc time)/time

Pause/Latency

  应用线程的延时或者暂停。

Footprint/Memory

  内存的消耗。

  JVM的调优往往是这三者之间的权衡,如果用Parallel GC会有比较大的吞吐量和较少的内存,但是会有长延时,使用CMS会有较短的延时但是吞吐量会下降,内存消耗也更大。

 

几种垃圾回收算法

串行垃圾回收器(The serial collector)

  单线程的垃圾回收期,适用于单核机器。

并行垃圾回收器(The parallel collector)

  进行垃圾回收时会造成stop-the-world. 垃圾回收时停止所有应用线程,开启垃圾会首先称并行的进行垃圾收集,垃圾回收完毕后开启应用线程。这种回收器在垃圾回收时会造成应用的长时间的暂停。这类垃圾回收器吞吐量高,也叫throughput collector.

并发垃圾回收器(The concurrent collector)

  回收时也会造成stop-the-world,但是时间会短很多。大部分的时候垃圾回收线程和应用线程同时进行。

 

HotSpot VM堆的结构

CMS/Parallel GC

  JVM的内存由几部分组成,栈、堆、Permanent代。其中堆分为Young generationold generationyoung generationEden spacetwo survivor space组成。也有说法是堆由Young generation, old generation以及 permanent generation组成,这个关系不大,并不影响理解垃圾回收算法。

  JVM的分代收集是基于一种假设,绝大部分的对象生命周期都很短。也就是说大部分的对象在new出来之后,很快就变成了不可达的对象。在现实的大部分应用中也确实是这样的。

  一个新的对象首先是分配在Eden space中,当Eden space的空间满了会进行一次minor gcminor gc会把Eden space中回收后剩下的对象以及 from survivor中剩下的对象copyto survivor中,并且清空Eden spacefrom survivor。下一次minor gc时执行同样的操作。也就是说两个survivor space永远有一个是空的。当一个对象在两个survivor中来回复制了N次后,这个对象会被promoteold generation中。这个N可以配置-XX:MaxTenuringThreshold=n, 默认是15次。当old generation 占满时会造成major gc(full gc), 注意cms进行full gc的时机可以配置(XX:CMSInitiatingOccupancyFraction=92),配置高了容易导致concurrent mode failure, 然后对整个堆进行长时间的gc

为什么full gc效率低而minor gc效率很高?

  Minor gc回收的是young generation中的对象,根据上面的假设,绝大部分的对象生命周期都非常短,虽然eden space满了才进行minor gc,但实际上绝大部分对象活不过一个minor gc的周期,minor gc扫描的对象就非常少了。而old generation中的对象都是经过多次minor gc后存活下来的对象,进行full gc时,绝大部分的对象都是可到达的,因此需要扫描的对象非常多。

Concurrent Mark Sweep(CMS)

CMS算法主要分为四个步骤:

1. Initial marking pause

暂停所有应用线程(stop-the-word)并且标记能直接从roots节点到达的节点,虽然会造成STW,由于只扫描全局变量、栈等根节点直接到达的对象,这一步会非常快。

2. Concurrent marking phase.

恢复应用线程,并且开启gc线程从第一个步骤中扫描到的节点开始进行深度遍历并标记,这个过程GC Thread和应用线程是同时进行的,当应用进程改变了某个对象的状态时会把当前对象所在的page标记为dirtynewobject所在的page也标记为dirty,深度遍历完整个对象图。

3. Final marking pause

暂停所有应用进程并扫描roots以及标记为dirty page所在的区域,这个步骤也会暂停所有应用,由于只扫描roots以及dirty page,因此暂停时间比较短。

4. Concurrent sweeping phase.

恢复应用进程并对内存进行回收。

 

由于dirty page中的对象要扫描两遍,CMS的吞吐量会比Parallel GC小一些。CMS默认在old generation的空间达到92%时进行major gc,并且CMS会产生floating garbage(图中的c节点),因此CMS比其它垃圾回收期需要更大的内存空间。换来的是应用的低延时。同时CMS并没有把对象进行compact,会有内存碎片。-XX:CMSFullGCsBeforeCompaction可以设置几次GC后对old gen进行压缩避免oome

 

GC友好编程

少分配大的对象

大对象的分配容易造成内存碎片,频繁的分配大的对象会影响应用的“稳定状态”(应用在开启一段时间后,内存、cpu等的使用应该是相对稳定的)。

集合类的使用

当你能预见到某个容器的大小估值时尽量指定其size,尤其是对大的容器,当容器size不够时往往会申请一片更大的空间,把老的元素copy过来,然后丢弃老的空间,造成不必要的空间分配,同时也容易造成内存碎片。

缩短对象的生命周期

不要使用SoftReference,会造成对象生命周期的延长,进行不必要的扫描。

 

Trouble shooting

Offline mode, after the fact

GCHisto or GCViewer (search for GCHisto” or chewiebug GCViewer– both are GC 

Online mode, while application is running

VisualGC plug-in for VisualVM (found in JDKs bin directory, launched as 'jvisualvm')

jconsole

Others

jstat

jstack pid>jstack.log

jmap dump:format=b.file=heapdump.hprof

IBM Heap Analyzer

在程序出现问题时可以开启GC logs,正常的生产环境不开启。可以使用GCHisto 或者GCViewer离线分析gc的情况。

-Xloggc:/home/user/logs/gc.log

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-XX:+UseGCLogFileRotation

-XX:NumberOfGClogFiles

-XX:GCLogFileSize

当发生OOMEdump 堆,建议开启。可以使用IBM Heap Analyzer来分析堆中异常情况(往往是因为堆中对象分配过多造成的)。

-XX:+HeapDumpOnOutOfMemoryError

 

其它

开启-server

-Xmx -Xms设置的大小一致,避免堆的自适应变大变小的开销。

-XX:-UseParallelOldGC与-XX:-UseParallelGC同时使用

-XX:-UseConcMarkSweepGC与-XX:+UseParNewGC同时使用

GC的时间主要花在对象的扫描和释放空间,由于释放空间可以和应用线程同时进行,因此实际上影响最大的是在对象的扫描上,而实际应用中,经常会有分配一个很大的static变量做缓存的情况,缓存里的这些对象绝大部分都是不变的,只有少量的会更新,但是在垃圾回收时每次都要扫描那些根本不需要扫描的对象。淘宝根据这种业务状况开发了taobao jvm,但是对于这类对象的使用也有限制,如必须是readonly的,不能和堆中其它对象有引用。Terracotta也有类似的产品bigmemory

References

[1]. http://infoq.com/presentations/Extreme-Performance-Java

[2].http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html

[3].http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

[4]. http://www.oracle.com/technetwork/java/tuning-139912.html

[5]. Tony Printezis, David Detlefs, A Generational Mostly-concurrent Garbage Collector. International Symposium on Memory Management, 2000

[6]. http://www.slideshare.net/achinth/garbage-collection-algorithms

[7]. http://docs.neo4j.org/chunked/stable/configuration-jvm.html

[8]. http://www.oracle.com/technetwork/java/hotspotfaq-138619.html

[9].http://www.softwareengineeringsolutions.com/blogs/2010/05/01/garbage-collection-in-java-part-3/

 

posted @ 2013-06-08 12:45  zhoubo  阅读(1989)  评论(0编辑  收藏  举报