概述
垃圾回收(Garbage Collection)简称GC,早在Lisp还在胚胎时期时,其作者John McCarthy就思考过垃圾回收需要完成的三件事情:
哪些内存需要回收
什么时候回收
如何回收
今天的内存动态分配与内存回收机制已经非常成熟了,都进入了“自动化时代”,那我们为什么还要学习垃圾回收和内存分配呢?
当我们需要检查各种内存溢出,内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术进行监控和调节
在对象中添加一个引用计数器,每当一个地方引用它时,计数器值就加一;当引用失效时,计数器的值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
优点:原理简单,判定效率高
缺点:需要占用一定额外的内存空间,需要考虑大量的额外情况,在java虚拟机中没有采用这种方式来判断对象是否能够回收
通过“GC Roots”的根对象作为起始节点集,从这些节点根据关系引用开始往下搜索,搜索过程走过的路径称为“引用链”,如果摸个对象到GC Roots间没有任何引用链相连,,或者说不可达时,则证明此对象是可以被回收的
在Java体系内,固定可以作为GC Roots的对象包括:
1.在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
2.在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。 ·
3.在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
4.在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
5.Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 :NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
6.所有被同步锁(synchronized关键字)持有的对象。
7.反映Java虚拟机内部情况的JM XBean、JVM TI中注册的回调、本地代码缓存等。
- 强引用
只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收- 软引用( SoftReference )
仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用
对象
可以配合引用队列来释放软引用自身- 弱引用( WeakReference )
仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
可以配合引用队列来释放弱引用自身- 虚引用( PhantomReference )
必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,
由 Reference Handler 线程调用虚引用相关方法释放直接内存- 终结器引用( FinalReference )
无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象
暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize
方法,第二次 GC 时才能回收被引用对象
算法分为标记和清除两个阶段:
首先标记出所有需要进行回收的对象,在标记完成后,统一回收掉所有被标记的对象
**优点:**速度快
**缺点:**执行效率不稳定;容易造成内存碎片
先进行标记,让所有存活的对象向内存的一端移动,然后直接清理掉边界以外的内存
**优点:**不会产生内存碎片
**缺点:**由于涉及到了存活对象的移动,这种移动需要全程暂停应用程序才可以进行,这种停顿被作者称为“Stop The World”
**优点:**不会有内存碎片
**缺点:**需要占用双倍内存空间
对象首先分配在伊甸园区域 新生代空间不足时,触发 minor gc ,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的 对象年龄加 1 并且交换 from to
minor gc 会引发 stop the world ,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
当对象寿命超过阈值时,会晋升至老年代,最大寿命是 15 ( 4bit )
当老年代空间不足,会先尝试触发 minor gc ,如果之后空间仍不足,那么触发 full gc , STW 的时 间更长
概述
七款经典的垃圾收集器
串行收集器:Serial,Serial old
并行收集器:PawNew,Parallel Scavenge,Parallel Old
并发收集器:CMS,G1
新生代收集器:Serial,PawNew,Parallel Scavenge
老年代收集器:Serial old,Parallel Old,CMS
整堆收集器:G1
需要明确的是:虽然我们会对回收器进行比较,但并非为了挑选出一个最好的回收器,虽然垃圾收集器的技术在不断进步,但是还是没有出现最好的收集器,所以我们只是针对具体的场景应用最合适的收集器。
垃圾收集器的组合关系
Serial收集器是最基础,历史最悠久的收集器,是一个单线程工作的收集器,这里的单线程不仅仅强调它只会使用一个处理器或者一条线程去完成垃圾回收的工作,更意味着它在进行垃圾收集的时候。必须暂停其他所有工作的线程才可以进行,称为“Stop The World”,对于交互较强的应用来说这种停顿是不可接收的
一个有趣的例子:“妈妈在打扫房间的时候肯定会让你老老实实呆着,如果她一边打扫你一边乱扔,这房间打扫的完?”是一个合情合理的矛盾
优点:简单而高效,运行在client模式下的虚拟机是个不错的选择
PawNew GC是Serial GC的多线程版本
PawNew收集器除了是采用并行收回的方式执行内存回收之外,其他与Serial GC几乎没有任何区别,都是采用了复制算法和“Stop The World”机制
PawNew是很多JVM运行在server模式下新生代默认的垃圾收集器
对于新生代,回收频率高,采用并行的方式高效
对于老年代,回收频率低,采用串行方式节省资源(省去切换线程的资源)
Parallel ScavengeGC与PawNew GC不同,Parallel Scavenge收集器的目标是达到一个可控制的 吞吐量(Throughput),它也被称为吞吐量优先的一个垃圾收集器
自适应调节策略也是Parallel ScavengeGC与PawNew GC的一个重要区别
高吞吐量则可以高效的利用CPU的时间,尽快的完成计算任务,主要适合在后台运算而不需要太多交互的任务。例如,那些批量处理,订单处理,工资支付等
Parallel收集器在jdk6提供了用于执行老年代垃圾收集器Paralle Old,用来代替Serial Old
Paralle Old收集器采用了标记-压缩算法 但也还是基于并行回收和“Stop The World”机制
在吞吐量优先的场景下,Parallel+Parallel Old收集器的组合,在server模式下内存回收性能很不错
在java8中,默认是此垃圾回收器
CMS收集器:HotSpot虚拟机第一个真正意义上的并发收集器,第一次实现了让垃圾收集线程和用户线程同时工作
注意:JDK9之后使用CMS垃圾收集器后,默认年轻代就为ParNew收集器,并且不可更改,同时JDK9之后被标记为不推荐使用,JDK14就被删除了。
CMS收集器收集器的关注点就是尽可能缩短垃圾收集时用户进程停顿的时间 停顿时间越短越适合与用户交互的程序,良好的响应速度能够提高用户的体验
CMS收集器采用标记-清除算法,并且也会“Stop The World”
CMS作为老年代的收集器,新声代只能选择PawNew和Serial,而不能和Parallel Scavenge组合使用
CMS工作过程
初始标记阶段:在这个阶段中,程序中所有的进程都会因为“Stop The World”机制而出现短暂的暂停,这个阶段的任务仅仅是标记和“GC Roots”能直接关联到的对象,由于关联对象较小,所以这里速度较快
并发标记阶段:从“GC Roots”的直接关联对象开始遍历整个对象图的过程,这个比较耗时但不需要停顿用户线程,可以和其他垃圾收集线程一起并发运行
重新标记阶段:由于并发标记阶段,程序的工作线程会和垃圾回收线程交叉运行,因为为了修正并发标记期间,因用户程序继续运作而导致标记产生变化的那一部分对象的标记记录
并发清除阶段:此阶段删除掉那些在标记阶段判断已经死亡的对象,释放内存空间
那既然标记-清除算法会产生内存碎片,为什么不采用标记-整理算法呢?
因为并发清除时,用Compact整理内存的时候,原来用户线程的内存还怎么使用?要保证用户进程能够使用,前提是他运行的资源不受影响Mark-Conpact更适合“Stop The World”下使用
优点:并发收集,低延迟
缺点:
会导致产生内存碎片,用户线程可用空间不足,提前触发full gc
CMS收集器对CPU资源十分敏感
CMS收集器无法处理浮动垃圾
HotSpot有这么多的收集器,那么Serial GC,Parallel GC,CMS GC这三者有什么区别呢?
如果需要最小化的使用内存和并行开销:Serial GC
如果需要最大化应用程序的吞吐量:Parallel GC
如果需要最小化GC的响应时间:CMS GC
G1(Garbage-First)是一款面向服务端应用的垃圾收集器,主要针对配备多核CPU以及大容量内存的机器,以极高概率满足GC停顿的同时,还兼具高吞吐量的性能特征
是jdk9以后默认的垃圾回收器,取代了CMS,以及Parallel +Parallel Old的组合,被Oracle官方称为“全功能的垃圾收集器”
G1收集器的特点:
并行性:G1在回收期间,可以支持多个GC线程同时工作,有效利用多核计算能力。此时用户线程STW
并发性:G1用于与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此一般来说,不会整个回收阶段发生完全阻塞应用程序的情况
分代收集
从分代上来看,G1仍然属于分代型垃圾回收器,它会区分年轻代和老年代,年轻代仍然有Ender区和survivor区,但从堆的结构上来看,它不要求整个Ender区,年轻代,老年代都是连续的,也不坚持固定大小和数量
将堆空间分为了若干个区域,这些区域中包含了逻辑上的年轻代和老年代·
和之前的各类回收器不同,它兼顾年轻代和老年代
垃圾回收器总结