初始JVM-- 二.垃圾收集简介

    说起垃圾收集(GC),大部分人把这项技术当做Java语言的伴生产物。事实上,GC的历史比Java久远,1960年诞生于MIT的Lisp是第一门真正使用内存分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考GC需要完成的三件事情:

  1. 那些内存需要回收?
  2. 什么时候进行回收?
  3. 如何回收?

    经过半个多世纪的发展,目前内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了自动化时代,那为什么还需要去了解GC呢?因为,当需要各种排查问题时,例如内存溢出,内存泄露。或者说当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些自动化的技术实施必要的监控和调节。

    之前介绍了Java内存运行时区域的各个部分,其中程序计数器,虚拟机栈,本地方法栈,随着线程的生命周期而存活;栈中的栈帧随着方法的进入和退出而有条不紊的执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知,因此这几个区域就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟着回收了。而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的。垃圾收集器关注的主要也是这部分内存。

    在堆里面,存放着Java世界几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“活着”,哪些已经“死去”。

    判断对象是生是死,有以下几种算法:

一.引用计数算法

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

    客观的说,这种方法实现简单,判定效率也很高。在大部分情况下是一个不错的算法。但是,主流的Java虚拟机并没有选用这种算法来管理内存,其中最主要的原因就是它很难解决对象之间相互循环引用的问题。

    举例:

public class Object {
    
    Object field = null;
        
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                Object objectA = new Object();
                Object objectB = new Object();//1
                objectA.field = objectB;
                objectB.field = objectA;//2
                //to do something
                objectA = null;
                objectB = null;//3
            }
        });
        thread.start();
        while (true);
    }
        
}

    大致解释一下,在代码中标注了1、2、3三个数字,当第1个地方的语句执行完以后,两个对象的引用计数全部为1。当第2个地方的语句执行完以后,两个对象的引用计数就全部变成了2。当第3个地方的语句执行完以后,也就是将二者全部归为空值以后,二者的引用计数仍然为1。根据引用计数算法的回收规则,引用计数没有归0的时候是不会被回收的。

 

二.可达性分析算法

    在主流的商用程序语言中(Java,C#...),都是通过可达性分析算法来判定对象是否存活的。

    这个算法的基本思路,就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连(用图论来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。如图,对象5,6,7虽然互有关联,但是到GC Roots不可达,所以会被判定为可回收的对象。

    在Java中,可作为GC Roots的对象包括以下几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法栈中Native方法引用的对象

 

    无论通过引用计数算法判断对象的引用数量,还是可达性分析算法分析引用链是否可达,判定对象是否存活都与“引用”有关。在JDK1.2之前,Java中的引用的定义很传统:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。这种定义很纯粹,但是过于狭隘,一个对象在这种定义下只有被引用和没被引用两种状态,对于描述一些“食之无味,弃之可惜”的对象就显得无能为力。我们希望能描述这样一类对象:当内存空间足够时,则能保留在内存中;如果内存空间在进行垃圾收集之后还是非常紧张,那么再抛弃这些对象。很多系统的缓存功能 都符合这样的应用场景。

    在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为:强引用,软引用,弱引用,虚引用,这4中引用强度依次逐渐减弱。

    之前说的都是对堆内存中进行垃圾回收。但是其实方法区也是可以进行垃圾回收的。例如回收里面的废弃常量,废弃的类。回收废弃常量跟回收堆中对象非常类似,而判断一个类是否是一个无用的类相对比较苛刻,需符合下面三点:

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

    当满足上面几点时,虚拟机可以对无用类进行回收,但仅仅是“可以”。而并不是跟对象一样,没用了就必然被回收。是否对类进行回收,虚拟机提供了参数可配置,这里不做具体研究。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值