垃圾回收和内存分配策略(一)

jvm垃圾回收算法需要考虑的问题是:哪些内存需要回收?什么时候回收?如何回收?

由于程序计数器、虚拟机栈、本地方法栈随线程而生随线程而灭,同时栈中的栈帧随着方法的进入和退出而执行着出栈和入栈操作,每一个栈帧中分配多少内存基本上是在类结构确定是就已经知道了,这三个区域的内存分配和回收都确定;而java堆和方法区则不同,我们只有在程序运行期间才能知道需要创建那些对象,这些内存的分配和回收都是动态的,因此需要回收的是堆和方法区所占的内存。

那应该在什么时候回收呢?在此需要对堆里面的对象,在垃圾收集器回收前,进行判断哪些对象在任何途径下都不可能使用。
1、 引用计数法:给对象添加一个引用计数器,每当有个地方引用该对象时,计数器就加1,引用失效,计数器就减1,当任何时刻引用计数器为0时,该对象就不可能再被使用了。
但是,至少主流的java虚拟机没有使用引用计数算法来管理内存,主要原因是它很难解决对象之间相互循环的引用。

2、可达性分析算法:主流的语言的主流实现中,都是通过可达性分析算法来判断对象是否存活。
基本思想:通过一些列的’GC Roots’的对象作为起始点,从这些节点开始往下搜索,搜索所走过的路径成为’引用链’,当一个对象到GC Roots没有任何引用链时(就是从GC Roots到这个对象不可达),则证明这个对象不可用,这些对象将被判定可回收。
这里写图片描述

如图:对象object5、object6、object7虽然互相有关联,但是它们到GC Roots 是不可大,因此它们将会被判定为 可回收的对象。

在java中,可作为GC Roots 的对象有:
1、java虚拟机栈(栈帧中的本地变量表)中引用的对象
2、方法区中类静态属性引用的对象
3、方法区中常量引用的对象。
4、本地方法栈中(一般说的Native方法)引用的对象。

jdk1.2之前,只有引用和没有被引用两种状态,jdk1.2之后,引用分为了强引用、软引用、弱引用、虚引用,引用强度依次减弱。

1、强引用:代码中普遍存在的;例如:Object object = new Object(); 只要强引用还存在,垃圾收集器就永远不会回收被引用的对象。当内存空间不足,Java虚拟机抛出OutOfMemoryError错误,使程序异常终止;

2、软引用:有用但不是必需的对象的引用,Java虚拟机会尽量让软引用的存活时间长一些,在内存将要溢出异常之前才列入回收进行第二次回收,如果清理后内存仍然不足,再抛出OutOfMemoryError错误;

3、弱引用:也是用来描述非必需对象,但是被弱引用的对象,只能存活到下一次垃圾收集之前,当垃圾收集时,无论内存是否够用,都会回收掉只被弱引用引用的对象;

4、虚引用:仅用来处理对象被回收时收到一个系统通知,Java虚拟机不负责清理虚引用。

在可达性分析中不可达的对象,这些对象不是’非死不可’(缓刑阶段),要宣告一个对象死亡,至少要经历两次标记:
1、GC Roots不可达将会进行第一次标记且进行一次筛选,筛选的条件就是此对象有没有必要执行finalize()方法,当对象没有覆盖finalize()或finalize()被虚拟机调用过,这两种情况虚拟机视为”没有必要执行”。

2、此外就是有必要执行finalize()方法(覆盖),那么这个对象就是会被放入F-Queue的队列中,finalize()方法是对象逃脱死亡的最后一次机会(与引用链上的某个对象关联起来,把对象赋值给某个类变量或对象的成员变量,将会逃脱被回收集合);稍后GC对F-Queue中的对象进行小规模的第二次标记,被两次标记对象真的要被回收了。

一个对象的自我拯救,代码如下:

/**
*此代码有两点:
*1、对象可以在GC时自我拯救
*2、只有一次机会,因为一个对象的finalize()最多被系统自动调用一次
**/
public class FinalizeEscapeGC  {


        public static FinalizeEscapeGC  SAVE_HOOK = null;  //类静态对象

        public void isAlive() {
            System.out.println("yes, i am still alive :)");
        }
        //finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("finalize mehtod executed!");
            FinalizeEscapeGC .SAVE_HOOK = this;
        }

        public static void main(String[] args) throws Throwable {
            SAVE_HOOK = new FinalizeEscapeGC ();
            //对象第一次成功拯救自己
            SAVE_HOOK = null;
            System.gc();
            // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
            Thread.sleep(500);
            if (SAVE_HOOK != null) {
                SAVE_HOOK.isAlive();
            } else{
                System.out.println("no, i am dead :(");
            }
            // 下面这段代码与上面的完全相同,但是这次自救却失败了
            SAVE_HOOK = null;
            System.gc();
            // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
            Thread.sleep(500);
            if (SAVE_HOOK != null) {
                SAVE_HOOK.isAlive();
            } else{
                System.out.println("no, i am dead :(");
            }
        }
}
运行结果:
finalize mehtod executed!  
yes, i am still alive :)  
no, i am dead :( 

从运行结果可以看到,SAVE_HOOK对象的finalize()方法确实被GC收集器触发过,并且在被收集前成功逃脱了。

另外就是,代码中有两段完全一样的代码片段,执行结果却是一次逃脱成功,一次失败,这是因为任何一个对象的finalize()方法都只会被系统自动调用一次,如果对象面临下一次回收,它的finalize()方法不会被再次执行,因此第二段代码的自救行动失败了。

需要特别说明的是,并不鼓励大家使用finalize()方法来拯救对象,建议大家尽量避免使用它,。它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序。有些教材中提到它适合做“关闭外部资源”之类的工作,这完全是对这种方法的用途的一种自我安慰。finalize()能做的所有工作,使用try-finally或其他方式都可以做得更好、更及时,大家完全可以忘掉Java语言中还有这个方法的存在。

回收方法区:

永久代垃圾收集分为两部分:废弃常量和无用的类。
回收废弃常量和回收java堆中对象非常类似,例如字符串“abc”已经进入了常量池,但是系统中没有任何一个String对象叫做“abc”,
常量没有其他地方引用就会被移除常量池(回收),方法区中的类(接口)字段等符号引用也是如此。

满足以下3个条件才算是’无用的类’:(
1:该类的所用实例都已经被回收,也就是堆中不存在该类的任何实例
2:加载该类的ClassLoader已经被回收
3:该类对应的java.lang.Class对象没有任何地方被引用,无法通过发射访问该类的方法
java虚拟机可以对这些无用类回收,是否被回收,java虚拟机提供了一些设置参数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值