JVM——垃圾回收
在Java开发中,JVM不仅负责运行Java字节码,还通过自动内存管理机制帮助开发者避免手动内存管理的复杂性。
1. JVM内存模型
JVM的内存模型主要包括以下几个部分:
- 方法区(JDK8之后叫元空间): 存储类信息,常量池,静态变量
- 堆:所有线程共享的一块内存区域,存放对象实例
- 栈:线程私有
- 程序计数器:线程私有,记录当前线程执行的字节码行号
- 本地方法栈:为Native方法服务
2. Java堆的划分
- 年轻代
- Survivor 区0
- Survivor 区1
- Eden Space伊甸园区
- 老年代
其中:年轻代的GC成为Young GC,老年代的GC叫做Full GC
3. 什么是垃圾
在JVM中,“垃圾”指的是那些不再被使用的对象。具体来说,如果一个对象不能通过程序中的任何引用链从GC Roots到达,那么这个对象就被认为是垃圾。
GC Roots
GC Roots是一组必须活跃的引用,它们作为可达性分析的起点。以下是一些常见的GC Roots的例子:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象:比如正在执行的方法中的局部变量。
- 方法区中类静态属性引用的对象:例如类级别的静态成员变量。
- 方法区中常量引用的对象:如字符串常量池里的引用。
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
如何判断对象是否为垃圾
有两种主要的方法用来确定哪些对象被认为是垃圾:
-
引用计数法(Reference Counting):
- 这种方法给每个对象关联一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值减1。任何时刻计数器为0的对象就是不可能再被使用的。
- 但是这种方法很难解决对象之间的循环引用问题,因此在Java中并没有采用这种方法。
-
可达性分析(Reachability Analysis):
- JVM使用这种方法来判断对象是否存活,它通过一系列称为“GC Roots”的对象作为起始点,开始向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象没有任何引用链与GC Roots相连时,则证明此对象是不可用的。
- 这是目前主流的垃圾判定算法,在Java中得到了广泛应用。
4. 如何进行垃圾回收
4.1 垃圾回收算法
标记-清理法:如果堆中的对象没有被GCRoots指向,我们就判断这些对象为垃圾,并标记起来,然后再扫描一遍,将标记的对象清理掉。但是缺点是会产生内存碎片。主要用于FullGC
标记-整理算法:清理垃圾的时候,让存活的对象填充内存碎片,但是代价是开销太大。主要用于FullGC
复制算法:将堆的区间分为1区和2区,缺点是2倍内存。主要用于YoungGC
4.2 GC触发条件
4.2.1 YoungGC
YoungGC:当我们new对象实例时,对象会出生在伊甸园区,伊甸园区快满了的时候,会触发youngGC,通过复制算法。注意:S0区和S1区交替工作。
S0:S1:Eden的内存分配大小默认是1:1:8。这是因为大部分对象都是“朝生夕死”的。
(1)判断Eden区和S1区存活对象
(2)将Eden区和S1区存活的对象,复制到S0区,并清空S1区和Eden区
(3)循环1,2的过程
- E+S1的存活对象复制到S0,清空E+S1
- E+S0的存活对象复制到S1,清空E+S0
- 循环....
注意:每次GC,对象的age都会+1,当对象的年纪到达6/15岁,这个对象会被移除到Old区。
4.2.2 FullGC
当Old区间快满了,会引起OldGC,但是OldGC往往伴随着YoungGC,所以也叫FullGC。整个Java程序暂停,进行垃圾回收。采用标记清理/标记整理算法
4.3 垃圾收集器
年轻代垃圾回收器:ParNew(内部采用复制算法实现)
老年代垃圾回收器:CMS(标记清理算法实现)
最新版JDK:G1垃圾收集器。