文章目录
前言
上一次谈到JVM虚拟机中内存是如何划分的,以及什么情况下会造成这些区域发生内存溢出的异常。学习Java的大伙都知道,Java为避免内存溢出的情况发生,有良好的垃圾自动回收机制,那本章就来谈一谈Java的垃圾回收。
一、什么算是垃圾?
没有任何引用指向的一个对象称之为一个垃圾。
没有任何引用指向的多个对象(循环引用)称为一堆垃圾。
二、怎么判断垃圾是否是垃圾?(如何定位)
1.引用计数法
给对象添加一个引用计数器,当有一个地方引用他时,引用计数器加1;当引用失效时,引用计数器减1。当在任何时刻,引用计数器的值为0的
对象就是不可能再被使用的对象。
缺陷:这种方式定位不到循环引用的情况,也就是对象之间相互引用。
2.根可达算法
从GC Root的对象作为起点,一直向下搜索,所走过的路径称为引用链,
当一个对象到GC Root没有任何一条引用链相连时,则证明这个对象是
不可用的。
在Java中,GC Root有以下几种。
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。(静态变量)
- 方法区中常量引用的对象。(常量池)
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
三、垃圾回收算法
1.标记-清除算法(Mark-Sweep)
- 首先“标记”出所有需要回收的对象。
- 随后对标记的对象进行清除。
缺陷:第一个缺陷是性能问题,标记和清除这两个过程的效率都不高,其次是这样标记-清除之后,会产生大量不连续的空间碎片,太多的空间碎片,会导致在需要为一个大的对象分配一片空间是,因为内存空间不足,提前触发下一次GC。如下图。
2.复制算法(Copying)
将内存分为大小相等的两个区域(A和B),每次只使用一个区域,如A区域的空间用完了,就将A区域中所有还存活的对象全部复制到B区域中,然后整体清除A区域,这样的做法很高效。
缺陷:将内存直接砍为一半,太狠了,浪费空间。如下图。
3.标记-压缩算法(Mark-Compact)
标记过程与标记-清除算法一致,但是在后续清除操作时,没有直接操作,而是将所有存活的对象全部移向一端,然后直接清理掉端边界意外的内存。没有碎片产生。
缺陷:过程效率偏低。如下图。
四、堆内存逻辑分区
从分代GC的角度看,堆分为新生代和老年代
1. 新生代
- 新生代分为Eden + 2个survivor区
- 当无法在新生代为对象分配空间时,会产生YGC,YGC使用的算法
是上边提到的复制算法(Copying),复制算法需要额外的空间,所
以除了Eden区,还有survivor区,一般Eden和survivor的空间比例
8:1:1。
垃圾回收流程
*s0是第一个survivor区,s1是第二个survivor区。
- 第一次YGC:大多数对象都会被回收,Eden -> s0
- 第二次YGC:Eden + s0 -> s1
- 第三次YGC:Eden + s1 -> s0
- 当对象在新生代年龄(被复制的次数)足够大,则进入老年代。
追加一些知识。
- 用 -XX:MaxTenuringThreshold可以设置年龄阈值。
- Parallel Scavenge垃圾回收器进入老年代年龄默认为15、CMS回收器为6、G1回收器为15。
- 动态年龄判定:Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值
- 内存分配担保:YGC时,JVM会首先检查老年代最大的可用连续空间,是否大于新生代所有对象的总和,如果大于,那么这次YGC是安全的,如果不大于的话,JVM就需要判HandlePromotionFailure是否允许空间分配担保。
1.允许空间分配担保:JVM继续检查老年代最大的可用连续空间是否大于历次晋升到老年代的所有对象总的平均大小,如果大于表示此处YGC相对安全,正常进行一次YGC,如果小于,进行FGC。
2.不允许空间分配担保:FGC
2. 老年代
- 进入老年代的对象,除非要垃圾回收,不然它会终身存在。
- 在老年代区域会产生FGC(Full GC),使整个内存都会回收,效率比较低,会产生STW(Stop-The-World),所有线程暂停,进行垃圾回收。
- 老年代中因为对象存活率高、没有额外空间 对它进行分配担保,就必须使用“标记—清除”或者“标记—压缩”算法来进行回收。
在GC调优中,很重要的一个目的就是为了减少FGC。
3. 设定参数
- -Xmn 指定新生代内存大小
- -Xms:指定堆内存最小值
- -Xmx:指定堆内存最大值
总结
本章了解了垃圾回收的一些基本知识,认识了三种垃圾回收算法,下一篇文章重点总结10种垃圾回收器的知识。