- Java 虚拟机是自动内存管理,将原本需要由开发人员手动回收的内存,交给垃圾回收器来自动回收。不过既然是自动机制,肯定没法做到像手动回收那般精准高效
1 判断对象死亡
- 垃圾回收将已经分配出去的,但却不再使用的内存回收回来,以便能够再次分配
1.1 引用计数算法
- 定义:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的
- 主流的Java虚拟机里面都没有选用引用计数算法来管理内存
- 原因:必须要配合大量额外处理才能保证正确地工作,如单纯的引用计数就很难解决对象之间相互循环引用的问题
1.2 可达性分析算法
- 基本思路 "GC Roots"的跟对象作为起始节点集,根据引用关系向下搜索,搜索路径称为"引用链",如果对象不可达,则对象不可能再被使用的。
- GC Roots的对象包括以下几种
- 在方法区中类静态属性引用的对象,如Java类的引用类型静态变量
- 在方法区中常量引用的对象,如字符串常量池(String Table)里的引用
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器
- 所有被同步锁(synchronized关键字)持有的对象。
- 已启动且未停止的 Java 线程。
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
- java方法栈帧中的局部变量
- 除了这些固定的"GC Roots"集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合
1.3 引用
- 场景 当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象——很多系统的缓存功能都符合这样的应用场景
- 引用类型
- 强引用
- 指在程序代码之中的引用赋值 如 Object obj=new Object()
- 垃圾收集器永远不会回收掉被引用的对象
- 软引用/SoftReference
- 指一些还有用,但非必须的对象
- 垃圾收集器在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收
- 弱引用/WeakReference
- 指非必须对象,但是它的强度比软引用更弱一些
- 无论当前内存是否足够,垃圾收集器都会回收掉被弱引用关联的对象
- 虚引用/PhantomReference
- 为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知
- 无法通过虚引用来取得一个对象实例
- 强引用
1.4 回收方法区
- 在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力
- 收集性价比低,主要回收两部分内容:废弃的常量和不再使用的类型
-
被允许回收条件
- 回收废弃常量
- 没有任何字符串对象引用常量中的某个常量
- 且虚拟机中也没有其他地方引用这个字面量
- 不再使用的类型
- 该类所有的实例都已经被回收
- 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
- 回收废弃常量
- JVM参数
- -verbose:class 跟踪类加载和卸载 下面两个参数与它等价
- -XX:+TraceClassLoading 跟踪类加载
- -XX:+TraceClassUnloading 跟踪类卸载
2 垃圾收集算法
- 对象 28定律80%的对象朝生夕死,20%的对象越活越久。这一特点奠定了垃圾收集器的一致的设计原则
- 收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄分配到不同的区域之中存储。
- 问题:对象不是孤立的,对象之间会存在跨代引用如进行Minor GC,新生代中的对象对能被老年代引用,如何找出该区域的存活对象?
- 跨代引用假说:跨代引用相对于同代引用来说仅占极少数
- 实现 Remembered Set 新生代记录哪块卡表有跨代引用
- 卡表(Card Table) 将整个堆划分为一个个大小为 512 字节的卡,并且维护一个卡表,用来存储每张卡的一个标识位。这个标识位代表对应的卡是否可能存有指向新生代对象的引用
- 这样Minor GC时,老年代中包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描
- 跨代引用假说:跨代引用相对于同代引用来说仅占极少数
- GC 术语
- 部分收集(Partial GC):目标不是完整收集整个Java堆的垃圾收集
- 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集
- 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为,在不同资料上常有不同所指,有时需按上下文区分到底是指老年代的收集还是整堆收集
- 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为
- 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集
- 部分收集(Partial GC):目标不是完整收集整个Java堆的垃圾收集
2.1 标记-清除算法
- 首先标记出需要 回收的对象/存活的对象,标记完成后一回收掉所有被标记的 对象/未被标记对象。后续算法,都是以标记-清除算法为基础
- 缺点
- 执行效率不稳定,如果大量对象都被标记,清除导致标记清楚两个过程的执行效率随着对象数量变化。
- 内存空间的碎片化问题,标记-清除后产生大量不连续内存碎片。当分配大对象没有连续内存,又得触发垃圾收集
2.2 标记-复制算法
- 目的:为了解决标记-清除算法面对大量可回收对象时执行效率低的问题
- 将可用内存容量划分大小相等两块,每次只使用其中的一块。当这一块的内存用完了,将存活的对象复制另一块上面,然后再把已使用的内存空间一次清理掉。
- 缺点
- 将可用内存缩小为了原来的一半
- 虚拟机大多采用这种收集算法回收新生代。
2.3 标记-整理算法
- 首先标记出需要 回收的对象/存活的对象,让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内
- 老年代大多使用此算法
3 经典垃圾收集器
- HotSpot虚拟机的垃圾收集器 image.png
- 标记-复制
- Serial,Parallel Scavenge,Parallel New
- 标记-整理
- Serial Old,Parallel Old,G1
- 标记-清除
- CMS
3.1 新生代收集器
-
Serial 单线程收集器
- 优点:简单高效
- 缺点:需要停顿
- 算法:标记-复制
- 场景: 虚拟机client模式下默认的新生代收集器
-
parNew Serial 的多线程版本
- 优点:简单高效
- 缺点:需要停顿
- 算法:标记-复制
- 场景: 虚拟机Server模式下默认的新生代收集器,唯一可以配合CMS收集器工作的垃圾收集器。
-
Parallel Scavenge多线程处理器,吞吐量优先
- 相关信息
- 吞吐量指的是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量 = 运行用户代码时间 / (运行用户代码的时间 + 垃圾收集时间 )
- -XX:MaxGCPauseMilis 用来控制最大垃圾收集停顿时间
- -XX:GCTimeRatio 用来控制 GC时间占总时间的比率
- -XX:UseAdaptiveSizePolicy用来进行GC自适应调节策略
- 优点:能够控制垃圾收集时间的吞吐量
- 缺点:效率相对较低
- 算法:标记-复制
- 场景: 适合后台运算而不需要太多交互的任务。
- 相关信息
3.1 老年代收集器
-
Serial Old 是Serial的老年代版本 单线程收集器
- 算法: 标记整理
- 优点: 作为 CMS收集器的预备方案,CMS发生Concurrent Mode Failure时使用
-
Parallel Old 是Parallel Scavenge收集器的老年代版本,多线程收集器
- 算法:标记整理
- 优点:吞吐量优先原则
-
CMS收集器 CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器
- 算法 标记清除
- 优点 并发收集、低停顿。
- 缺点
- 标记-清除算法会产生空间碎片
- 无法处理浮动垃圾。可能出现“ConCurrent Mode Failure”失败而导致另一次Full GC的产生
- CPU资源非常敏感。默认回收线程数是(CPU数量 + 3)/ 4 。CPU数量越多,CMS消耗资源相对来说越少
- gc过程
- 初始标记:需要“Stop The World”,标记一下GC Roots能直接关联到的对象,速度很快。
- 并发标记:是进行GC Roots Tracing的过程。
- 重新标记:需要“Stop The World”,修正并发标记期间,用户程序继续运行而导致标记产生变动的那一部分标记。停顿时间稍长,但比初始标记短。
- 标记清除:和用户线程并发的收集垃圾清理工作。
- image.png
-
G1收集器 面向服务器端应用。整体来看是采用 标记 – 整理 算法,而局部上来看是基于 复制 算法来实现的。
-
相关信息
- 将整个堆划分成多个相同大小的独立区域(Region),并维护一个优先列表,用于跟踪记录每一个Region的回收价值(根据回收获得的空间大小以及回收所需的时间确定),每次在允许的时间内,优先回收价值最大的Region
-
gc过程
-
初始标记:标记一下GC Roots能直接关联到的对象。
-
并发标记:从GC Roots开始进行可达性分析,递归扫描整个堆里对象图,找出要回收的对象。
-
最终标记:修正并发标记期间用户程序运行导致的标记变动。
-
筛选回收:进行Region的回收价值排序并进行垃圾回收。
- image.png
-
网友评论