码迷,mamicode.com
首页 > 编程语言 > 详细

Java虚拟机(4)-GC概述,如何回收对象

时间:2019-09-11 13:40:08      阅读:100      评论:0      收藏:0      [点我收藏+]

标签:实例   img   一个   停止   关于   com   ble   omr   建议   


1.垃圾回收概述

随着程序的不断运行,程序所产生的对象必将越来越多,而系统的内存则是有限的,所以,将没有用的对象进行清除是程序长期稳定运行的关键.

垃圾回收主要关注三个问题

  • 什么对象应该被回收?
    当然是没有用的对象.当对象不再被引用时,我们认为该对象应该被回收.如何判断对象是否还被引用,会在后面详述.

  • 对象应该在什么时间被回收?
    程序在运行过程中,对象的引用关系是一直变化的,如何选择合适的时机开始GC,也是一个重要的问题,后面详述.

  • 应该怎样回收?
    当我们知道是无用对象后,如何将无用对象清除,保留有用对象,将是垃圾回收算法主要关注的问题,将在下一节详述.


2.判断对象是否被引用

主要有两种办法判断对象是否被引用.

  • 引用计数器法
    顾名思义,为每个对象定义一个计数器,每当对象被引用一次时,程序计数器+1,当引用被销毁一次,计数器-1,如果计数器值为0的时,则对象无用.
    此方法有一个缺点,就是无法解决对象之间的相互引用问题.
    如:objA.instance = objB; objB.instance = objA;
    则这两个对象将永远无法回收.

  • 可达性分析法
    选择一些根节点(GC Roots)对象,向下进行引用查找,查找走过的路径则称为引用链,如果一个对象没有一条引用链可以达到它,则它为无用对象.如图所示.

技术图片

如GCRoot引用到了A对象。
A对象引用到了B对象。
则A、B对象都不可以回收。


3.选择合适的时间开始GC

  1. 由于程序运行过程中,对象的引用关系一直在发生变化,所以我们需要等到所有线程停止执行(stop the world)才能开始进行.

  1. JVM准备GC之前,会将一个标志位设置为真,每个线程都会去检查这个标志位,如果发现为真,则停止往下执行,并将当前线程栈中引用(局部变量)的对象作为GC Roots,向下查找引用链,并存入一个oopMap中.

注:哪些对象可以作为GC Roots?

  • 静态变量
  • 常量
  • 线程栈中引用(局部变量)引用的对象

  1. 如果线程每执行一条指令就去检查标志位,显然太过浪费,此时就让线程多走几步到某些位置上,再开始进行可达性分析,这些位置称为安全点.

注:一般将哪些位置作为安全点?

  • 方法return之后
  • 循环单次结束后
  • 发生异常准备跳转catch前.

  1. 当线程正在sleep()时,引用关系是不会发生变化的,这段时间称为安全区域.线程进入安全区域和走到安全点效果是一样的.当线程sleep()结束后,会先通过标志位判断GC是否结束,没有结束则会继续等待.

4.其他

  • 对象不可达时,如何避免被回收?
    实现finalize()后,有且仅有一次执行finalize()以逃脱回收的机会,但一般不建议使用.
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("对象仍然活着!!!");
    }

    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize()方法被执行!!!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        new FinalizeEscapeGC(); // 对象被创建
        SAVE_HOOK = null;
        System.gc(); // 回收,会执行finalize()
        Thread.sleep(500);

        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("对象已死!!!");
        }

        SAVE_HOOK = null;
        System.gc(); // 回收,finalize()已经被执行过,不会再被执行
        Thread.sleep(500);

        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("对象已死!!!");
        }
    }
}
finalize()方法被执行!!!
对象仍然活着!!!
对象已死!!!

  • 关于引用
    如果reference类型的数据中存储的是另一块内存的地址,则次reference对象为一个引用.
    按照强弱程度,引用可以分为强引用,软引用,弱引用,虚引用.

1.强引用

Object obj = new Object();  

除非栈帧清空,强引用不会被GC,即使发生OOM.

2.软引用

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
sf.get();//有时候会返回null

软引用在发生内存不足时会被回收,内存足够时何以通过get()获取.
软引用通常被用于实现缓存.

3.弱引用

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收

弱引用是在第二次垃圾回收时回收,即使内存足够.短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null

4.虚引用

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除

在垃圾回收时,虚引用一定会被回收,就和没有引用一样.


  • 方法区的垃圾回收
    垃圾回收的主要区域是堆,但方法区一样也会发生垃圾回收.一次对年轻代的回收可以回收70%-95%的空间,但永久代的回收效率要低很多.
    永久代的垃圾回收主要针对废弃常量和废弃类.

废弃常量

如"abc"如果没有任何引用,则会被回收.

废弃类
对废弃类的回收需要满足一下三个条件:
1.该类的所有实例已被回收
2.该类的ClassLoader已被回收
3.该类对应的java.lang.Class对象无任何地方被引用

注:可以通过参数-Xnoclassgc,设置是否对类进行回收.

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


Java虚拟机(4)-GC概述,如何回收对象

标签:实例   img   一个   停止   关于   com   ble   omr   建议   

原文地址:https://www.cnblogs.com/guan-li/p/11505597.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!