Java对象回收

 垃圾收集器在对堆进行回收前,第一件事就是要确定这些对象之中哪些还“存活”,哪些已经“死去”。

1引用计数算法

在对象中添加一个引用计数器,每当有一个地方引用它时,计数器+1,当引用失效时,计数器-1。任何时刻计数器为0的对象就是不可能再被使用的。

1.1 算法缺陷

对象之间相互循环引用的问题。

public class CrossReference {
    private static class A {
        B reference = null;
    }
    private static class B {
        A reference = null;
    }
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.reference = b;
        b.reference = a;
        // a 和 b 引用设置为空后,按照引用计数法,A 与 B 的实例的引用也都不为0,为1
        a = null;
        b = null;
    }
}

2 可达性分析

当前主流的商用程序语言(Java、C#等)的内存管理子系统都是通过可达性分析算法来判定对象是否存活的。

通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走的路径称为“引用链”,如果某个对象到GC Root间没有任何引用链相连,则证明此对象是不可能再被使用的。

图 可达性分析判断对象是否可回收

2.1 固定作为GC Roots的对象

1)在虚拟机栈中引用的对象,比如各线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。

2)在方法区中类静态属性引用的对象。

3)在方法区中常量引用的对象,比如字符串常量池里的引用。

4)在本地方法栈中JNI(Native方法)引用的对象。

5)Java虚拟机内部的引用。

6)被同步锁(synchronized关键字)持有的对象。

7)反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

除了这些固定的集合外,根据用户所选用的垃圾收集器以及当前回收区域的不同,还可以有其他对象“临时性”地加入。

3 引用类型

类型

说明

强引用

Strongly Reference

指程序中普遍存在的引用赋值。任何情况下,只有该引用关系存在,垃圾收集器就永远不会回收掉被引用的对象。

软引用

Soft Reference

描述一些还有用,但非必须的对象。只要软引用关联着对象,在系统将要发送内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没足够内存,才会抛出内存溢出异常。

弱引用

Weak Reference

描述那些非必须对象,比软引用引用更弱些。被弱引用关联的对象只能生存到下一次垃圾收集发生为止。

虚引用

Phantom Reference

是最弱的引用关系,不会对其生成时间构成影响,也无法通过虚引用来取得一个对象实例。设置虚引用的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

表 Java四种引用类型,强度依次减弱

3.1 软引用场景

软引用的非必须属性,很适合用来做缓存。如果该对象被回收,我们可以在业务线程上抛出自定义的异常或者记录到日志。

public class SoftReferenceService {

    private static SoftReference<List<Object>> cacheList;

    // 该方法会在Service最开始执行
    public void setResultCacheList() {
        //从数据库中读取大量的数据放入到resultCacheList作为缓存
        List<Object> objects = new ArrayList<>();
        objects.add("数据1");
        cacheList = new SoftReference<>(objects);
    }

    public List<Object> readDate() {
        List<Object> result = null;
        // 先从缓存中找数据,如果没有则抛出错误
        if (cacheList != null) {
            List<Object> objectList = cacheList.get();
            if (objectList == null) {
                System.out.println("没有数据了");// 记录该缓存以被回收
                result = new ArrayList<>(); 没有缓存则获取新的数据
            } else result = objectList;
        } else result = new ArrayList<>();
        return result;
    }
}

3.2 弱引用使用场景

通过Map存储键值对,当某个key不需要时(这个key被GC回收时),应该把这个条目从map中移除,否则会导致内存泄漏。WeakHashMap(非线程安全)封装好了这个,以下是对其源码进行分析:

图 WeakHashMap底层数据结构Entry的部分源码

其Entry并没有直接存储key,而是调用了WeakReference(T,ReferenceQueue)的构造函数。该构造函数的作用是创建一个WeakReference,并且设置其回收时放入的引用队列(即该引用被回收时,会被放入引用队列中)。

图 WeakHashMap类中的expungeStaleEntries函数

该函数主要作用是清除过期元素(弱引用)作为key的Entry的value。

图 expungeStaleEntries函数被使用的场景

在获取size、resize(扩容)及getTable时会调用该函数。

WeakHashMap 能自动清除无强引用时的key。value会在相应的map操作中删除。

3.3 虚引用使用场景

如果GC在某个时间点确定虚引用的所指对象只有虚引用可达,届时或稍后将其加入引用队列。虚引用点引用对象不会被释放直到所有指向该对象的虚引用被清除。所以虚引用通常用来跟踪对象被垃圾回收的活动:

public class PhantomReferenceNotice { // vm配置: -Xms1m -Xmx1m

    private static ReferenceQueue<PhantomReferenceNotice> queue = null;

    // 监控引用队列的线程
    static class QueueMonitorThread extends Thread {
        @Override
        public void run() {
            while (true) {
                if (queue != null) {
                    Reference<? extends PhantomReferenceNotice> remove = queue.poll();
                    if (remove != null) {
                        System.out.println("追踪对象垃圾回收活动(对象GC前执行):" + remove + "该实例对象被GC了");
                    }
                }
            }
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("对象执行了finalize()");
    }

    public static void main(String[] args) throws InterruptedException {
        queue = new ReferenceQueue<>();
        QueueMonitorThread thread = new QueueMonitorThread();
        thread.setDaemon(true);
        thread.start();
        PhantomReferenceNotice instance = new PhantomReferenceNotice();

        PhantomReference<PhantomReferenceNotice> reference = new PhantomReference<>(instance,queue);
        System.out.println("reference.get():" + reference.get());
        System.out.println("第一次gc");
        System.gc();
        TimeUnit.SECONDS.sleep(2);
        instance = null;
        System.out.println("第二次gc");
        System.gc();
        String tempStr = ""; //为了占满堆内存,让JVM好执行垃圾回收
        for (int i = 0; i < 10000; i++) {
            tempStr += System.currentTimeMillis() + "";
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("main线程执行完毕,instance = null");
    }
}
/*
运行结果:
reference.get():null
第一次gc
第二次gc
对象执行了finalize()
追踪对象垃圾回收活动(对象GC前执行):java.lang.ref.PhantomReference@30535167该实例对象被GC了
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOfRange(Arrays.java:3664)
	at java.lang.String.<init>(String.java:207)
	at java.lang.StringBuilder.toString(StringBuilder.java:407)
	at day02.article.PhantomReferenceNotice.main(PhantomReferenceNotice.java:49)

Process finished with exit code 1
 */

4 对象回收判定

即使在可达性分析算法中判定为不可达对象,也不是“非死不可”,这时它们暂时处于“缓行”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:1)进行可达性分析后发现没有与GC Roots相连接的引用链,则第一次标记;2)随后进行一次筛选,筛选条件是此对象是否有必要执行finalize()方法(加入对象没有覆盖finalize()方法,或者该方法已被虚拟机调用过,则没必要执行)。将判定为“有必要执行finalize()方法” 的对象放置于F- Queue队列中,并且稍后由Finalizer线程去执行finalize()方法。finalize()方法是逃脱死亡的最后一次机会。稍后收集器将对F- Queue中的对象进行第二次小规模标记。

图 对象的二次标记

public class SecondLabel {

    private static SecondLabel instance = null;

    @Override
    protected void finalize() throws Throwable {
        System.out.println("SecondLabel的对象执行了finalize()方法");
        instance = this;
    }

    public static void main(String[] args) throws InterruptedException {
        instance = new SecondLabel();
        instance = null;
        System.gc();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("第一次gc()");
        System.out.println("instance:" + instance);
        instance = null;
        System.gc();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("第二次gc()");
        System.out.println("instance:" + instance);
    }
}
/* 运行结果:
SecondLabel的对象执行了finalize()方法
第一次gc()
instance:day02.article.SecondLabel@1540e19d
第二次gc()
instance:null

Process finished with exit code 0
 */

不鼓励通过这个方法拯救对象,可以用try-finally或者其他方式替代。

5 回收方法区

方法区垃圾收集的“性价比”通常比较低。

废弃的常量和不再使用的类型。判断一个常量是否“废弃”与回收Java堆中的对象非常类似。而判断“不再使用的类型”的条件就比较苛刻,需要满足以下三个条件:

1)该类所有的实例都已经被回收。

2)加载该类的类加载器已经被回收。这个条件除非是经过精心设计的可替代类加载器的场景,如OSGi、JSP的重加载等,否则通常很难达成的。

3)该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问改类的方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java面向对象 1 1 学习方法与要求 1 2 面向对象语言与面向过程语言的区别 7 3 面向对象?什么对象? 8 4 什么是类? 9 5 如何创建一个类Class? 10 6 如何使用类创建对象 10 7 引用与实例 11 8 实例属性与实例方法 11 9 实例属性与实例方法的使用 12 10 实例属性属于实例本身,与其他实例没有关系 13 11 javaBean编码规范 14 12 练习: 15 13 什么构造方法 16 14 构造方法的作用? 17 15 this关键字 17 16 方法调用时的引用传递还是值传递 19 17 面向对象语言的三大特性:封装,继承,多态 20 18 什么是封装?封装在哪? 20 18.1 封装的好处? 20 19 访问修饰符 20 20 面向对象的特性-继承 21 20.1 继承的关键字:extends 21 20.2 继承的语法格式: 21 20.3 继承的好处 21 20.4 Object类 21 20.5 继承是以什么方法实现的? 22 20.6 两个子类的实例,super会指向同一个父类实例吗? 23 20.7 在创建子类实例时,会创建父类实例,先创建哪个? 23 20.8 在子类构造方法中如何调用父类构造方法? 23 20.9 super关键字 24 20.10 父类中私有属性和私有方法可以继承吗? 24 20.11 在代码开发时先开发父类还是先开发子类? 24 20.12 父类是怎么来的? 25 20.13 父类中放的所有子类的共性。子类可以有自己的特性。 26 20.14 方法重写(核心@Override) 28 20.15 练习: 29 20.16 作业题: 31 21 类与类之间的关系 32 22 GC:垃圾回收机制 33 23 Object类 33 23.1 常用方法:boolean equals(Object) 33 23.2 常用方法:String toString() 34 24 继承整理 34 25 面向对象三大特性:多态(核心) 34 25.1 多态的前提? 35 25.2 什么是多态? 35 25.3 父类的引用指向子类实例时,父类引用可以调用哪些方法? 35 26 引用类型的自动类型与强制类型转换 36 27 多态的应用 37 27.1 在使用多态时的一般格式: 37 27.2 练习:创建一个薪资专员(Persionnel),能计算员工工资,负责汇总当月所有员工的总工资数 38 27.3 练习:NewBasePlusSalesEmployee:针对BasePlusSalesEmployee有固定底薪的销售人员,有任务额度10K,满足任务额度正常发放,不满足任务额度发放底薪的80%,提成正常发放。 38 27.4 练习:设计一个形状类Shape 39 28 abstract关键字 41 28.1 abstract可以修饰的对象 41 28.2 抽象方法 41 28.3 抽象类 41 28.4 抽象方法与抽象类的关系 42 28.5 抽象方法与抽象类的使用 42 28.6 abstract的使用场合 42 29 练习:写一个“愤怒的小鸟”: 43 30 final关键字 43 30.1 final可以修饰到3个地方 43 30.2 引用类型加final修饰表示引用不可变 44 31 static关键字 44 31.1 static关键字可以修饰4个地方 44 31.2 静态属性 44 31.3 静态的与实例的 45 31.4 静态代码块 45 31.5 静态方法 45 31.6 静态方法是不能被继承 46 31.7 如何区分静态方法和实例方法的应用 46 31.8 静态导入(1.5新特性) 46 32 单例模式 47 32.1 饿汉模式 47 32.2 懒汉模式 47 33 接口(interface) 48 33.1 如何创建一个接口。 48 33.2 如何使用接口 48 33.3 如何使用类实现一个接口 49 33.4 接口的细节 49 34 接口的应用(面向对象分析) 49 34.1 案例(第一版,使用接口) 50 34.2 案例(第二版) 52 35面向对象设计原则 54 1、找出应用中可能需求变化的代码,把它们独立出来,不要和那些需求不变化的的代码混在一起 54 2、针对接口编程,而不要针对实现类编程 54 3、多用组合,少用继承(包含实现) 54 4、为了交互对象之间的松耦合设计而努力 54 5、类应该对扩展开放,对修改关闭 54 6、依赖倒置,要依赖抽象,不要依赖具体类 54 36 练习:超市收银 54 37 练习:接口表示一种能力,也可以是一种规范 56 38 策略模式 57 38.1 动作冒险游戏 57 38.2 类图 57 38.3 编写使用武器行为接口和实现类 58 38.4 编写角色类和子类 58 38.5 测试类 58 38.6 动作冒险游戏补丁:增加新的角色和新的技能 59 38.7 编写新的打斗行为 59 38.8 编写新的治疗行为和实现类 59 38.9 修改角色父类 60 38.10 修改King类 60 39 披萨工厂 61 39.1 编写披萨父类 61 39.2 编写各种披萨 61 39.3 编写披萨商品类 62 39.4 简单工厂 63 39.5 使用简单工厂修改商店类 63 39.6 使用简单工厂将创建对象代码单独的封装的好处? 64 39.7 使用常量优化工厂类 64 39.8 测试类 65 40 枚举(enum) 65 40.1 创建一个枚举 66 40.2 为枚举创建实例 66 40.3 如何使用枚举的实例 66 40.4 使用枚举优化披萨工厂 67 40.5 枚举类型也可以有构造方法 68 40.6 枚举类型也可以有属性 68 40.7 枚举类型也支持带参数的构造方法。 68 40.8 枚举类型也可以有实例方法 68 40.9 枚举类型也可以支持抽象方法 69 41 内部类 70 41.1 内部类的分类 70 41.2 匿名内部类 70 41.3 成员内部类 71 41.4 静态内部类 72 41.5 局部内部类 73 42 作业 :商超案例,以OOP重构 73

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值