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

2017-11-27  本文已影响10人  lyoungzzz

经过半个世纪的发展,目前内存的动态分配与内存回收技术已经相当成熟了 ,一切看起来已经进入“自动化”时代,那么学习垃圾收集器(GC)和内存分配的意义何在?

当面临上述两种情况时,就需要对垃圾收集和内存分配进行必要的监控和调节。

Java内存运行时各个区域内存分配和回收的情况

由于这三个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而出栈和入栈,每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,在方法或者线程结束时,内存自然完成回收。

一个接口中多个实现类需要的内存可能不一样,一个方法中多个分支需要的内存也可能不一样,只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配与回收都是动态的。

如何确定对象的“存活”与“死亡”

垃圾收集器在对堆进行会收前,第一件事就是要判断哪些对象还“存活”,哪些已经“死亡”。

* 引用计数算法

定义:在对象中增加一个引用计数器,每当有一个地方引用它时,计数器加1,当引用失效时,计数器减1,减为0时,表示该对象不可能再被使用。

*可达性分析算法

定义:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对对象是不可用的。
在java语言中,可作为GC Roots的对象包括以下几种:

再谈引用

狭隘定义:reference类型的数据中存储的数值是另外一块内存的起始地址,则称这块内存代表着一个引用。

在jdk1.2以后,对引用的概念进行了扩充,将引用分为强引用软引用弱引用虚引用4种。引用强度依次减弱。

对象:生存还是死亡

当对一个对象进行可达性分析后,如果该对象不存在一条连接GC Roots的引用链,那么该对象可能存在一次自救的机会,来确定是否被回收。

回收方法区

在堆中,尤其时新生代中,进行一次垃圾收集一般可以回收70%-95%的空间,而永久代的垃圾收集效率远低于此。
永久代的垃圾收集:废弃常量,无用的类

垃圾收集算法

* 标记-清除算法:最基础的算法

首先标记出所有需要被回收的对象,在标记完成后统一回收被标记的对象。

* 复制算法:适用于存活率较低的对象(新生代)

内存按容量分为大小相等的两块,当一块的内存使用完,将还存活的对象按内存顺序复制到另一块的上,然后清理掉已经使用过的内存。

主要应用在java堆中新生代的垃圾收集上。
新生代:内存分为一块较大的Eden空间和两块较小的Survivor空间(From Survivor和To Survivor),每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性的复制到另一块Survivor上,清理掉刚才使用过的空间。当Survivor空间不够时,对象直接进入老年代(大对象可以直接进入老年代)。

*标记-整理算法:适用于存活率较高的对象(老年代)

不需要进行较多的复制操作,更关键的是没有浪费50%的空间,标记过程与“标记-清除”算法一样,但后续操作不是对可回收对象进行清理,而是让所有存活的对象向一端移动,然后清理掉边界意外的内存。

*分代收集算法

根据对象的存活时间的不同,对新生代采取“复制”算法,老年代采取”标记-清除“或者“标记-整理”进行回收。

HotSpot的算法实现

垃圾收集器(暂时跳过)

内存分配与回收策略

对象主要分配在Eden区上,少数情况直接分配到老年代。分配的细节取决于使用哪一种垃圾收集器组合和虚拟机中与内存相关的设置。

*对象优先在Eden中分配

大多数情况下,对象在新生代Eden中分配,当Eden区中没有足够空间进行分配时,虚拟机将发起一次Minor GC

*大对象直接进入老年代

大对象:需要大量连续内存空间的Java对象(长字符串、数组)

*长期存活的对象将进入老年代

虚拟机给每个对象定义一个年龄计数器,对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15)时,就会被晋升到老年代。

*动态对象年龄判断:为了适应不同程序的内存状况

虚拟机并不要求对象的年龄达到了MaxTenuringThreshold才能晋升老年代,当Survivor空间中相同年龄对象的总和大于Survivor空间一半,大于或者等于该年龄的对象可以直接进入老年代。

*空间分配担保

在发生Minor GC前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立,Minor GC可以确保安全,否则,虚拟机会查看HandlePromotionFailor设置值是否允许担保失败。如果允许,会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC(取平均值进行比较仍是一种比较冒险的手段,可能某次Minor GC后存活的对象特别多,远远高于平均值,依然会导致担保失败,仍需重新发起一次Full GC),如果小于,或者HandlePromotionFailor设置不允许,则这时要改为进行一次Full GC。

上一篇下一篇

猜你喜欢

热点阅读