深入理解java虚拟机读书笔记(三)

垃圾收集器和内存分配策略

3.2对象已死吗?

    判断对象是否可以进行回收

3.2.1 引用计数算法

    给对象添加一个引用计数器,每当一个地方引用他时,计数器就加1,当引用失效时,计数器就减1,任何时刻引用计数器为0的对象就是不可能再被使用的。但是其没有办法解决对象之间循环引用的问题。

3.2.2 可达性分析算法

    通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,所走过的路径称为引用链,当一个对象到“GC Root”没有任何引用链相连时,则证明此对象是不可用的。

在java语言中,可作为“GC Root”的对象包括:

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

(2)方法区中类静态属性的引用

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

(4)本地方法栈中JNI所引用的对象

3.2.3 再谈引用

    强引用:强引用就是程序中普遍存在的,类似“Object obj = new Object()”这样的引用,只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。

    软引用:软引用用来描述一些有用但是非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收,如果这次回收还是没有足够的内存,才会抛出内存异常的错误。

    弱引用:弱引用也是用来描述非必需对象的,但是它的强度要比软引用还要弱一些,被弱引用关联的对象,只能活到下次垃圾收集发生之前。

    虚引用:虚引用也被称为幽灵引用或幻影引用,他是最弱的一种引用关系。为一个对象设置虚引用的目的是为了能在对象被回收时受到一个系统通知

3.2.4 生存还是死亡

    即使是可达性分析算法中不可达的对象,也不是“非死不可”,要判断一个对象真的可以进行回收,至少要经历两次标记,当经过可达性分析后发现没有与“GC Root”相连的引用链,则这个时候对象会被第一次标记,然后会进行一次筛选,筛选的条件是看对象有没有必要执行finalize()方法,当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过了,这两种情况都会被视为“没有必要执行”。

    如果这个对象有必要执行finalize()方法,那么这个对象将会被放到F-Queue队列中,稍后将会被一个虚拟机自动建立的,低优先级的线程去执行它,finalize()方法是对象逃脱被回收命运的最后一次机会,GC会对F-Queue队列中的对象进行第二次标记,如果对象在finalize()方法中将自己重新与引用链上的任何一个对象关联,他就会在第二次标记的时候被移出即将被回收的集合,如果没能在finalize()的方法中将自己与引用链上的对象关联,那么就真要被回收了。

    finalize()方法只会被系统自动调用一次,同时finalize()方法运行的代价高昂所以尽量避免使用。

3.2.5 回收方法区

    回收方法区可以自己选择是否要进行垃圾回收

    永久带的垃圾收集主要回收两个部分:废弃常量和无用的类

    要同时满足以下三个条件才可以被称作无用的类:

    (1)该类所有的实例已经被回收,java堆中不存在该类的任何实例

    (2)加载该类的ClassLoader已经被回收

    (3)该类对应的java.lang.Class对象没有在任何地方被引用,也无法在任何地方通过反射访问此类的方法

3.3 垃圾收集算法

    3.3.1 标记-清除算法

    如名字一样,这个算法分为两个阶段:首先标记出所有需要回收的对象,然后对这些对象同一进行回收处理。

    缺点:(1)效率问题:标记和回收两个过程效率都不是很高

               (2)空间问题:采用标记-清除算法容易产生很多不连续的碎片空间,这样很容易导致在后面为较大对象分配内存时,无法找到足够大的连续空间从而不得不触发下一次的垃圾回收操作

    3.3.2 复制算法

     复制算法是为了解决标记清除算法中的效率问题而产生的,其将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块,当这一块用完了,就将这块上面还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次性清理掉。这样做每次都能对整个半区进行回收,在内存分配时也不用内存碎片等复杂情况,只要移动堆顶的指针,按照顺序分配内存就可以了,实现简单,运行高效。

    缺点:将内存缩小为了一半,代价较大

    但是新生代中的对象大多数都是朝生夕死,所以把内存分配为完全相等的两块,太过于浪费,所以并不需要按照1:1的比例来划分内存空间,而是将内存空间分成了一个较大的Eden空间和两个较小的survivor空间。每次使用Eden和其中一块Survivor空间,当回收时,将Eden和Survivor空间还存活的对象复制到另外一个Survivor空间上面,最后清理掉Eden空间和已经使用过的Survivor空间。HotSpot虚拟机默认的Eden和Survivor大小比例为8:1,所以可用内存为90%只有10%会被浪费掉。

    但是由于Servivor空间较小同时我们又不能保证每次回收时所存活的对象只占10%。所以也会存在Survivor空间不够用的情况,这时候就需要依赖其他内存(老年代)进行分配担保。如果另一块Survivor空间不足以存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。

3.3.3 标记-整理算法

    复制算法在对象存活率较高的情况下,需要进行较多的复制操作,效率会变低,并且也没有足够的额外空间进行担保,所以老年代就很不适合这种垃圾收集算法。针对老年代的特点,就有了“标记-整理”算法,标记过程还是和“标记-清除”算法时一样,只不过后面就不是直接对标记对象进行清除了,而是将所有还存活着的对象想一端移动,然后清除掉直接端边界以外的内存

3.3.4 分代收集算法

    按照对象存活的周期的不同将内存分为几块,一般是吧堆分为新生代和老年代,然后针对每一个年代的特点采用不同的垃圾收集算法,比如新生代对象存活时间较短,就适合使用复制算法,花费少量的复制代价。而老年代对象存活时间较长,则更适合使用“标记-清除”和“标记-整理”算法

3.4 HotSpot的算法实现

    在判断对象是否还存活的时候我们说过可以使用可达性分析算法,可达性分析算法中我们需要用到被称为“GC Root”的对象,但是并非所有对象都可以作为“GC Root”,如果一个个遍历寻找“GC Root”的话,效率太低,消耗时时间太多,并且在分析算法进行时整个系统应该看起来像是冻结在了某个时间(停顿),不能出现对象引用关系不断变化的情况(一致性)。所以这些原因导致不能够直接遍历寻找“GC Root”,所以现在主流的虚拟机都使用准确式GC。使用一个OopMap数组来达到这个目的,在OopMap的协助下,HotSpot可以快速且准确的完成“GC Root”枚举。

    我们不可能随处都生成OopMap数组,这样太浪费空间了,即程序在执行过程中并非在所有位置都可以停下来开始GC,只有在特定点才可以,这些点我们称之为安全点。安全点就是所有的线程在要GC的时候停顿的位置。那么如何让所有的线程都到安全点上在停顿下来呢?这里有两种方案可以选择:

    (1)抢先式中断:这种方式不需要执行代码去配合,在GC发生时,首先把所有线程中断,当发现有的线程不在安全点时,就放开他,让他跑到安全点(这种方式现在的虚拟机很少使用)

    (2)主动式中断:在GC需要中断线程时,不主动操作线程,而是设置一个标志,各个线程在运行过程中去轮询这个标志,当发现这个中断标志为真的时候,就自己挂起,中断标志的地方和安全点是重合的。

    当发生GC时,那些Sleep的对象没有办法响应虚拟机,从而进入安全点,这时候就需要安全区域了,安全区域是指一段代码中,引用关系不会发生变化,在任何时间GC都是安全的,当代码执行到安全区域时,首先标示自己已经进入了安全区域,那样如果在这段时间里jvm发起gc,就不用管标示自己在安全区域的那些线程了,在线程离开安全区域时,会检查系统是否正在执行gc,如果是那么就等到gc完成后再离开安全区域。

3.5 垃圾收集器

    3.5.1 Serial收集器

    Serial是一个单线程收集器,单线程不仅仅说明他只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是,他再垃圾收集进行时,必须中断其它所有的工作线程。对于运行在Client模式下的虚拟机来说是一个很好的选择。

    3.5.2 ParNew收集器

    Serrial的多线程版本,是许多运行在Server模式下的虚拟机的首选新生代收集器,目前只有他可以和CMS配合工作

    3.5.3 Paraller Scavenge

    吞吐量优先收集器

    3.5.4 Servial Old收集器

    Servial收集器的老年代版本,两种用途:一种是与Paraller Scavenge收集器搭配使用,并一种用途是作为CMS的后备预案。

    3.5.5 Paraller Old收集器

    Paraller Scavenge的老年代版本,采用多线程和“标记-整理”算法

    3.5.6 CMS收集器

    一种以获取最短回收停顿时间为目标的收集器,采用“标记-清除”算法实现

    (1)初始标记

    (2)并发标记

    (3)重新标记

    (4)并发清除

    3.5.7 G1收集器

    和CMS相比特点:

(1)并行与并发

(2)分代收集

(3)空间整合采用“标记-整理”算法

(4)可预测的停顿

   执行过程:

(1)初始标记

(2)并发标记

(3)最终标记

(4)筛选回收

猜你喜欢

转载自blog.csdn.net/zz0129/article/details/80869595