垃圾回收

方法栈帧的局部变量引用了一个对象的地址,对象保存在 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 的对象:
  1. 虚拟机栈(栈帧中的本地变量表,局部变量)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中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;
    }
}
复制代码

 

posted @   维维尼~  阅读(113)  评论(0编辑  收藏  举报
编辑推荐:
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示