第3章 垃圾收集器与内存分配策略
3.1 概述
略
3.2 对象已死?
“死去”即不可能以任何途径访问到
3.2.1 引用计数算法
每个对象维护一个计数器,引用即加1,引用失效便减1。
3.2.2 可达性分析算法(主流)
即根据GC Roots是否可由引用链到达
-Java GC Roots:
1>虚拟机栈的栈帧中的变量表、参数、局部及临时变量
2>方法区静态引用对象
3>方法区常量引用对象(String常量池,static修饰的对象)
4>本地方法栈所引用的对象
5>虚拟机内部引用:基本数据类型、class对象、常驻异常对象、系统类加载器等。
6>被同步锁持有的对象
7>本地代码缓存等
3.2.3 再谈引用
1.强引用:传统赋值引用,只要强引用还在,GC便不会回收被强引用的对象。
2.软引用:还有用、非必须。只被软引用的对象在OOM之前会对软引用对象做回收,若回收之后还没有足够的内存才会出现OOM异常。
3.弱引用:只被弱引用的对象,在GC时一定会回收。
4.虚引用:无引用关系,只会在GC回收时通知。
3.2.4 生存还是死亡
第一次标记为不可达之后,会执行对象Finalize方法,之后进行第二次不可达标记(针对于执行finalize的对象),若在finalize中重新加上了对this的引用,则不会被回收,即两次标记才会被回收。
【finalize只会被调用一次,且不建议使用】
3.2.5 回收方法区
常量池部分不使用了会自动回收。
-类型卸载的许可条件:
1>class所有的实例已被回收
2>类加载器已被回收(很苛刻,一般只有JSP等才会符合)
3>java.lang.class未被引用
【1.以上类是否允许回收可以通过–Xnoclassgc参数控制
2.在大量使用了JSP或者反射的项目中,通常需要类卸载的能力】
3.3 垃圾收集算法
主流的垃圾收集算法为追踪式垃圾收集
3.3.1 分代收集理论
1>大部分对象朝生夕死
2>越没被回收,越难以磨灭
3>跨代引用只占少数
3.3.2 标记-清除算法
先标记(可达性算法),再清除
3.3.3 标记-复制算法
新生代中:新对象分配入end区。yong GC时存活的对象放入S0,下一次yong GC会把S0和eden存活的对象放入S1。
【大小比值-> eden:S0 = 8:1】
3.3.4 标记-整理算法
标记后,移动存活对象到相邻的区域
3.4 HotSpot的算法细节实现
3.4.1 根节点枚举
在类加载与即时编译时便会把记录在Map中,实际GC时可以不必完全依靠遍历方法区
3.4.2 安全点
导致OopMap或者说对象引用关系变化的操作非常多,虚拟机会在例如方法调用、循环跳转、异常跳转等可能长时间执行的命令执行时产生安全点,在这些安全点可以执行GC操作,而用户线程通过轮询GC标志位来知晓应该在下一个安全点停下。
3.4.3 安全区域
线程将引用不变的一段区域设定为安全区域,所以GC线程不必等待某些sleep与blook线程进入安全点。
3.4.4 记忆集与卡表
1>好处:为了避免每次GC时都将老年代整个加入扫描范围,知道某一块儿非收集区域有指向一块需要收集区域的指针便可以将该非收集区域的对象加入GCRoots中一并扫描。
2>实现:HotSpot用一个数组来映射JVM中收集区域的内存地址。用数组中的一个元素的0与1表示映射内存地址是否存在脏元素(跨带引用)。
3.4.5 写屏障
写屏障可以在引用赋值前后进行操作,相当于引用赋值的AOP切面,这边在引用赋值时利用写屏障更新卡表中对应页的跨带引用情况。
3.4.6 并发的可达性分析
对象标记:
白色(未标记/不可达)
灰色(正进行引用可达性分析)
黑色(引用可达性分析完毕,可达)
在并发标记时,使用以下两种方法规避标记时的引用变化。
1>在黑色对象插入插入对白色对象的引用后,它变为灰色对象
2>无论引用关系删除与否,都会按照刚开始扫描时那一刻的对象图快照来进行搜索
3.5 经典垃圾收集器
(收集器关系图P90)
【并发:物理上同时;并行:逻辑上同时】
3.5.1 Setial收集器
最基础历史最悠久,单线程收集,stop用户线程的新生代收集器。
优点:简单而高效(redis理念),占用内存少,单核。
适用:客户端等使用内存较少的场景。
3.5.2 ParNew收集器
并行多线程版本的Serial新生代收集器
优点:多核CPU适应
适用:与CMS old收集器协同适用
3.5.3 Parallel Scarenge收集器
并行多线程:新生代收集器
优点:可通过JVM参数设置GC停顿时间、GC吞吐量(GC与用户代码运行时间的比值)
适用:对吞吐量有要求的情况(一般为处理器资源有限的计算程序)
3.5.4 Serial old 收集器
单线程老年代收集器
优点:简单高效,适合客户端
适用:客户端内存小,CPU单核长假
3.5.5 Parallel old 收集器
吞吐量、并发 old收集器
优点:同 Parallel Scarenge Young
适用:同上,并与Parallel Scarenge Young协同适用以获取最优吞吐量。
【响应时间与吞吐量不可兼得,除非无限制提升硬件】
3.5.6 CMS收集器
低响应延迟的CMS old收集器
优点:通过并行标记将大部分可达性标记与用户代码同时进行,而不stop the world来通过降低整体计算效率以提高响应速度,且用标记-清除法,可能留下大量碎片而不得不发生full GC
适用:低响应延迟的服务交互场景
3.5.7 Garbage First 收集器
化整为零,分区回收的G1
优点:大部分标记工作可以与用户线程并行,将堆分区为多个region,每个region可以是eden,s1,s0,或者是老年代,对每个region单独回收,通过参数指定期望的GC停顿时间,在回收时以region为单位,优先回收垃圾多的region
适用:需短暂停顿的服务端应用
【G1在8GB以上的内存消耗下一般表现比CMS好,通过使用更多的cpu与内存资源做分区卡表等额外操作来获得更低的延迟效率】
3.6 低延迟垃圾收集器
低延迟实现的关键在于回收内存时是并发的。主要通过对象的转发指针二次访问实现。
3.5.2 ZGC收集器
通过对象指针的高四位表示对象状态,略。
3.7 选择合适的垃圾收集器
3.7.1 Epsilon收集器
特点:只做堆等内存分配,不回收
适用:测试、运行时间短等不必GC的场景
3.7.2 收集器的权衡
老JDK、4GB-6GB一般使用CMS,更大则使用G1
【要实验,不可以纸上谈兵,P122】
3.7.3 虚拟机及垃圾收集器日志
日志级别:Trace、Debug、Info、Warning、Error、Off
日志标签:gc
附加信息:P123
3.7.4 垃圾收集器参数总结
p128~P129
3.8 实战:内存分配与回收策略
自动化内存管理:
1>自动给对象分配内存
2>自动回收分配给对象的内存
3.8.1 对象优先在Eden分配
3.8.2 大对象直接进入老年代
可以使用JVM参数指定大对象具体多大
3.8.3 长期存活的对象将进入老年代
经过一次young GC,对象增加一岁,默认15岁后进入老年代
3.8.4 动态对象年龄判定
如果幸存者(s1,s0)中大多数对象处于同年龄,那么大于等于该年龄的对象直接进入老年代
3.8.5 空间分配担保
当老年代可用空间小于新生代时,可通过参数指定,当老年代剩余空间大于youngGC存活平均大小时,尝试先发起youngGC,空间不足再发起full GC(一般可以开启)