Tips of 垃圾回收 (GC)

来源:互联网 发布:微电影片头素材知乎 编辑:程序博客网 时间:2024/06/06 02:04

绝大多数的对象都在young generation被分配,也在young generation被收回,当younggeneration的空间被填满,GC会进行minor collection(次回收),这次回收不涉及到heap中的其他generation,minor collection根据weak generational hypothesis(弱年代假设)来假设young generation中大量的对象都是垃圾需要回收,minor collection的过程会非常快。young generation中未被回收的对象被转移到tenuredgeneration,然而tenured generation也会被填满,最终触发major collection(主回收),这次回收针对整个heap,由于涉及到大量对象,所以比minor collection慢得多。

----------------------------------------------------

名词GC Roots正是分析这一过程的起点,例如JVM自己确保了对象的可到达性(那么JVM就是GCRoots),所以GC Roots就是这样在内存中保持对象可到达性的,一旦不可到达,即被回收。通常GCRoots是一个在current thread(当前线程)的call stack(调用栈)上的对象(例如方法参数和局部变量),或者是线程自身或者是systemclass loader(系统类加载器)加载的类以及nativecode(本地代码)保留的活动对象。所以GC Roots是分析对象为何还存活于内存中的利器。

用Strong Ref,存储大量数据,直到heap撑破;利用interned strings(或者class loader加载大量的类)把perm gen撑破。

----------------------------------------------------

垃圾回收(GC)的产生过程

     1)新生成的对象在Eden区完成内存分配

     2)当Eden区满了,再创建对象,会因为申请不到空间,触发minorGC,进行young(eden+1survivor)区的垃圾回收。(为什么是eden+1survivor:两个survivor中始终有一个survivor是空的,空的那个被标记成To Survivor)

     3)minorGC时,Eden不能被回收的对象被放入到空的survivor(也就是放到To Survivor,同时Eden肯定会被清空),另一个survivor(From Survivor)里不能被GC回收的对象也会被放入这个survivor(To Survivor),始终保证一个survivor是空的。(MinorGC完成之后,To Survivor 和 From Survivor的标记互换)

     4)当做第3步的时候,如果发现存放对象的那个survivor满了,则这些对象被copy到old区,或者survivor区没有满,但是有些对象已经足够Old(通过XX:MaxTenuringThreshold参数来设置),也被放入Old区

     5)当Old区被放满的之后,进行完整的垃圾回收,即 Full GC

     6)FullGC时,整理的是Old Generation里的对象,把存活的对象放入到PermanentGeneration里(X)。

----------------------------------------------------

Java中四种垃圾回收算法

Java中有四种不同的回收算法,对应的启动参数为
–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC

1. Serial Collector
大部分平台或者强制 java -client 默认会使用这种。
young generation算法 = serial
old generation算法 = serial (mark-sweep-compact)
这种方法的缺点很明显,stop-the-world, 速度慢。服务器应用不推荐使用。

2. Parallel Collector
在linux x64上默认是这种,其他平台要加 java -server 参数才会默认选用这种。
young = parallel,多个thread同时copy
old = mark-sweep-compact = 1
优点:新生代回收更快。因为系统大部分时间做的gc都是新生代的,这样提高了throughput(cpu用于非gc时间)
缺点:当运行在8G/16G server上old generation live object太多时候pause time过长

3. Parallel CompactCollector (ParallelOld)
young = parallel = 2
old = parallel,分成多个独立的单元,如果单元中live object少则回收,多则跳过
优点:old generation上性能较 parallel 方式有提高
缺点:大部分server系统old generation内存占用会达到60%-80%, 没有那么多理想的单元live object很少方便迅速回收,同时compact方面开销比起parallel并没明显减少。

4. ConcurentMark-Sweep(CMS) Collector
young generation = parallel collector = 2
old = cms
同时不做 compact 操作。
优点:pause time会降低, pause敏感但CPU有空闲的场景需要建议使用策略4.
缺点:cpu占用过多,cpu密集型服务器不适合。另外碎片太多,每个object的存储都要通过链表连续跳n个地方,空间浪费问题也会增大。

几条经验:
1. java -server
2. 设置Xms=Xmx=3/4物理内存
3. 如果是CPU密集型服务器,使用–XX:+UseParallelOldGC, 否则–XX:+UseConcMarkSweepGC
4. 新生代(Xmn设置),Parallel/ParallelOld可设大于Xmx1/4,CMS可设小,小于Xmx1/4
5. 优化程序,特别是每个用户的session中的集合类等。我们的一个模块中session中曾经为每个用户使用了一个ConcurrentHashMap, 里面通常只有几条记录,后来改成数组之后,每台机大概节约了1~2G内存。

不过总的说来,Java的GC算法感觉是业界最成熟的,目前很多其他语言或者框架也都支持GC了,但大多数都是只达到Java Serial gc这种层面,甚至分generation都未考虑。JDK7里面针对CMS又进行了一种改进,会采用一种G1(Garbage-First Garbage Collection)的算法。实际上Garbage-Firstpaper(PDF) 2004年已经出来了,相信到JDK7已经可以用于严格生产环境,有时间也会进一步介绍一下G1。
另外在今年的Sun Tech Days上JoeyShen讲的Improving Java Performance(PDF)也是一个很好的Java GC调优的入门教程。

----------------------------------------------------

虚拟机规范不要求实现永久代的垃圾回收,原因有二:

1、该区域上的垃圾少,回收的性价比非常低;

2、判断一个类是否为垃圾比判断一个对象是否为垃圾昂贵得多,复杂度高。

鉴于此,我们对该区域的垃圾回收别报希望,只要记住一点就行了:永久代上不会主动进行垃圾回收。因此,在日常开发的过程中,尤其是在struts、hibernate等框架的使用上一定要留意,不要让动态生成的类占满了永久代区域。

开启永久代的gc:-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

----------------------------------------------------

关于shallow size、retained 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的shallowsize也是24字节。

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