Java引用概述
StrongReference(强引用) 不存在这个类 默认实现
Java.lang.ref提供了与 Java垃圾回收器密切相关的引用类。SoftReference(软引用),WeakReference(弱引用),PhantomReference(虚引用)。这四种引用的强度按照上面的顺序依次减弱.
一、强引用(StrongReference)
-就是指在程序代码中普遍存在的,类似Object obj = new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。 我们一般都是使用强引用来对对象进行引用。如:
String tag = new String("T");
此处的 tag 引用就称之为强引用。而强引用有以下特征:
- 强引用可以直接访问目标对象。
- 强引用所指向的对象在任何时候都不会被系统回收。
- 强引用可能导致内存泄漏
只有显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。
二、软引用(SoftReference)
是用来描述一些还有用但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
对于软引用关联着的对象,如果内存充足,则垃圾回收器不会回收该对象,如果内存不够了,就会回收这些对象的内存。在 JDK 1.2 之后,提供了 SoftReference 类来实现软引用。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
demo
public class SoftRefTest {
private static ReferenceQueue<MyObject> softQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) softQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("Object for SoftReference is " + obj.get());
}
}
}
// -Xmx5M -XX:+PrintGCDetails
public static void main(String[] args) {
MyObject object = new MyObject();
SoftReference<MyObject> softRef = new SoftReference<>(object, softQueue);
new Thread(new CheckRefQueue()).start();
object = null; //删除强引用
System.gc();
System.out.println("After GC: Soft Get= " + softRef.get());
System.out.println("分配大块内存");
byte[] b = new byte[5 * 1024 * 928];
System.out.println("After new byte[]:Soft Get= " + softRef.get());
System.gc();
}
}
out:
[GC (Allocation Failure) [PSYoungGen: 1024K->481K(1536K)] 1024K->513K(5632K), 0.0038919 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (System.gc()) [PSYoungGen: 682K->481K(1536K)] 714K->537K(5632K), 0.0035611 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 481K->0K(1536K)] [ParOldGen: 56K->498K(4096K)] 537K->498K(5632K), [Metaspace: 2717K->2717K(1056768K)], 0.0068592 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
After GC: Soft Get= I am MyObject
分配大块内存
[GC (Allocation Failure) [PSYoungGen: 40K->32K(1536K)] 539K->530K(5632K), 0.0006437 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 32K->64K(1536K)] 530K->562K(5632K), 0.0004163 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 64K->0K(1536K)] [ParOldGen: 498K->488K(4096K)] 562K->488K(5632K), [Metaspace: 2718K->2718K(1056768K)], 0.0070447 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 488K->488K(5632K), 0.0006157 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
[PSYoungGen: 0K->0K(1536K)] [ParOldGen: 488K->476K(4096K)] 488K->476K(5632K), [Metaspace: 2718K->2718K(1056768K)], 0.0057286 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
MyObject's finalize called
Object for SoftReference is null
Heap
PSYoungGen total 1536K, used 61K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 1024K, 6% used [0x00000007bfe00000,0x00000007bfe0f740,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 4096K, used 476K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
object space 4096K, 11% used [0x00000007bfa00000,0x00000007bfa77338,0x00000007bfe00000)
Metaspace used 2749K, capacity 4490K, committed 4864K, reserved 1056768K
class space used 294K, capacity 386K, committed 512K, reserved 1048576K
at com.gxgeek.javabasic.ref.SoftRefTest.main(SoftRefTest.java:55)
构造MyObject对象,并将其赋值给object变量,构成强引用。然后使用SoftReference构造这个MyObject对象的软引用softRef,并注册到softQueue引用队列。当softRef被回收时,会被加入softQueue队列。设置obj=null,删除这个强引用,因此,系统内对MyObject对象的引用只剩下软引用。此时,显示调用GC,通过软引用的get()方法,取得MyObject对象的引用,发现对象并未被回收,这说明GC在内存充足的情况下,不会回收软引用对象。
接着,请求一块大的堆空间,这个操作会使系统堆内存使用紧张,从而产生新一轮的GC。在这次GC后,softRef.get()不再返回MyObject对象,而是返回null,说明在系统内存紧张的情况下,软引用被回收。软引用被回收时,会被加入注册的引用队列。
二、弱引用(SoftReference)
WeakReference 是弱于 SoftReference 的引用类型。弱引用的特性和基本与软引用相似,区别就在于弱引用所指向的对象只要进行系统垃圾回收,不管内存使用情况如何,永远对其进行回收(get() 方法返回 null)。
弱引用有以下特征:
- 弱引用使用 get()方法取得对象的强引用从而访问目标对象。
- 一旦系统内存回收,无论内存是否紧张,弱引用指向的对象都会被回收。
- 弱引用也可以避免 Heap 内存不足所导致的异常。
public class WeakRefTest {
private static ReferenceQueue<MyObject> weakQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) weakQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("删除的弱引用为:" + obj + " but获取弱引用的对象obj.get()=" + obj.get());
}
}
}
public static void main(String[] args) {
MyObject object = new MyObject();
Reference<MyObject> weakRef = new WeakReference<>(object, weakQueue);
System.out.println("创建的弱引用为:" + weakRef);
new Thread(new CheckRefQueue()).start();
object = null;
System.out.println("Before GC: Weak Get= " + weakRef.get());
System.gc();
System.out.println("After GC: Weak Get= " + weakRef.get());
}
}
out:
创建的弱引用为:java.lang.ref.WeakReference@2503dbd3
Before GC: Weak Get= I am MyObject
After GC: Weak Get= null
MyObject's finalize called
删除的弱引用为:java.lang.ref.WeakReference@2503dbd3 but获取弱引用的对象obj.get()=null
可以看到,在GC之前,弱引用对象并未被垃圾回收器发现,因此通过 weakRef.get()可以获取对应的对象引用。但是只要进行垃圾回收,弱引用一旦被发现,便会立即被回收,并加入注册引用队列中。此时再试图通过weakRef.get()获取对象的引用就会失败。
Java 虚引用
public class PhantomRefTest {
private static ReferenceQueue<MyObject> phanQueue = new ReferenceQueue<>();
public static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
public static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) phanQueue.remove();
System.out.println("删除的虚引用为:" + obj + " but获取虚引用的对象obj.get()=" + obj.get());
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
Reference<MyObject> phanRef = new PhantomReference<>(object, phanQueue);
System.out.println("创建的虚引用为:" + phanRef);
new Thread(new CheckRefQueue()).start();
object = null;
TimeUnit.SECONDS.sleep(1);
int i = 1;
while (true) {
System.out.println("第" + i++ + "次gc");
System.gc();
TimeUnit.SECONDS.sleep(1);
}
}
}
out:
创建的虚引用为:java.lang.ref.PhantomReference@2503dbd3
第1次gc
MyObject's finalize called
第2次gc
删除的虚引用为:java.lang.ref.PhantomReference@2503dbd3 but获取虚引用的对象obj.get()=null
PhantomReference是所有“弱引用”中最弱的引用类型。不同于软引用和弱引用,虚引用无法通过 get() 方法来取得目标对象的强引用从而使用目标对象,观察源码可以发现 get() 被重写为永远返回 null。
那虚引用到底有什么作用?其实虚引用主要被用来 跟踪对象被垃圾回收的状态,通过查看引用队列中是否包含对象所对应的虚引用来判断它是否 即将被垃圾回收,从而采取行动。它并不被期待用来取得目标对象的引用,而目标对象被回收前,它的引用会被放入一个 ReferenceQueue 对象中,从而达到跟踪对象垃圾回收的作用。
引用类型 | 取得目标对象方式 | 垃圾回收条件 | 是否可能内存泄漏 |
---|---|---|---|
强引用 | 直接调用 | 不回收 | 可能 |
软引用 | 通过 get() 方法 | 视内存情况回收 | 不可能 |
弱引用 | 通过 get() 方法 | 永远回收 | 不可能 |
虚引用 | 无法取得 | 不回收 | 可能 |
FinalReference 以及 Finzlizer
st=>start: Reference
e=>end: Finalizer
op=>operation: FinalReference
st->op->e
FinalReference 作为 java.lang.ref 里的一个不能被公开访问的类,又起到了一个什么样的作用呢?作为他的子类, Finalizer 又在垃圾回收机制里扮演了怎么样的角色呢?
实际上,FinalReference 代表的正是 Java 中的强引用,如这样的代码 :
Bean bean = new Bean();
在虚拟机的实现过程中,实际采用了 FinalReference 类对其进行引用。而 Finalizer,除了作为一个实现类外,更是在虚拟机中实现一个 FinalizerThread,以使虚拟机能够在所有的强引用被解除后实现内存清理。
让我们来看看 Finalizer 是如何工作的。首先,通过声明 FinalizerThread,并将该线程实例化,设置为守护线程后,加入系统线程中去。
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
在 GC 的过程中,当一个强引用被释放,由系统垃圾收集器标记后的对象,会被加入 Finalizer 对象中的 ReferenceQueue 中去,并调用 Finalizer.runFinalizer() 来执行对象的 finalize 方法。
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
private void runFinalizer(JavaLangAccess jla) {
synchronized (this) {
if (hasBeenFinalized()) return;
remove();
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee);
/* Clear stack slot containing this variable, to decrease
the chances of false retention with a conservative GC */
finalizee = null;
}
} catch (Throwable x) { }
super.clear();
}
注意,标记处所调用的 invokeFinalizeMethod 为 native 方法,由于 finalize 方法在 Object 类中被声明为 protected,这里必须采用 native 方法才能调用。随后通过将本地强引用设置为空,以便使垃圾回收器清理内存。
可以看到,通过这样的方法,Java 将四种引用对象类型:软引用 (SoftReference),弱引用 (WeakReference),强引用 (FinalReference),虚引用 (PhantomReference) 平等地对待,并在垃圾回收器中进行统一调度和管理。
何时注册(实例化FinalReference)
JVM在类加载的时候会遍历当前类的所有方法,包括父类的方法,只要有一个参数为空且返回void的非空finalize方法就认为这个类在创建对象的时候需要进行注册。
对象的创建其实是被拆分成多个步骤,注册的时机可以在为对象分配好内存空间后,也可以在构造函数返回之前,这个点由-XX:-RegisterFinalizersAtInit控制,这个参数默认为true,即:在构造函数返回之前调用。注册入口是Finalizer的register()方法。
GC回收问题
对象因为Finalizer的引用而变成了一个临时的强引用,即使没有其他的强引用,还是无法立即被回收;
对象至少经历两次GC才能被回收,因为只有在FinalizerThread执行完了f对象的finalize方法的情况下才有可能被下次GC回收,而有可能期间已经经历过多次GC了,但是一直还没执行对象的finalize方法;
CPU资源比较稀缺的情况下FinalizerThread线程有可能因为优先级比较低而延迟执行对象的finalize方法;
因为对象的finalize方法迟迟没有执行,有可能会导致大部分f对象进入到old分代,此时容易引发old分代的GC,甚至Full GC,GC暂停时间明显变长,甚至导致OOM;
对象的finalize方法被调用后,这个对象其实还并没有被回收,虽然可能在不久的将来会被回收。
finalizer的生存周期
- 在创建对象时,如果对象override了finalize()方法,jvm会同时创建一个Finalizer对象
- 所有Finalizer对象组成了一个双向链表
- 所有Finalizer对象都有一个名为queue的成员变量,指向的都是Finalizer类的静态Queue。
- cms gc执行到mark阶段的最后时,会把需要gc的对象加入到Reference的pending list中。
- 有一个专门的高级别线程Reference Handler处理pending list,把pending list中的对象取出来,放到这个对象所指的Reference Queue中,对于Finalizer对象来说,这个queue指向Finalizer类的静态Queue。
- Finalizer类有一个专门的线程负责从queue中取对象,并且执行finalizer引用的对象的finalize函数。