1.垃圾回收概述
随着程序的不断运行,程序所产生的对象必将越来越多,而系统的内存则是有限的,所以,将没有用的对象进行清除是程序长期稳定运行的关键.
垃圾回收主要关注三个问题
- 什么对象应该被回收?
当然是没有用的对象.当对象不再被引用时,我们认为该对象应该被回收.如何判断对象是否还被引用,会在后面详述. - 对象应该在什么时间被回收?
程序在运行过程中,对象的引用关系是一直变化的,如何选择合适的时机开始GC,也是一个重要的问题,后面详述. - 应该怎样回收?
当我们知道是无用对象后,如何将无用对象清除,保留有用对象,将是垃圾回收算法主要关注的问题,将在下一节详述.
2.判断对象是否被引用
主要有两种办法判断对象是否被引用.
- 引用计数器法
顾名思义,为每个对象定义一个计数器,每当对象被引用一次时,程序计数器+1,当引用被销毁一次,计数器-1,如果计数器值为0的时,则对象无用.
此方法有一个缺点,就是无法解决对象之间的相互引用问题.
如:objA.instance = objB; objB.instance = objA;
则这两个对象将永远无法回收.
- 可达性分析法
选择一些根节点(GC Roots)对象,向下进行引用查找,查找走过的路径则称为引用链,如果一个对象没有一条引用链可以达到它,则它为无用对象.如图所示.
如GCRoot引用到了A对象。
A对象引用到了B对象。
则A、B对象都不可以回收。
3.选择合适的时间开始GC
- 由于程序运行过程中,对象的引用关系一直在发生变化,所以我们需要等到所有线程停止执行(stop the world)才能开始进行.
- JVM准备GC之前,会将一个标志位设置为真,每个线程都会去检查这个标志位,如果发现为真,则停止往下执行,并将当前线程栈中引用(局部变量)的对象作为GC Roots,向下查找引用链,并存入一个oopMap中.
注:哪些对象可以作为GC Roots?
- 静态变量
- 常量
- 线程栈中引用(局部变量)引用的对象
- 如果线程每执行一条指令就去检查标志位,显然太过浪费,此时就让线程多走几步到某些位置上,再开始进行可达性分析,这些位置称为安全点.
注:一般将哪些位置作为安全点?
- 方法return之后
- 循环单次结束后
- 发生异常准备跳转catch前.
- 当线程正在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概述,如何回收对象
原文地址:https://www.cnblogs.com/guan-li/p/11505597.html
时间: 2024-10-08 17:43:07