一、为什么关注GC
当需要排查内存溢出、内存泄漏,垃圾收集成为系统高并发的瓶颈 需要对自动的GC实施监控和调节
二、回收哪些对象
-GC只需要关注Java堆和方法区的已死对象 -调用GC回收System.gc()
如何确定对象已经死去?
1.引用计数法
给对象添加引用计数器。 缺点:难解决循环引用问题
2.可达性分析法 (Java虚拟机采用的方法)
- 有一系列GC rootl,包括虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中Native方法引用的对象。分为全局性引用(类静态变量、常量)和执行上下文(本地变量表)。
- 如果当前对象与GC root没有路径(图论),对象不可用
三、引用与死亡标记
JDK1.2之前,引用状态有两种:引用和没有引用。如果reference类型的数据,存储了另外一块内存地址,则代表一个引用。
JDK1.2之后,引用分为4种:强引用、软引用、弱引用、虚引用。
- 强引用 Object obj = new Object()
- 软引用 在要发生内存溢出之前,先回收一次,如果不够再抛出异常
- 弱引用 垃圾回收器会直接回收弱引用
- 虚引用 不影响生存时间,无法获得他的对象实例,被回收时收到一个系统通知
死亡之前经历两次标记,才真正死亡
对象的finalize只能调用一次),则不需要执行gc了
第二次标记:把对象放置在F-Queue队列中,并且由finalizer线程执行,在执行过程中进行第二次标记。执行finalize时候可以自我拯救,赋值给一个成员变量,第二次标记会被移出”即将回收“的集合。
回收方法区:主要回收废弃常量和无用的类
无用的类”可以“回收,不是必然,但是需要提供参数支持
什么是无用的类:
- Java堆不存在该类的实例
- 加载该类的ClassLoader被回收
- 该类的Class对象已经没有被引用,无法通过反射访问该类的方法
五、hotpod算法实现
- 遍历所有GC Roots(包含了常量、类静态属性、栈帧中的本地变量表),这个过程是很耗时的,另外进行GC必须停顿所有的执行线程(Stop the world)。现在的主流虚拟机使用准确式GC,不需要完全扫描。使用一个OopMap结构,可以知道某个对象的某个偏移量是什么类型的数据。
- 安全点。只有在安全点线程才能暂停执行GC。
六、垃圾收集器
- 新生代:采用复制算法
- Serial
- 单线程:只使用一个收集线程完成GC
- Client模式下的新生代收集器
- Parnew
- Serial的多线程版本
- Server模式下新生代收集器
- Parallel Scanvenge:目的是达到一个可控制的吞吐量(程序运行之间占总时间的比例,总时间包括GC时间)
- 使用参数- XX:MaxGCPauseMillis -XX:GCTimeRatio -XX:UseAdaptiveSizePolicy -XX:PretenureSizeThreshold
- 老年代:采用标记清除/标记整理算法
- Serial Old
- 单线程,Client模式下用
- 标记整理算法
- 与Parallel Scaevnge搭配使用,也可以作为CMS的后备
- Parallel Old:吞吐量优先,注重吞吐量和CPU资源
- Parallel Scavenge的老年代版本
- 使用标记-整理算法
- 与Parallel Scavenge配合使用
- CMS(concurrent mark sweep)收集器:时间优先,尽可能缩短stop the world时间
- 可以与Serial、ParNew配合使用
- 步骤:初始标记、并发标记、重新标记、并发清除
- Cncurrent Mode Failure:无法处理浮动垃圾(并发清除阶段,用户线程产生的垃圾),临时启用Serial Old。
-
- XX:CMSInitiatingOccupancyFraction太高导致大量Concurrent Mode Failure
- G1收集器:替换CMS
- 缩短Stop the world时间
- 分代收集
- 空间整合:局部看是复制算法、整体看是标记-整理算法
- 可以人为指定最长停顿时间
- 将Java堆划分为多个大小相等的Region,新生代和老生代不再隔离,每个Region有一个价值大小,G1每次回收价值最大的Region
七、内存分配与回收策略
- 对象优先在eden分配
- 大对象直接进入老年代
- 长期存活的对象进入老年代
- 对象年龄会动态增长
- 空间分配担保:大量对象在MinorGC后存活的情况,需要老年代担保。
|