3、垃圾收集器与内存分配策略

来源:互联网 发布:在淘宝上做虚拟充值 编辑:程序博客网 时间:2024/06/07 12:36

1、概述

Java内存运行时区域的各部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭。在这几个区域不用考虑内存回收的问题,因为方法结束或线程结束时,内存自然就跟随着回收了。Java堆和方法区则不同,我们只有在程序运行期间才能创建对象,这部分内存的分配和回收是动态的。


2、可达性分析算法

        可达性分析算法的基本思路是通过一系列的GC Roots的对象作为起点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连,则证明对象不可用,所以将会被判定为可回收的对象。

可作为GC Roots的对象有以下几种:

1)虚拟机栈(栈帧中的本地变量表)中引用的对象;

2)方法区中类静态属性引用的对象;

3)方法区中常量引用的对象;

4)本地方法栈中JNI(即一般说的NavtiveY方法)引用的对象。


3、引用

引用分为以下几种:

1)强引用(Strong Reference):就是指在程序代码之中普遍存在的,类似“Object o = new Object();”这类引用,只要强引用存在,垃圾收集器永远不会回收被引用的对象;

2)软引用(Soft Reference):用来描述有用但并非必需的对象,在将要发生内存溢出之前,将会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,则抛出内存溢出异常;

3)弱引用(Weak Reference):非必需对象,但比软引用弱些,只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够都会被回收;

4)虚引用(Phantom Reference):最弱的一种,为对象设置虚引用关联的唯一目的是被回收时收到一个系统通知。


4、回收方法区

永久代的垃圾回收主要回收两部分内容:废弃常量和无用的类。

1)废弃常量:假如一个字符串“abc”,已经进入常量池中,但当前系统没有任何一个String引用“abc”,也没有任何地方引用这个字面量,当发生内存回收,且有必要的话,这个“abc”常量就会被系统清理出常量池。

2)无用的类:判断一个无用的类需要满足以下3个条件:

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

可以对满足以上3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样,不使用了就必然会回收。


5、垃圾收集算法

1)标记-清除(Mark-Sweep)算法:如同它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有要回收的对象,然后统一回收。有两点不足:一是效率问题,标记和清除的效率都不高;二是产生大量不连续的内存碎片,碎片太多会导致在分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集。


2)复制算法:为了解决效率问题,一种称为“复制”(Coping)的收集算法出现了,它将内存容量划分为两块大小相等的两块,每次只使用其中一块。当这一块用完了,就将存活的对象复制到另一块上面,然后再把已使用的内存空间一次清理掉。这样使得每次对整个半区进行内存回收,内存分配时不用考虑内存碎片问题,只要移动堆顶的指针,按顺序分配即可。(现在商业虚拟机都采用这种算法回收新生代,即将新生代分为Eden、From/To Survive)


3)标记-整理算法:标记的步骤同“标记-清除”算法一样,后续步骤是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。


4)分代收集算法:根据对象的存活周期的不同,将内存划分为几块,一般是分为新生代和老年代,这样可以根据各个年代的特点采用最适当的收集算法。在新生代,每次垃圾收集都会有大批对象死去,只有少量存活,就采用复制算法。而老年代因为对象存活率较高,就必须使用“标记-清理”或“标记-整理”算法。


6、HotSpot的算法实现

1)枚举根节点

2)安全点

3)安全区域


7、垃圾收集器


1)Serial收集器:单线程收集器,它会暂停其它所有工作的线程,直到收集结束。


2)ParNew收集器:是Serial收集器的多线程版本,其余行为与Serial收集器一样。除了Serial收集器,只有它能与CMS收集器配合工作。


注意:解释一下两个名词:并行和并发

并行(Parallel):指多条垃圾收集线程并行工作。用户线程仍处于等待状态;

并发(Concurrent):指用户线程和垃圾收集线程同时执行。

3)Parallel Scavenge收集器:是一个新生代收集器,使用复制算法,和ParNew一样是并行的多线程收集器。它的目标是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 / (运行用户代码时间+垃圾收集时间),虚拟机运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

4)Serial Old收集器:单线程收集器,使用“标记-整理”算法。

5)Parallel Old收集器:Parallel Scavenge收集器的老年代版本,使用“标记-整理”算法。


6)CMS(Concurrent Mark Sweep)收集器:是一种以获取最短回收停顿时间为目标的收集器。采用“标记-清除”算法。执行过程包括以下4个步骤:

  • 初始标记:标记GC Roots能直接关联到的对象,速度很快;
  • 并发标记:进行GC Roots Tracing的过程;
  • 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那部分对象的标记记录;
  • 并发清除

初始标记和重新标记需要Stop The World。

由于整个过程中耗时最长的并发标记和并发清除过程收集器线程可以与用户线程一起工作,所以,CMS收集器回收过程是与用户线程一起并发执行的。


优点:并发、低停顿;

缺点:

  • 对CPU资源敏感,当CPU在4个以上,并发回收占用不少于25%的CPU资源。
  • 无法处理“浮动垃圾”,在CMS并发清理阶段,用户程序在运行,还会有新的垃圾不断产生,这部分垃圾在标记过程之后,CMS无法当次处理掉,只能留到下次回收是处理。因此,CMS不能像其它收集器,等老年代几乎完全被填满再进行回收,需要预留一部分空间,JDK1.5默认为使用68%的空间时被激活。如果老年代增长不是太快,可通过调高-XX:CMSInitiatingOccupancyFraction的值来提高触发的百分比,以便降低回收次数获得更好的性能。JDK1.6阀值提升到92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Model Failure”失败,这时虚拟机会临时启动Serial Old收集器,这样停顿时间会很长。
  • CMS是一款基于“标记-清除”算法的收集器,收集结束后会产生大量空间碎片,会出现老年代有很大的空间,但无法找到足够大的连续空间来为大对象分配而触发一次Full GC。可通过设置-XX:+UseCMSCompactAtFullCollection或-XX:CMSFullGCsBeforeCompaction来进行调节。

7)G1(Garbage-First)


8、内存分配与回收策略

空间分配担保:



0 0
原创粉丝点击