写在前面的话
本章主要进行的是垃圾回收器与分配策略,之前一直听说的young GC和full GC这些问题迎刃而解,挺好的一章,学到不少
垃圾收集器与内存分配策略
概述
随线程而生而灭
- 程序计数器
- 虚拟机栈
- 本地方法栈
垃圾回收
- 堆
对象生存判断
引用计数法
方法
- 给对象添加计数器
- 每当有引用则+1,释放则-1
问题
不能解决循环引用
可达性分析算法
方法
- 设置GC ROOTS作为起始点
- 从这些节点向下的路径被称为引用链
- 当一个对象到GC ROOTS没有任何引用链接,则对象不可用
GC ROOTS
- 虚拟机栈
- 方法区中类静态属性引用对象
- 方法区中常量引用对象
- 本地方法栈中引用的对象
再谈引用
JAVA对引用进行了扩充
- 强引用:代码普遍存在,类似
Object obj = new Object()
,只要强引用存在,则永远不会GC - 软引用:描述一些还有用但并非必需的对象,
SoftReference类
;系统将要发生内存溢出异常之前,会把这些对象列近回收范围之中进行第二次回收 - 弱引用:描述非必须对象,
WeakReference
;只能生存到下一次垃圾收集发生之前 - 虚引用:最弱的引用关系,是否由虚引用完全不会对生存时间造成印象,也无法通过虚引用来取得一个对象实例。
PhantomReference
生存还是死亡
两阶段死亡
- 对象在可达性分析中无GC ROOT关联,被第一次标记
- 当finalize()有必要执行,则将对象放入到F-Queue的队列中
- GC对F-Queue队列进行标记,只要对象能构建起与GC ROOT的关联,就会被拯救
P.S. finalize只会被执行一次,只能被拯救一次
回收方法区
主要回收两部分
- 废弃常量:与回收无用的对象很类似
- 无用的类:
- 该类所有实例都已经被回收
- 加载该类的ClassLoader已经被回收
- 该类对应的Java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法
垃圾回收算法
标记-清除算法
方法
- 标记:标记出所有需要回收的对象
- 清除:统一回收所有被标记的对象
优劣
- 效率:标记和清除的效率都不高
- 空间:会产生大量不连续的内存碎片
复制算法
在标记清除的基础之上:
- 将内存划分为大小相等的两块
- 当一块使用完,就将另一块复制过去,再做清除
优劣
- 实现简单,运行高效
- 内存会缩小为原来的一半
现在商业化虚拟机时用这种算法来回收新生代
标记-整理算法
流程与标记清除一样,但是后续操作是让所有存活对象都向一端移动,然后清理掉端边界以外的内存
分代收集算法
根据对象存活的周期将内存划分为新生代和老年代,不同的代使用最适当的收集算法
- 新生代:每次GC都会有大批对象死去,选用复制算法
- 老年代:对象存活率高,必须使用标记-清理或者标记-整理
HotSpot算法实现
枚举根节点
- GC停顿:这项分析工作必须在一个能确保一致性的快照中进行(不允许出现分析过程中对象引用关系还在变化,STOP-THE-WORLD)
- 准确式GC:通过OopMap的数据结构知道哪些地方存放着对象引用
安全点
- 只在特定的位置才会进行OopMap,被称为安全点,以程序是否具有让程序长时间执行的特征
- 让所有线程都“跑”到最近的安全点上再停顿下来
- 抢先式中断:先把所有线程中断,如发现不在安全点上,则唤醒线程让之执行到安全点上
- 主动式中断:简单设置标志,所有线程执行时主动轮询标志,发现中断为真则自己中断线程
安全区域
解决程序不执行时的GC问题(Sleep或者Block)
- 在一段代码片段中,引用关系不会发生变化,在这个区域的任何地方开始GC都是安全的
当线程执行到时,先标记自己进入Safe Region
;离开安全区域前需要检查系统是否完成了GC,否则要等待或者收到可以离开信号
垃圾收集器
理解GC日志
1 | 33.125:[GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs] |
- 33.125: 代表GC发生的时间,JAVA虚拟机启动以来经过的秒数
- [GC / Full GC:有Full代表发生了Stop-The-World
- [DefNew:代表GC发生的区域
- 3324k-152k(3712k):GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)
- 3324K->152k(11904k):GC前JAVA堆已使用容量->GC后JAVA堆已使用容量(JAVA堆总容量)
- 0.0025925 secs 该区域内存GC时间
内存分配与回收策略
对象有限在Eden分配
大多数情况下,对象将在Eden区进行分配,如果空间不过,则会进行一次Young gc
大对象直接进入老年代
虚拟机提供了一个 -XX:PretenureSizeThreshold参数,大于这个的对象则会进入老年代
长期存活对象进入老年代
通过-XX:MaxTenuringThreshold来设置,经历过一次young GC存活并且能被Survivor容纳,就会yi’dao移到Survivor空间,并且年龄+1
动态对象年龄判定
如果在Survivor空间中相同年龄的所有对象大小的综合大于Survivor空间的一半,则年龄大于或等于该年龄对象就可以直接进入老年代
空间分配担保
- 老年代最大可用连续空间是否大于新生代所有对象总空间
- 大于则young GC是安全的
- 小于则查看HandlePromotionFailure设置值是否允许失败
- 如果允许,则查看最大连续空间是否大于晋升平均大小
- 如果大于,ze则尝试young GC
- 否则,进行Full GC