如何判断一个引用是否存活
引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时间计数器为 0 的对象就是不可能再被使用的。
长处:可即刻采取垃圾,当对象计数为0时,会立刻采取;
弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放。JVM不用这种算法
可达性分析算法
通过 GC Root 对象为起点,从这些节点向下搜索,搜索所走过的路径叫引用链,当一个对象到 GC Root没有任何的引用链相连时,说明这个对象是不可用的。
- JVM中的垃圾采取器通过可达性分析来探索全部存活的对象
- 扫描堆中的对象,看可否沿着GC Root对象为起点的引用链找到该对象,如果找不到,则表示可以采取
GC Root的对象有哪些?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象,例如各个线程被调用的方法栈用到的参数、局部变量或者临时变量等。
- 方法区中类静态属性引用的对象或者说Java类中的引用范例的静态变量。
- 方法区中常量引用的对象或者运行时常量池中的引用范例变量。
- 本地方法栈中JNI(即一样平常说的Native方法)引用的对象
- JVM内部的内存数据结构的一些引用、同步的监控对象(被修饰同步锁)。
方法区的采取
因为方法区主要存放永久代对象,而永久代对象的采取率比新生代低很多,因此在方法区上举行采取性价比不高。
主要是对常量池的采取和对类的卸载。
在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGi 这类频繁自定义 ClassLoader 的场景都必要虚拟机具备类卸载功能,以保证不会出现内存溢出。
类的卸载条件很多,必要满意以下三个条件,并且满意了也不肯定会被卸载:
- 该类全部的实例都已经被采取,也就是堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被采取。
- 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
可以通过 -Xnoclassgc 参数来控制是否对类举行卸载。
finalize()
finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。(Java 9中已弃用)
当一个对象可被采取时,如果必要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。自救只能举行一次,如果采取的对象之前调用了 finalize() 方法自救,背面采取时不会调用 finalize() 方法。
引用范例
四个引用的特点:
- 强引用:gc时不会采取
- 软引用:只有在内存不够用时,gc才会采取
- 弱引用:只要gc就会采取
- 虚引用:是否采取都找不到引用的对象,仅用于管理直接内存
强引用
平常常见的- Object object = new Object();
复制代码 只要一个对象有强引用,垃圾采取器就不会举行采取。即便内存不够了,抛出OutOfMemoryError异常也不会采取。因此强引用是造成java内存走漏的主要缘故原由之一。 对于一个平凡的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相 应(强)引用赋值为 null,就是可以被垃圾网络的了,详细采取时机还是要看垃圾网络策略。
- /**
- * 一个对象
- * 重写finalize方法,可以知道已经被回收的状态
- */
- public class OneObject {
- @Override
- protected void finalize() throws Throwable {
- System.out.println("啊哦~OneObject被回收了");
- }
- }
- /**
- * 强引用例子
- */
- public class ShowStrongReference {
- public static void main(String[] args) {
- // 直接new一个对象,就是强引用
- OneObject oneObject = new OneObject();
- System.out.println("输出对象地址:" + oneObject);
- System.gc();
- System.out.println("第一次gc后输出对象地址:" + oneObject);
- oneObject = null;
- System.gc();
- System.out.println("置为null后gc输出对象地址:" + oneObject);
- }
- }
- //输出:
- 输出对象地址:com.esparks.pandora.learning.references.OneObject@72ea2f77
- 第一次gc后输出对象地址:com.esparks.pandora.learning.references.OneObject@72ea2f77
- 置为null后gc输出对象地址:null
- 啊哦~OneObject被回收了
复制代码 软引用
特点:软引用通过java.lang.SoftReference类实现。只有在内存不够用时,gc才会采取
软引用的生命周期比强引用短一些。只有当 JVM 认为内存不敷时,才会去试图采取软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清算软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)团结使用,如果软引用所引用的对象被垃圾采取器采取,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被采取。如果队列为空,将返回一个null;否则该方法返回队列中前面的一个Reference对象。- SoftReference<OneObject> oneObjectSr = new SoftReference<>(new OneObject());
复制代码
当内存充足的时间,垃圾采取器不会举行采取。当内存不够时,就会采取只存在软引用的对象释放内存。
常用于本地缓存处理。- /** * 软引用 * 内存不够了就会采取 * 注意,运行时必要保证heap大小为35m,即小于实行中全部对象的大小,才能触发gc * -Xmx35m * */public class ShowSoftReference { public static void main(String[] args) { // 我们必要通过SoftReference来创建软引用 SoftReference<OneObject> oneObjectSr = new SoftReference<>(new OneObject()); // 我们这里创建一个大小为20m的数组 SoftReference arraySr = new SoftReference(new byte[1024 * 1024 * 20]); System.out.println("软引用对象oneObjectSr的地点:" + oneObjectSr); System.out.println("通过oneObjectSr关联的oneObject对象的地点:" + oneObjectSr.get()); System.out.println("数组的地点:" + arraySr); System.out.println("通过arraySr关联的byte数组的地点:" + arraySr.get()); System.gc(); System.out.println("正常gc一次之后,oneObject对象并没有采取。地点" + oneObjectSr.get()); // 再创建另一个大小为20m的数组,这样heap就不够大了,从而系统自动gc。如果依旧不够,会把已有的软引用关联的对象都采取掉。 System.out.println("创建另一个大小为20m的数组otherArray"); byte[] otherArray = new byte[1024 * 1024 * 20]; System.out.println("otherArray的地点:" + otherArray); // gc后,软引用对象还在,但是通过软引用对象创建的对象就被采取了 System.out.println("现在软引用对象oneObjectSr的地点:" + oneObjectSr); System.out.println("通过oneObjectSr关联的oneObject对象的地点:" + oneObjectSr.get()); System.out.println("现在数组的地点:" + arraySr); System.out.println("现在arraySr中关联的byte数组的地点:" + arraySr.get()); }}
复制代码 执行代码,可以看到以下输出:- 软引用对象oneObjectSr的地址:java.lang.ref.SoftReference@4f8e5cde
- 通过oneObjectSr关联的oneObject对象的地址:test.niuke.Test1$OneObject@504bae78
- 数组的地址:java.lang.ref.SoftReference@3b764bce
- 通过arraySr关联的byte数组的地址:[B@759ebb3d
- 正常gc一次之后,oneObject对象并没有回收。地址test.niuke.Test1$OneObject@504bae78
- 创建另一个大小为20m的数组otherArray
- otherArray的地址:[B@484b61fc
- 现在软引用对象oneObjectSr的地址:java.lang.ref.SoftReference@4f8e5cde
- 通过oneObjectSr关联的oneObject对象的地址:null
- 现在数组的地址:java.lang.ref.SoftReference@3b764bce
- 现在arraySr中关联的byte数组的地址:null
复制代码 弱引用
特点:弱引用通过WeakReference类实现。只要gc就会采取
弱引用的生命周期比软引用短。在垃圾采取器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间充足与否,都会采取它的内存。由于垃圾采取器是一个优先级很低的线程,因此不肯定会很快采取弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)团结使用,如果弱引用所引用的对象被垃圾采取,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。- WeakReference<OneObject> oneObjectWr = new WeakReference<>(new OneObject());
复制代码
只要发生gc,就会采取只存在弱引用的对象。
常用于Threadlocal。- /**
- * 弱引用
- * 只要gc就会回收
- */
- public class ShowWeakReference {
- public static void main(String[] args) {
- // 我们需要通过WeakReference来创建弱引用
- WeakReference<OneObject> objectWr = new WeakReference<>(new OneObject());
- System.out.println("弱引用objectWr的地址:" + objectWr);
- System.out.println("弱引用objectWr关联的oneObject对象的地址:" + objectWr.get());
- System.gc();
- // gc后,弱引用对象还在,但是通过弱引用对象创建的对象就被回收了
- System.out.println("gc后,弱引用objectWr的地址:" + objectWr);
- System.out.println("gc后,弱引用objectWr关联的oneObject对象的地址:" + objectWr.get());
- }
- }
复制代码 执行代码,可以看到以下输出:- 弱引用objectWr的地址:java.lang.ref.WeakReference@72ea2f77
- 弱引用objectWr关联的oneObject对象的地址:com.esparks.pandora.learning.references.OneObject@33c7353a
- gc后,弱引用objectWr的地址:java.lang.ref.WeakReference@72ea2f77
- gc后,弱引用objectWr关联的oneObject对象的地址:null
- 啊哦~OneObject被回收了
复制代码 虚引用
特点:虚引用也叫幻象引用,通过PhantomReference类来实现。是否采取都找不到引用的对象,仅用于管理直接内存
无法通过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 以后,做某些事情的机制。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时间都可能被垃圾采取器采取。虚引用必须和引用队列 (ReferenceQueue)团结使用。当垃圾采取器准备采取一个对象时,如果发现它还有虚引用,就会在采取对象的内存之前,把这个虚引用加入到与之关联的引用队列中。 程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾采取。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被采取之前采取一些程序行动。- private ReferenceQueue<OneObject> queue = new ReferenceQueue<>();
- PhantomReference<OneObject> oneObjectPr = new PhantomReference<>(new OneObject(), queue);
复制代码 无论是否gc,其实都获取不到通过PhantomReference创建的对象。
其仅用于管理直接内存,起到通知的作用。
这里补充一下背景。因为垃圾采取器只能管理JVM内部的内存,无法直接管理系统内存的。对于一些存放在系统内存中的数据,JVM会创建一个引用(类似于指针)指向这部分内存。
当这个引用在采取的时间,就必要通过虚引用来管理指向的系统内存。这里还必要依赖一个队列来实现。当触发gc对一个虚引用对象采取时,会将虚引用放入创建时指定的ReferenceQueue中。之后单独对这个队列举行轮询,并做额外处理。
[code]/** * 虚引用 * 只用于管理直接内存,起到通知的作用 */public class ShowPhantomReference { /** * 虚引用必要的队列 */ private static final ReferenceQueue QUEUE = new ReferenceQueue(); public static void main(String[] args) { // 我们必要通过 PhantomReference来创建虚引用 PhantomReference objectPr = new PhantomReference(new OneObject(), QUEUE); System.out.println("虚引用objectPr的地点:" + objectPr); System.out.println("虚引用objectPr关联的oneObject对象的地点:" + objectPr.get()); // 触发gc,然后检查队列中是否有虚引用 while (true) { System.gc(); Reference |