1、GC如何判断对象可以被回收?
1)引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收
(但是引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互引用 计数器=1 永远无法被回收。)
2)可达性分析法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GCRoots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。
GC Roots的对象有:
(1)虚拟机栈(栈帧中的本地变量表)中引用的对象
(2)方法区中类静态属性引用的对象
(3)方法区中常量引用的对象
(4)本地方法栈中Native方法引用的对象
2、类的生命周期
加载:查找并加载类的二进制数据
验证:确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:把类中的符号引用转换为直接引用
初始化:为类的静态变量赋予正确的初始值
3、JVM内存模型:
1)程序计数器:
是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器,它是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域。
2)虚拟机栈:
主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。是线程私有的,生命周期和线程一致。(大小通过-Xss控制)
3)本地方法栈:
用于管理本地方法(Native Method 就是一个 Java 调用非 Java 代码的接口)的调用,也是线程私有的。
4)堆:
Java 堆是 Java 虚拟机管理的内存中最大的一块,被所有线程共享。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数据都在这里分配内存。
为了进行高效的垃圾回收,虚拟机把堆内存逻辑上划分成三块区域(分代的唯一理由就是优化 GC 性能):
(1)新生代:存放大多数新创建的对象实例,负责gc的叫Minor GC(mai ner GC)
a、其区域被分为一个Eden Memory和两个Survivor Memory,默认比例是8:1:1(-XX:SurvivorRatio)。
b、大多数新创建的对象都位于 Eden 内存空间中,当 Eden 空间不足时,执行Minor GC,并将所有幸存者对象移动到一个Survivor空间中,
Minor GC 再次检查幸存者对象,并将它们移动到另一个幸存者空间。所以其中的一个幸存者空间总是空的。经过多次GC后存活下来的对象被移动到老年代(年龄计数器,默认值15:-XX:MaxTenuringThreshold)
(2)老年代:被长时间使用的对象,老年代的内存空间应该要比年轻代更大,老年代垃圾收集称为 主GC(Major GC)
大对象直接进入老年代(大对象是指需要大量连续内存空间的对象,超过参数-XX:PetenureSizeThreshold)。这样做的目的是避免在 Eden 区和两个Survivor 区之间发生大量的内存拷贝。
(3)元空间(JDK1.8 之前叫永久代):像一些方法中的操作临时对象等,JDK1.8 之前是占用 JVM 内存,JDK1.8 之后直接使用物理内存
注意:
1、通常会将 -Xmx 和 -Xms 两个参数配置为相同的值,其目的是为了能够在垃圾回收机制清理完堆区后不再需要重新分隔计算堆的大小,从而提高性能,防止内存抖动(影响程序卡顿)
2、默认情况下新生代和老年代的比例是 1:2,可以通过 –XX:NewRatio 来配置
3、在jdk1.8中,永久代已经不存在,存储的类信息、编译后的代码数据等已经移动到了元空间(MetaSpace)中,元空间并没有处于堆内存上,而是直接占用的本地内存
(可通过MetaspaceSize设置初始大小,MaxMetaspaceSize设置最大空间)
4、堆区和方法区是所有线程共享的,栈、本地方法栈、程序计数器是每个线程独有的
5、程序计数器是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域
5)方法区:用于存储类信息、常量、静态变量、编译后的代码等数据
4、垃圾回收算法
1)标记 - 清除:将存活的对象进行标记,然后清理掉未被标记的对象。
不足:
(1)标记和清除过程效率都不高;
(2)会产生大量不连续的内存碎片,导致无法给大对象分配内存。
2)标记 - 整理:让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
3)复制:将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
不足:
只使用了内存的一半。
现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将新生代划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间,
每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和使用过的那一块 Survivor
5、分代收集
1)新生代使用: 复制算法
2)老年代使用: 标记 - 清除 或者 标记 - 整理 算法
6、Full GC 的触发条件
1.调用 System.gc(),不建议
2.大对象直接进入老年代
3.空间分配担保失败:使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC