(2)垃圾收集器与内存分配策略
? 当要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。
? 在上一节谈到的几个JAVA内存区域中,其中程序计数器、虚拟机栈、本地方法栈三个区域随线程而生,随线程而灭。每个栈桢分配多少内存,在类结构确定下来后就已知的,因此这几个区域的内存分配和回收都具备确定性,所以这几个区别不需要过多考虑回收的问题,因为方法结束或线程结束时,内存自然就跟着回收了。
? 而JAVA堆和方法区而和上述几个区域不同,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样。只有在程序处于运行期间时才能知道会创建多少个对象,这部分内存的分配和回收都是动态的,本节讨论的内存分配和回收是指这一部分内存。
?? 如何判断对象已死?
主流的程序语言都是使用根搜索算法(GC Roots Tracing)判定对象是否存活
基本思路是:通过一系列名为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索超过的路径称为引用链(Reference chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
下图A的各个对象有引用链有GC Roots相连,说明对象都存活,而图B的三个对象虽然都各自相连,但却没有与任何一个GC Roots相连,则可判定它们为可回收的对象。

在JAVA中,可作为GC Roots的对象包括下面几种:
1,虚拟机栈(栈桢中的本地变量表)中引用的对象。
2,方法区中的类静态属性引用的对象。
3,方法区中的常量引用的对象。
4,本地方法栈中JNI(即一般说的Native方法)的引用的对象。
?
JDK1.2后,JAVA对引用的概念进行了扩充,针引用分为以下四种:强度依次逐渐减弱
强引用:类似"Object obj = new Object()”这类的引用就是强引用,只要引用关系还存在,就不会回收被引用的对象。
软引用:用来描述一些还有用,但并非必需的对象,在即将发生内存溢出之前,会将这些对象列入回收范围,以进行第二??
???????????? 次回收。在JDK1.2后,提供了SoftReference类来实现软引用。
弱引用:用来描述非必要对象,比软引用更弱一些,这些对象只能生存到下一次垃圾收集之前。1.2后,使用
???????????? WeakReference来实现弱引用。
虚引用:是最弱的一种引用关系,一个对象是否有虚引用存在,完全不会对其生存时间构成影响,它的唯一目的就是希望
???????????? 能在这个对象被收集器回收时收到一个系统通知。1.2后,提供了PhantomReference来实现虚引用。
?
关于finalize方法:
如果一个对象在GC Roots上没有与任何一个对象连接,但它有覆盖finalize方法,如果它在finalize方法中有重新连接上GC Roots对象,则不会被回收。
?
?
?
?图中的问号表示1.7版本即将推出的G1收集器。如果两个垃圾收集器之间存在连线。就说明它们可以搭配使用。
没有一种垃圾收集器是放之四海皆准,任何场景下都适用的。
Serial收集器
??? 它是1.3版本之前新生代的唯一收集器,是一个单线程收集器,在它进行垃圾收集时,必须暂停其他所有的工作线程(sun将此称之为Stop the world),直到它收集结束 。
?
此种收集器在client模式下的默认新生代收集器,简单高效,收集几十M甚至一两百M的新生代,停顿时间完成可以控制在几十毫秒左右。
ParNew收集器
? ? ParNew是Serial收集器的多线程版本,除了使用多线程进行垃圾回收外,其余行为与Serial基本一样。
它是Server模式下虚拟机首选的新生代收集器
Parallel Scavenge收集器
?? 这也是一个新生代,使用复制算法的并行多线程垃圾收集器,这个收集器的特点是:其目标是使用CPU运行时间达到一个可控的吞吐量。所谓吞吐量是指CPU运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),例如:CPU总运行时间为100分钟,其中处理垃圾回收占了1分钟,则吞吐量就是99%。
此收集器提供了两个参数用于精确控制吞吐量,分别是最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数及直接吞吐量大小的-XX:GCTimeRatio参数。
??? -XX:MaxGCPauseMillis参数允许的值是大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过设定值,此时间变小,会使垃圾回收变得更频繁。可能使吞吐量下降。
??? -XX:GCTimeRatio参数的值是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,相当于是吞吐量的倒数。如果把此参数设置为19,那允许的最大GC时间就占总时间的5%(即1/(1+19)),默认为99,就是最大允许1%的垃圾回收时间。
?上面介绍的三个都是新生代垃圾回收器,下面再介绍三个老年代垃圾回收器
Serial Old收集器
这个收集器的示例图与Serial一样,它是Serial收集器的老年代版本,使用“标记-整理”算法,这个收集器的主要目的是在Client模式下的虚拟机使用。在Server模式下,它主要是作为CMS模式的后备预案,或与Parallel Scavenge收集器搭配使用。
Parallel Old收集器
Paralle? Old是Paralle Scavenge收集器的老年代版本,使用“标记-整理“算法,这个收集器是1.6版本后提供,在注重吞吐量及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。
?CMS(Concurrent Mark Sweep)收集器
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,使用的是“标记-清除”算法。互联网服务器尤其重视服务的响应速度,CMS收集器非常符合这类应用的需求。
它的运作过程分为四步:
1,初始标记
2,并发标记
3,重新标记
4,并发清除
??? 其中初始标记和重新标记这两个步骤仍然需要“stop the world",初始标记只是标记一下GC ROOTS能直接关联到的对象,速度很快。并发标记就是进行 GC ROOTS TRACING的过程,而重新标记阶段是为了修正并发标记期间,因用户程序继续运作导致标记变动的那一部分对象的标记记录。这一阶段的停顿时间会稍比初始标记长一些,但远比并发标记时间短。
??? 整个过程耗时最长的并发标记和并发清除过程中,收集线程都可以和用户线程一起工作。
CMS收集器是一款优秀的收集器,主要优点是并发收集、低停顿。但它有下面三个主要的缺点:
1:对CPU资源非常敏感,会占用一部线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。
2,无法处理浮动垃圾,在并发清理阶段程序还在运行,可能产生新的垃圾(浮动垃圾),当这些浮动垃圾过大 ,CMS
???? 运行期的预留内存无法容下这些浮动垃圾时,将出来“Concurrent Mode Failure“失败,这些虚拟机将启用Serial
???? Old来进行老年代垃圾回收。
3,最后一个缺点是,由于CMS采用“标记-清除“算法,垃圾收集结束后,会产生大量空间碎片。空间碎片过多,将会给大
???? 对象分配带来很大麻烦。很可能由于无法找到足够大的连续空间来分配此大对象,导致触发一次FULL GC。虚拟机提供
???? 了一个参数-XX:+UseCMSCompactAtFullCollection开关参数,表示在FULL GC后,会进行一次碎片整理。
?
G1收集器
G1收集器在1.7版本正式发布,关于G1垃圾收集器的新特性可参考这里
?
内存分配与回收策略
JAVA的内存自动管理可以归结为:对象的内存分配以及回收分配给对象的内存。上面用大量篇幅描述了内存回收,下面再谈谈内存分配。
从大方向说,就是在堆上分配,对象主要分配在新生代的Eden区上,当然也不是固定的,其细节取决于使用哪种垃圾收集器组织及其参数设置。
下面介绍几条最普遍的内存分配规则:
1,对象优先在Eden分配
在大多数情况下,对象在新生代Eden区中分配,当Eden区中没有足够的空间进行分配时,虚拟机将发起一次Minor GC。
先看下面这段程序:
[GC [DefNew: 6472K->140K(9216K), 0.0066974 secs] 6472K->4236K(19456K), 0.0067406 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC [DefNew: 6370K->139K(9216K), 0.0006466 secs] 10466K->4236K(19456K), 0.0006861 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 2351K [0x03b00000, 0x04500000, 0x04500000) eden space 8192K, 27% used [0x03b00000, 0x03d28fd8, 0x04300000) from space 1024K, 13% used [0x04300000, 0x04322fe0, 0x04400000) to space 1024K, 0% used [0x04400000, 0x04400000, 0x04500000) tenured generation total 10240K, used 4096K [0x04500000, 0x04f00000, 0x04f00000) the space 10240K, 40% used [0x04500000, 0x04900020, 0x04900200, 0x04f00000) compacting perm gen total 12288K, used 2105K [0x04f00000, 0x05b00000, 0x08f00000) the space 12288K, 17% used [0x04f00000, 0x0510e7f8, 0x0510e800, 0x05b00000)No shared spaces configured.??
上面这个是允许分配担保的例子:
与第一个例子的区别是在第二行GC日志,allocation4,5,6在新生代都被回收,Eden由6370K->139K,原来新生代+老年代共占用了10466K,这时也优化到了4236K。最后堆的占用情况与第一个例子类似。
?



