深入Java虚拟机--判断对象存活状态

程序计数器,虚拟机栈和本地方法栈

首先我们先来看下垃圾回收中不会管理到的内存区域,在Java虚拟机的运行时数据区我们可以看到,程序计数器,虚拟机栈,本地方法栈这三个地方是比较特别的。这个三个部分的特点就是线程私有的,它们随着线程的创建而诞生,也因线程的结束而灭亡。栈中的栈帧随着方法的进入和退出会有条不絮的执行着进栈和出栈。每一个栈帧中分配多少内存,基本上是在类结构确认下来的时候就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束,内存自然就跟随着回收了。

Java堆和方法区

我们讨论的垃圾回收,主要就是关于Java堆中废弃对象的回收。Java堆和方法区中,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们会在程序运行的时候动态创建对象,这部分内存的分配和回收也是动态的,垃圾收集器所关注的是这部分内存如何进行回收。

对象状态判断

在研究对象的回收之前,我们需要先看一下如何进行判断对象是否还有存活价值,即要先判断对象是否还有被引用,这是我们进行垃圾回收的第一步,判断对象存活状态。接下来我会讲一下几种判断的方法。

1.、引用计数法

一个比较通俗的方法就是当对象在创建的时候,就给对象创建一个对象计数器,每当有一个地方引用到这个对象的时候,计数器加一;当引用失效的时候,计数器减1;任何时候计数器为0的对象就是不可能被使用的,就是我们所认知的 --死亡对象。 客观地说,引用计数算法的实现比较简单,判定效率也很高,在大部分情况下它都是一个不错的算法,也有一些比较著名的应用案例,例如微软公司的COM(Component Obejct Mode)技术,使用ActionScript3的FlashPlayer等技术都引用了技术算法进行内存管理。 但是,至少主流的Java虚拟机里面没有用到引用计数算法来管理内存,之中最主要的问题就是他很难解决对象之间相互循环引用的问题:

public class ReferenceGc {
    public Object instance = null;
    public static void main(String[] args){
        ReferenceGc gcA = new ReferenceGc();
        ReferenceGc gcB = new ReferenceGc();
        gcA.instance=gcB;
        gcB.instance=gcA;
        gcA=null;
        gcB=null;

        System.gc();
    }
}

这里面如果采用的是引用计数算法来进行垃圾回收的话,这种对象明明是没有使用,但是却仍然占内存,在Java中,这种情况是非常常见的,所以这种算法并不能解决Java中对象相互引用的问题,所以Java虚拟机判断对象存活状态的算法是选择了接下来介绍的--可达性分析算法。

2、可达性分析算法

这个算法的基本思想是,通过一系列的GC Roots 的对象作为七点,从这些节点开始的向下搜索,搜索所走过的路径成为引用连,当一个对象到GC Roots 没有任何引用链相连时,则证明此对象时不可用的。 如上图所示,虽然Object5,Object6和Object7相互有引用,但是他们与GC Roots间是不可达的,所以它们将会被判定为使可回收的对象。

大家可能会很疑惑,那为什么Object5为什么不能当GC Roots呢?

首先,我们要规定好GC Roots的定义范围,并不是说所有的对象都能够成为GC Roots:

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

- 虚拟机栈(栈帧中的本地变量表)引用的对象

- 方法区中类静态属性引用的对象

- 方法区中常量引用的对象

- 本地方法栈中JNI引用的对象

对象在被回收前最后的自我救赎

上面我们说完了Java虚拟机中判断对象存活状态的算法,那么是不是说我们判断出对象时没有(有效)引用之后就会被马上回收呢?实际上并不是这样的,即时在可达性分析分算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段。

对象要被宣告死亡要经历两个阶段:

第一个阶段就是可达性分析,判断对象是否具有有效引用

第二个阶段就是判断对象是否有必要执行 finalize()方法。当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况称为“没有必要执行”,就是对象已经没有任何机会完成救赎。

package com.Shop.Test;

/**
 * Created by Administrator on 2016/7/25.
 */
public class FinalizeEscapeGc {
    private static FinalizeEscapeGc SAVE_HOOK = null;
    public void isAlive(){
        System.out.println("yes, i am still alive");
    }

    @Override
    protected  void finalize() throws Throwable{
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGc.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws InterruptedException {
        SAVE_HOOK = new FinalizeEscapeGc();
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if(SAVE_HOOK!= null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no ,i am dead ");
        }


        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if(SAVE_HOOK!= null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no ,i am dead ");
        }
    }
}

输出结果:

finalize method executed!
yes, i am still alive
no ,i am dead

      我们可以看到,在对象“死亡”之后,并没有马上死亡,我们覆盖了父类的finalized 方法,这时候就会给对象一次救赎的机会,但是也就仅此一次,当对象第二次“死亡”的时候,我们发现,即使是覆盖了finalized方法也没有办法再去拯救这个对象了。

      从上面的例子我们可以很直白的理解这个finalized方法在垃圾回收中起到的作用,那就是一次且仅一次救赎的机会。

方法区的回收

实际上方法区还是会进行垃圾回收,但是效率远远比不上堆中垃圾的回收,因此在垃圾回收中经常会被忽略掉。方法区中,垃圾收集的主要内容分为两部分:废弃常量和无用的类

1. 废弃常量

这部分我们会比较容易理解,就举一个简单的例子,String str= "abc";这时候abc就已经进入了常量池中,当str改变类值,那么久没有对象引用"abc"这个常量,这时候,"abc"就会被系统清理出常量池。常量池中的其他类、方法、字段的符号引用也是如此

2. 无用的类

判定一个类是否是无用的类的条件相对苛刻许多。类需要同时满足以下三个条件才能算是“无用的类”

1。该类所有的实例都已经被回收,也就是Java堆中已经不存在该类的任何实例

2。加载该类的ClassLoader已经被回收

3。该类对象的Java.lang.Class 对象没有在任何地方呗引用,无法再任何地方通过反射访问该类

虚拟机可以对满足上述3个条件的无用类对象进行回收,这里说的仅仅是“可以”,行不是和对象一样,不适用就必然回收。

需要对HotSpot虚拟机的参数进行设置,控制回收。

一般情况下我们不会对方法区的无用类进行回收,但是在大量使用反射,动态代理、CGLib等ByteCode框架、这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏你不就像风一样

你知道Java的四种引用类型吗

在Java中提供了四个级别的引用:强引用,软引用,弱引用和虚引用。在这四个引用类型中,只有强引用FinalReference类是包内可见,其他三种引用类型均为p...

942
来自专栏武军超python专栏

2018年8月26日python中的垃圾回收机制(Garbage Collection:GC)

垃圾回收机制(Garbage Collection:GC)基本是所有高级语言的标准配置之一了

1384
来自专栏JMCui

终结 finalize()和垃圾回收(garbage collection)

1、为什么要有finalize()方法?     假定你的对象(并非使用new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,...

3017
来自专栏Java爬坑系列

【Java入门提高篇】Day1 抽象类

  基础部分内容差不多讲解完了,今天开始进入Java提高篇部分,这部分内容会比之前的内容复杂很多,希望大家做好心理准备,看不懂的部分可以多看两遍,仍不理解的部分...

1846
来自专栏编程心路

Java虚拟机内存管理(四)—垃圾回收

Java 虚拟机作为运行 Java 程序抽象出来的计算机,具有内存管理的能力,像内存分配、垃圾回收等这些相关的内存管理问题,Java 虚拟机都会帮我们解决,所以...

942
来自专栏coder修行路

《深入理解计算机系统》阅读笔记--信息的表示和处理(上)

2013
来自专栏GopherCoder

『Go 内置库第一季:strings』

这一季的系列主题,主要围绕内置库进行。如果一个人写的代码比你的赞,那么他对内置库的熟悉程度一定比你强。

1033
来自专栏小樱的经验随笔

【python进阶】Garbage collection垃圾回收1

前言 GC垃圾回收在python中是很重要的一部分,同样我将分两次去讲解Garbage collection垃圾回收,此篇为Garbage collection...

3027
来自专栏Java技术栈

Java对象引用四个级别(强、软、弱、虚)

最近,高级Java技术栈微信群中,有一些猿友在讨论JVM中对象的周期问题,有谈到引用的级别,现在为大家做个总结吧,虽然大多数公司并没有意识或者用到这些引用,但了...

42613
来自专栏TungHsu

这或许是对小白最友好的python入门了吧——3,数字处理

昨天那篇推文是不是太长了,以后为了大家能够更好地理解,所以我们还是写的少点,大家看完以后要好好练习哦! 不要在此处直接复制代码! 在Python中可以执行对整...

3026

扫码关注云+社区