前言
垃圾回收是Java实现动态内存管理的重要手段。
JVM内存模型
JVM将虚拟机分为5大区域:
- 程序计数器:线程私有的,是一块很小的内存空间,作为当前线程的行号指示器,用于记录当前虚拟机正在执行的线程指令地址;
- 虚拟机栈:线程私有的,每个方法执行的时候都会创建一个栈帧,用于存储局部变量表、操作数、动态链接和方法返回等信息,当线程请求的栈深度超过了虚拟机允许的最大深度时,就会抛出StackOverFlowError;
- 本地方法栈:线程私有的,保存的是native方法的信息,当一个jvm创建的线程调用native方法后,jvm不会在虚拟机栈中为该线程创建栈帧,而是简单的动态链接并直接调用该方法;
- JAVA堆:java堆是所有线程共享的一块内存,一般也是最大的,几乎所有对象的实例和数组都要在堆上分配内存,因此该区域经常发生垃圾回收的操作;
- 方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据。即永久代,在jdk1.8中不存在方法区了,被元数据区替代(原方法区被分成两部分;1:加载的类信息,2:运行时常量池);
垃圾回收
垃圾回收一般发生在JAVA堆区域。JAVA堆可以细分为新生代和老年代。新生代又被细分为Eden和Survivor(S0+S1),默认的分配比例为8:1:1。
当Eden区满时,会触发一次Minor GC(夭折?),存活下来的对象会被分到Survivor区,但是大对象(需要大量连续内存空间的对象)会被直接分到老年代。从Eden出生的对象,每经历一次Minor GC,年龄+1,到15被分配到老年代。
当老年代满了,而无法容纳更多对象的话,会触发一次FULL GC。FULL GC的对象是整个内存堆(年轻代和老年代)。Major GC是发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC。
对象存活判断
引用计数法:
为每个对象设置一个引用计数器,每被引用+1,引用失效-1,当计数为0,等待回收。但是存在循环引用的问题,一般主流虚拟机不采用该方法。可达性分析法:
从GC Roots向下搜索,不可达的对象,说明不可用,经过2次不可达标记后被正式回收。第一次,判断是否有执行finalize()的必要(是否执行过finalize,是否覆盖了finalize()[重写且不为空]),若无必要,等待回收,若有必要,将被放入F-Queue队列,生成一个finalize线程执行该方法,之后进行第二次GC标记,若依然不可达GC Roots,将被回收。
note*:
- finalize线程不能保证该方法一定被执行,因为如果线程执行缓慢或进入了死锁,会导致回收系统的崩溃。
- 可以作为GC Roots的对象:虚拟机栈中引用的对象、方法区类静态属性引用的变量、方法区常量池引用的对象、本地方法栈JNI引用的对象
垃圾回收算法
标记清除算法:
第一步,利用可达性进行存活和垃圾对象标记;
第二步,遍历,将标记为垃圾的对象回收;
特点:效率低,标记和清除的效率都不高;标记和清除后会产生大量的不连续的空间分片,可能会导致之后程序运行的时候需分配大对象而找不到连续分片而不得不触发一次GC。标记整理算法:
第一步:利用可达性进行存活和垃圾对象标记;
第二步:将所有的存活的对象向一端移动,将端边界以外的对象都回收掉;
特点:适用于存活对象多,垃圾少的情况;需要整理的过程,无空间碎片产生。复制算法:
将内存按照容量大小分为大小相等的两块,每次只使用一块,当一块使用完了,就将还存活的对象移到另一块上,然后在把使用过的内存空间移除;
特点:不会产生空间碎片;内存使用率极低。分代收集算法:
根据内存对象的存活周期不同,将内存划分成几块,java虚拟机一般将内存分成新生代和老年代,在新生代中,有大量对象死去和少量对象存活,所以采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;老年代中因为对象的存活率极高,没有额外的空间对他进行分配担保,所以采用标记清理或者标记整理算法进行回收;
垃圾回收器
垃圾回收器主要分为以下几种:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;
Serial:
单线程的收集器,收集垃圾时,必须stop the world,复制算法。ParNew:
Serial收集器的多线程版本,也需要stop the world,复制算法.Parallel Scavenge:
新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量,和ParNew的最大区别是GC自动调节策略;虚拟机会根据系统的运行状态收集性能监控信息,动态设置这些参数,以提供最优停顿时间和最高的吞吐量;Serial Old:
Serial收集器的老年代版本,单线程收集器,使用标记整理算法。Parallel Old:
是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。CMS:
是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片;G1:
标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选回收。不会产生空间碎片,可以精确地控制停顿;
G1将整个堆分为大小相等的多个Region(区域),G1跟踪每个区域的垃圾大小,在后台维护一个优先级列表,每次根据允许的收集时间,优先回收价值最大的区域,已达到在有限时间内获取尽可能高的回收效率;
参考文章
java中的垃圾回收机制,你理解了嘛?
史上最全!2020面试阿里,字节跳动90%被问到的JVM面试题(附答案)
Java虚拟机(JVM)你只要看这一篇就够了!