今天看看垃圾收集和内存分配策略。垃圾收集无非就是清除垃圾,释放内存,而JVM就像一个大管家,不需要你这位当家的介入,他把垃圾清理这个活给揽下了,你只需要创建对象,生产垃圾,JVM这位大管家来打扫。前面讲了JVM的运行时数据区,这些区域啊,也就相当于你这位当家的四合院。有院子,有单间。院子是公共区域(堆+方法区),单间是私有区域,每个单间都有(虚拟机栈+本地方法栈+程序计数器)这些标配。
差点忘了,每个单间还有个扫地机器人,每当你出门(方法退出)时,它自个儿就把卫生给打扫了。
那JVM大管家主要清理的是哪块儿区域呢?公共区域(方法区+堆)。
老管家在清理院子垃圾的时候,也遇到了问题,哪些是垃圾?
这不能靠猜,哪天把老东家的赏物当成垃圾扔了可不好。
问题1:怎么判断哪些需要清理?
方法1:引用计数
老管家就判断一个对象,有没有被使用,有人使用的话,就把记数器加个1,有人不使用的话,就把计数器减个1。当这个对象没人使用,计数为0的时候,就清除它。
问题:循环引用
看下下面的代码
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
objA和objB都已不在使用,需要回收,但各个的引用计数都为1,不为0,没法清除。。。
方法2:可达性分析
选中一些GC roots作为起点,当一个对象没有任何路径(引用链)可以到达GC roots时,则判断这个对象不可用,进行回收。如下图,A/B/C/D可由GC Roots到达,故不回收;E/F/G没有路径可以由GC Roots到达,故可回收。
补充:哪些对象可以作为GC Roots
-
虚拟机栈中的引用对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中JNI引用的对象
综上,无论是引用计数还是可达性分析都提到了引用,何为引用?引用类型是只有一种还是有多种?下面就来聊聊。
问题2:聊聊引用
引用的定义:引用是一个地址,这个地址表示一块内存的起始地址。
判断对象是否可回收的算法中,都提到了引用,可以根据回收的需要,将引用细分为几个类型。
-
强引用
Object A = new Object(); A就是强引用,只要A这种引用关系存在,GC就不会回收被A引用的对象;
-
软引用
软引用主要用于表示有用但不必需的对象,通俗点说就是,可要可不要的对象,用SofReference表示。在系统要发生内存溢出(OOM)时,注意是要发生,但还没有发生,这个时候,会把这些软引用,引用的对象回收掉,如果回收了,但还不够,再抛出OOM异常。
-
弱引用
弱引用是比软引用更弱的一种引用,弱到什么程度,在下一次发生垃圾回收时,就会被回收,没有内存快溢出这个前提条件。用WeakReference表示。
-
虚引用
这个引用和奇特,这个引用获得不了对象的引用,只是为了在垃圾回收时收到一个系统通知,用PhantomReference表示。
知识点:不可达对象的自救
当对象被判定为不可达对象后,是不是就一定非清除不可呢?
答案:不是,不可达对象可以通过覆写finalize()方法,来自救。
上面聊的都是堆内存的回收,下面聊聊方法区的回收。
问题3:方法区的回收
我们知道方法区,主要用于存放类信息、常量、静态变量。
方法区回收的自然也是这些东东。
方法区的回收率远小于堆的回收率。
比如我们定义的常量“ABCD”没有被任何String对象引用,垃圾回收就会把这个常量从常量池中移除。
判定一个类是否可以被移除,需要满足下面几个条件:
-
该类所有的实例都已被回收
-
加载该类的ClassLoader已被回收
-
该类的Class对象没有被任何地方引用,无法在任何地方通过反射访问该类的方法。
满足上述条件,表明这个类可以被回收,而不是必然会被回收。
是否回收,Hotspot使用-Xnoclassgc控制。
今天就先讲到这儿^_^。