垃圾收集器与内存分配策略
垃圾收集,三个步骤
什么时候收集,收集那些,怎么收集
1、收集那些
我们会将一些不使用的对象进行收集,进行回收内存空间,我们怎么知道呢
1、引用计数法
如果这个实例被其他地方引用,那么计数器加一,如果解除引用那么就减一,当计数器为0说明没有地方使用,即可回收,但是缺点就是如果两个对象互相引用,但是后续又用不到,那么就不会被回收,内存浪费
2、可达性分析
通过GC Root向下寻找,形成一条引用链,如果对象不再这条链上,那么说明该对象不可达,可回收,
什么是GC Root
虚拟机栈中引用的对象
方法区中静态属性引用的对象
方法区常亮引用的对象
本地方法栈中引用的对象
3、引用
我们不论是使用引用计数法,还是使用可达性分析,都有引用这个词,什么意思呢
jdk1.2之前的引用,是reference类型的数据存储的是另一块内存的起始地址,那么就称这块内存代表着一个引用,这种情况下,一个对象只有被引用和没有被引用两种状态,
我们希望有一些对象,在内存空间足够的时候,能够保留在内存中,如果内存空间在进行垃圾收集之后还是很紧张,那么就抛弃这些对象,所以出现 强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference),虚引用(Phantom Reference),这4中引用强度依次减弱
强引用
强引用就是指程序代码中普遍存在的 Object obj = new Object()这类引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象,使用StrongReference实现软引用
jdk1.2之后出现软引用用来描述一些有用,但不是必须的对象,软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列入可回收范围中进行第二次回收,如果这次回收还不能解决内存问题,那么抛出内存溢出错误,,使用SoftReference实现弱引用
弱引用也是用来描述非必需的对象,被弱引用关联的对象,只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时候,无论当前内存是否足够,都会回收掉,使用WeakReference类实现虚引用虚引用
最弱的引用,一个对象是否有徐勇勇的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,为一个对象设置虚引用关联的唯一目的,就是能在这个对象被收集器回收时收到一个系统通知,jdk1.2之后通过PhantomReference类来实现虚引用,
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收集器
具有以下特点,
并行与并发
充分利用多核多CPU,缩短Stop The World的时间,G1收集器通过并发的方式让java程序继续执行分代收集
与其他收集器一样,分代概念也在G1中保存,G1不需要通过其他收集器配合就可以管理真个堆,但是它可以通过不同的方式处理,空间整合
G1从整体来看是基于标记整理算法实现的,从局部来看是基于复制算法实现的,不会产生内存碎片,收集后能提供规整的可用内存,不会因为分配大对象找不到连续内存空间触发GC可预测的停顿
G1可以让使用者明确指定在一个长度为n毫秒的时间片段内,消耗在垃圾收集上的时间不能超过n毫秒,
作者:z七夜
链接:https://www.jianshu.com/p/e4dd7f5329a1