JVM垃圾收集(一)—— 概论与垃圾判定

引言

JVM垃圾收集(Garbage Collection, GC)机制是Java语言的一个比较鲜明的特点,从C/C++语言开始学习编程的同学都知道,分配内存/创建对象需要使用malloc/new操作,对应的还需要进行delete/free操作,来释放内存,防止内存泄漏。但是当我们初步学习Java之后发现,释放内存的步骤被省去了,在我当时的感觉是“悄悄”的消失了,以至于我都没有仔细感受这个变化。

但是“悄无声息”,并不代表毫不重要。这项技术是Java虚拟机内存管理的重中之重,并且当系统有高并发的情况时,垃圾收集可能会是限制系统的瓶颈。同时在就业笔试面试中也是常考的知识点之一,所以每一个Java工程师或想深入学习Java语言的同学都应该对垃圾收集了如指掌。

我在学习这一块知识的时候,为了让知识更有层次感、更方便记忆,我把这些内容分为了“哪些对象需要回收”、“什么时候进行回收”、“用什么算法回收”以及“具体的垃圾收集器”等模块来讲述。

 

目录

JVM垃圾收集(一)—— 概论与垃圾判定

JVM垃圾收集(二)—— 垃圾收集算法

 


 

垃圾判定算法

1.引用计数算法

从名字就可以大概猜出算法的大概原理,引用计数法的原理是:给对象添加一个引用计数器,每当有一个对象引用它的时候,就为计数器的值加1;引用失效的时候,计数器的值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

这个算法的实现比较简单,也有包括Python语言在内的众多语言使用了这种算法进行内存管理,但是主流的Java虚拟机中没有采用这种方式,因为它无法解决循环引用的问题。循环引用的具体场景可以通过下边的简化代码来描述:

public class TestMain {
    
    public Object instance = null;

    public static void main(String[] args) {

        TestMain obj1 = new TestMain();
        TestMain obj2 = new TestMain();

        obj1.instance = obj2;
        obj2.instance = obj1;

        obj1 = null;
        obj2 = null;

    }
}

这就是循环引用问题。当最后把obj1、obj2两个对象引用置为null的时候我们已经无法访问这两个堆内存中的对象了,但是按照引用计数法两个对象的引用计数器仍为1,不能被当做垃圾回收。

 

2.可达性分析算法

Java、C#以及古老的Lisp的主流实现中,都是通过可达性分析算法来判断对象是否存活。算法的基本流程是通过一系列的成为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连是,此对象就被认为是垃圾了。如图所示:

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

由图可知,object 5/6/7 是判定为可回收的对象。

在Java语言中,可以作为GC Roots的对象包含下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象 
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

 

方法区的回收

可达性分析法主要针对堆进行回收,那么方法区(或者说HotSpot虚拟机中的永久代)有没有垃圾回收呢?Java虚拟机规范中明确说可以不在方法区实现垃圾收集,并且在方法区中实现垃圾收集的性价比是比较低的,在堆中对刚创建的对象进行回收,回收率能达到70%-95%,而永久代的垃圾收集率远远低于这个数字。

但方法区的垃圾收集也是有虚拟机实现的,并且方法区的垃圾收集主要回收两部分内容:废弃常量和无用的类。我们分别对这部分进行讨论。

废弃常量。回收废弃常量和回收Java堆中的对象十分相似。以常量池中字面量的回收为例,加入一个字符串"crazy"已经进入了常量池中,但是当前系统没有任何一个String 对象是叫做“crazy”的,换句话说,就是没有任何String对象引用常量池中的"crazy"常量,也没有其他地方引用了这个变量,如果这时发生内存回收,而且必要的话,这个"crazy"常量就会被系统清理出常量池。

无用的类。相对于回收废弃常量来说,判定一个无用的类的任务要艰巨的多。类需要同时满足3个条件才能算是无用的类:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

当类满足上边三个条件的时候就可以被回收,但是具体是否会被回收还取决于很多因素的影响。比如HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载和卸载的信息;其中-verbose:class和-XX:TraceClassLoading可以在Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持。

在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

 

结语

垃圾判定算法为对象的回收提供了保证,但是这仅仅是垃圾回收的第一步,认识和了解Java虚拟机垃圾回收机制还有很长的路要走。

 


 

如有错误,欢迎指摘。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值