垃圾回收
方法栈帧的局部变量引用了一个对象的地址,对象保存在 Java 堆内存里面。
而在计算机中,内存资源是十分有限的,所以当一个方法执行完毕,那么方法的栈帧就会出栈,局部变量引用也会被清除。
这时,堆内存中的对象没有被任何一个变量引用,那么对于不需要的对象应该如何处理?
一、JVM 的垃圾回收机制
JVM 本身自带垃圾回收机制,这时一个后台自动运行的线程,这个线程会在后台不断检查 JVM 堆内存中的各个实例对象。
如果某个实例对象没有任何一个方法的局部变量指向它,也没有任何一个类的静态变量、常量指向它,
那么垃圾回收线程就会把这个未使用的实例对象回收,从内存中清除,不再占用任何内存资源。
二、对象存活周期
绝大部分对象存活周期是极为短暂的,只有少数对象是长期存活的。
public static class D{ public static void main (String[] args) { while (true) { load(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void load() { // 对象 C 在 main 方法中被调用 // load 方法执行完毕 load 出栈 // load 栈帧内引用的对象 c 将被标识为垃圾对象 // GC 时将被回收 // 所以像这类局部变量引用对象,一般存活周期极为短暂 C c = new C(""); c.toString(); } }
在方法内创建的局部变量创建并引用的对象,一旦方法执行完毕,整个栈帧将会出栈,局部变量创建的对象将被回收。
public static class E { // 对象 c 被全局变量或静态变量引用 // 创建的对象会被持久性的引用而不会被回收。 private static C c = new C("load"); public static void main (String[] args) { while (true) { load(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void load() { c.toString(); } }
而类属性或类静态变量创建并引用的对象,方法执行完成后不会被回收。因为类创建并存储在方法区,其类属性或静态变量也存储在方法区,创建的对象会被持久性的引用而不会被回收。
三、JVM 分代模型
JVM 将堆内存分了两个区域:年轻代、老年代。
创建和使用完后立即回收的对象放在年轻代,而创建之后需要一直存在的对象则放在老年代。
public static class Era { // 对象 old 被全局静态变量引用 private static Old old = new Old(); public static void main (String[] args) { // yang 方法执行完毕,对象 yang 将被回收 // 所以类似 yang 对象这种被局部变量引用的对象一般都存在 年轻代 // 因为它们几乎撑不过几次 YGC 就被回收了 yang(); while (true) { // old 方法执行完成后也不会回收 old 对象 // 因为引用 old 对象的是全局静态变量 // 全局静态变量存在于方法区,所以 old 对象是强引用关系 // 所以 old 对象经过一定次数 YGC 后会被转移到老年代 old(); } } private static void yang() { // 对象 yang 被局部变量引用 Yang yang = new Yang(); yang.toString(); } private static void old() { old.toString(); } }

四、永久代
JVM 永久代其实就是之前说的方法区
五、方法区内会不会进行垃圾回收?
方法区的类会被回收,满足以下条件就会被回收:
- 首先该类的所有实例对象都已经从 java 堆内存里被回收
- 其次加载这个类的 ClassLoader 已经被回收
- 最后,对该类的 Class 对象没有任何引用
六、大部分正常对象都优先在新生代分配内存
即使是类静态变量创建并引用的对象也是分配在新生代里的。
而垃圾回收的触发条件是,堆内存里囤积大量对象,导致新生代无法创建新的对象,
这时就会触发一次新生代内存空间的垃圾回收(Young GC / Minor GC),把新生代没有被引用的对象都给回收掉。
如果一个对象在经过一定次数的垃圾回收后没有被回收,这个对象就会进入老年代。关于对象分配机制:
- 新生代垃圾回收之后,存活对象太多,导致大量对象直接进入老年代
- 特别超大对象不经过新生代直接进入老年代
- 动态对象年龄判断机制
- 空间担保机制
七、判断变量引用的对象能否回收
JVM 使用了一种可达性分析算法来判断哪些对象是可以被回收的,哪些是不能回收的。
这个算法就是对每一个对象都向上分析判断是否有一个 GC Roots。
一句话总结:只要对象被方法的局部变量、类的静态变量引用了,就不会回收。
Java 中可以作为 GC Roots 的对象:
- 虚拟机栈(栈帧中的本地变量表,局部变量)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的native方法)中引用的对象
Java 中对象不同的引用类型
强引用、软引用、弱引用、虚引用,除了强引用一定不会被回收,其他引用都有可能被回收。
finialize() 方法
重写 Object 类中的 finialize() 方法,可以让某个 GC Roots 变量重新引用带回收对象。
public static class Yang { public static Yang instance; // finalize 方法是覆盖的超类 Object 中的 // 这个方法是在 GC 回收对象时调用的 // 所以这个方法影响着 GC 回收 // 一般不建议重写这个方法 // 因为一旦处理不当,容易引发内存溢出 @Override protected void finalize () throws Throwable { Yang.instance = this; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)