SpringBoot极简教程 · Spring Boot Spring-BootSpring Boot

深入理解java虚拟机(二)垃圾收集器与内存分配策略

2018-07-25  本文已影响2人  z七夜

写在前面:为了更加深入的了解java虚拟机,就看了一下《深入理解java虚拟机》这本书,一方面为了总结一下自己的认识,另一方面就是想与各位分享,如果有什么不对的地方,欢迎指正

深入理解java虚拟机(一)java内存区域与内存溢出异常

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

垃圾收集,三个步骤
什么时候收集,收集那些,怎么收集

1、收集那些

我们会将一些不使用的对象进行收集,进行回收内存空间,我们怎么知道呢

1、引用计数法

如果这个实例被其他地方引用,那么计数器加一,如果解除引用那么就减一,当计数器为0说明没有地方使用,即可回收,但是缺点就是如果两个对象互相引用,但是后续又用不到,那么就不会被回收,内存浪费

2、可达性分析

通过GC Root向下寻找,形成一条引用链,如果对象不再这条链上,那么说明该对象不可达,可回收,

什么是GC Root

3、引用

我们不论是使用引用计数法,还是使用可达性分析,都有引用这个词,什么意思呢

jdk1.2之前的引用,是reference类型的数据存储的是另一块内存的起始地址,那么就称这块内存代表着一个引用,这种情况下,一个对象只有被引用和没有被引用两种状态,

我们希望有一些对象,在内存空间足够的时候,能够保留在内存中,如果内存空间在进行垃圾收集之后还是很紧张,那么就抛弃这些对象,所以出现 强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference),这4中引用强度依次减弱

4.对象死了吗

当对象没有存在GC Root引用链上,也不是马上死,会被进行一次标记并进行一次筛选,查看对象的finalliaze方法是否被覆盖,或者是否被虚拟机调用过了,

如果被覆盖或者没有被调用,那么这个对象会被放置到F-Queue中,等待执行finaliaze方法,虚拟机会开启一条低优先级的线程去触发这些方法,如果对象在finalize方法中重新加入了引用链(把自己赋值给某个类变量,或者对象的成员变量),那么就不会被回收了,

5.回收方法区

回收方法区内废弃常量和无用的类

废弃常量:如果常量池中有各“abc”的字面量,但是却没有一个string 对象叫做abc,那么这个就是废弃常量,当垃圾回收的时候就会回收

无用的类:在堆中已经没有这个类的实例,加载该类的类加载器已经被回收,这个Class对象没有在任何地方被引用,无法通过反射获取这个类的方法

2、怎么收集

垃圾收集在方法区和堆区

1.标记-清除算法

标记清除:当堆内存耗尽的时候,触发GC线程,使用户线程停止,遍历所有的GC root,将还存活的随想标记一下,之后清除所有未标记的对象,继续用户线程,缺点:产生内存碎片,使得内存不连续,当有对象申请大内存的时候,出现问题

复制算法: 将原内存一分为二,只用一半的内存,当需要垃圾回收的时候, 标记存活对象,然后将存活对象拷贝到另一半空间中去,前一半直接全部回收,缺点:无论什么时候,只有一半的内存可用,浪费,拷贝对象需要时间

标记整理算法:标记存活对象,将存活对象按照某种规则排列,清除别的对象,缺点:需要遍历标记,需要移动对象,浪费时间

分代回收:
堆分为新生代和老年代,新生代分三个模块,Enden surviver0 surviver1 8:1:1 new出来的对象放在enden区域,当发生gc,将存活的放在surviver0,第二次将存活的,surviver0中的放在surviver1中,反复如此,使用标记复制算法,当新生代的对象活过一定次数gc,就会去到老年代
新生代使用复制算法,老年代使用标记清除,标记整理算法

3、常见的垃圾收集器

1.Serial垃圾收集器(客户端模式下的虚拟机,新生代默认收集器)

单线程垃圾收集器,在进行垃圾收集的时候,会停止用户线程,当垃圾收集完毕,开始用户线程,这个是对用户不可见的,完全由虚拟机执行,单CPU效率最高的收集器,没有线程交替的开销

2.ParNew垃圾收集器(服务端模式下的虚拟机,新生代默认收集器)

Serial收集器的多线程版本,在进行垃圾收集,也会先停止用户线程,除了Serial只有这个收集器可以和CMS收集器使用,

3. Parallel Scavenge收集器
多线程并行的垃圾收集器,与ParNew类似,但是这个收集器注点在于达到一个可控制的吞吐量,吞吐量就是CPU用于运行用户代码的时间与cpu总消耗时间的比值,
4. Serial Old收集器
是serial收集器的老年版本,同样是一个单线程收集器,使用标记整理算法,是给客户端模式下的虚拟机使用,在server模式下的客户端有两大用途:一是在jdk1.5之前,与Parallel Scavenge收集器搭配使用,二就是作为CMS收集器的后备预案,在并发收集器发生Concurrent Mode Failure时使用,
也会暂停用户线程
5. CMS收集器
是一种以获取短回收停顿时间为目标的收集器,采用标记清除方式,运作过程有

其中初始标记与重新标记需要停止用户线程,初始标记只是标记一下GC Root能直接关联到的对象,速度很快,并发标记阶段就是进行GC Root Tracing的过程,而重新标记阶段则是为了修正并发标记期间因为用户程序继续运作而导致的标记产生变动的那一部分对象的记录,这个阶段停顿的时间一般会比初始标记阶段稍长一点,但远比并发标记的时间短,,由于整个过程中耗时最长的并发标记和并发清除过程,收集器线程可以和用户线程一起工作,所以,总体来看,CMS收集器的内存回收过程食欲用户线程一起并发执行的,
缺点:CMS无法处理浮动垃圾,可能出现Concurrent Mode Failure失败而导致另一次Full GC的产生,由于CMS的并发清理阶段适合用户线程一起的,那么程序运行就会有新的垃圾产生,这一部分垃圾在标记之后,所以CMS不会处理,下一次处理, 也是由于在垃圾收集阶段用户线程还需要运行,那也就是需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全填满在进行收集,需要留一部分空间提供并发收集时的程序运作使用,当在CMS运行期间预留的内存无法满足程序需要就会出现一次Concurrent Mode Failure失败,这个时候就会启动Serial Old收集器进行老年代的垃圾收集,
6. G1收集器
具有以下特点,

G1可以让使用者明确指定在一个长度为n毫秒的时间片段内,消耗在垃圾收集上的时间不能超过n毫秒,
具体过程
G1将对分为多个大小相等的独立区域(Region),虽然保存着新生代与老年代的概念,但是不是物理隔离的,G1可以预测停顿时间,是因为,在进行垃圾收集的时候,G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小与回收所需要的时间的经验值),在后台维护一个列表,每次根据允许的收集时间,优先回收价值最大的Region,保证了G1收集器在有限时间内有高效率
具体步骤:

初始标记阶段是标记一下GC root能够达到的对象,会停顿用户线程,但是时间很短,并发标记是从GC Root开始从堆中对象进行可达性分析,找出存活的对象,时间较长,但是可以和用户线程并发执行,最终标记阶段是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分,筛选回收,则是进行回收

4、内存分配与回收策略

Minor GC : 新生代的垃圾回收

Full GC:老年代的垃圾回收,也会回收新生代

1.对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配,当Eden没有足够空间分配,就是触发Minor GC

2.大对象直接进入老年代

大对象,指需要大量连续内存空间的java对象,最典型的的就是那种很长的字符串和数组

3.长期存活的对象进入老年代

在Eden区的对象经过一次Minor GC,会被移动到Survivor区,年龄加一,当存活过一定时间,就会进入老年区

4.动态对象年龄判定

当survivor中相同年龄所有对象大小的总和大于survivor空间的一般,年龄大于等于改年龄的对象可以直接进入老年代,不需要达到指定年龄

5.空间分配担保

在进行Minor GC之前,虚拟机会检查老年代的最大可用的连续空间是否大于新生区所有对象的总空间,如果这个条件成立,那么这次Minor GC是安全的,如果不成立,虚拟机会查看HandlerPromotionFailure设置值是否允许担保失败,如果允许那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,就尝试着进行一次Minor GC,尽管这次Minor GC是有风险的,如果小于,或者不允许冒险,那么就进行Full GC

更多内容请看后续
QQ群:552113611

上一篇下一篇

猜你喜欢

热点阅读