举例说明如何创建软引用对象。
在 Java 中,软引用是通过 java.lang.ref.SoftReference 类来实现的。
示例代码:
java
import java.lang.ref.SoftReference;
public class SoftReferenceExample {
public static void main(String[] args) {
// 1. 创建一个普通对象
MyLargeObject strongRefObject = new MyLargeObject("Initial Data");
System.out.println("原始对象 (强引用) 创建成功: " + strongRefObject.getData());
// 2. 创建一个软引用指向这个对象
// SoftReference<MyLargeObject> softRef = new SoftReference<>(strongRefObject);
// 在创建软引用后,可以将强引用置为 null,这样 MyLargeObject 实例就只剩下软引用了
SoftReference<MyLargeObject> softRef = new SoftReference<>(strongRefObject);
strongRefObject = null; // 将强引用置为 null
System.out.println("软引用创建成功。");
// 3. 尝试通过软引用获取对象
MyLargeObject retrievedObject = softRef.get();
if (retrievedObject != null) {
System.out.println("通过软引用获取到对象: " + retrievedObject.getData());
} else {
System.out.println("通过软引用获取对象失败,对象可能已被回收。");
}
// 模拟内存紧张的情况,强制进行垃圾回收
// 在实际应用中,JVM 会根据内存情况自动决定何时回收软引用对象
// 这里只是为了演示效果,强制执行 GC,但不能保证一定回收
System.out.println("\n尝试进行垃圾回收...");
System.gc(); // 提示 JVM 进行垃圾回收
try {
Thread.sleep(100); // 等待一小段时间,让GC有机会执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 4. 再次尝试通过软引用获取对象
MyLargeObject retrievedObjectAfterGC = softRef.get();
if (retrievedObjectAfterGC != null) {
System.out.println("GC 后通过软引用获取到对象: " + retrievedObjectAfterGC.getData());
} else {
System.out.println("GC 后通过软引用获取对象失败,对象已被回收。");
}
// 进一步模拟内存紧张,例如创建大量对象
System.out.println("\n模拟内存紧张,创建大量对象...");
// 尝试创建大量对象,耗尽内存,迫使JVM回收软引用对象
// 注意:这可能导致 OutOfMemoryError,具体取决于JVM的配置和可用内存
java.util.List<byte[]> memoryEater = new java.util.ArrayList<>();
try {
for (int i = 0; i < 1000; i++) { // 创建1000个1MB的字节数组
memoryEater.add(new byte[1024 * 1024]); // 1MB
}
} catch (OutOfMemoryError e) {
System.out.println("模拟内存紧张成功,发生 OutOfMemoryError。");
}
// 5. 在极度内存紧张后,再次尝试通过软引用获取对象
MyLargeObject retrievedObjectAfterOOM = softRef.get();
if (retrievedObjectAfterOOM != null) {
System.out.println("内存紧张后通过软引用获取到对象: " + retrievedObjectAfterOOM.getData());
} else {
System.out.println("内存紧张后通过软引用获取对象失败,对象已被回收。");
}
}
}
// 模拟一个较大的对象,以便垃圾回收器更容易注意到它
class MyLargeObject {
private String data;
// 假设这个对象内部还有一些其他数据,占用内存
private byte[] largeArray = new byte[1024 * 10]; // 10KB
public MyLargeObject(String data) {
this.data = data;
System.out.println("MyLargeObject created with data: " + data);
}
public String getData() {
return data;
}
@Override
protected void finalize() throws Throwable {
System.out.println("MyLargeObject (" + data + ") is being finalized.");
super.finalize();
}
}
代码解释:
-
MyLargeObject strongRefObject = new MyLargeObject("Initial Data");- 首先,我们创建了一个
MyLargeObject的实例,并用一个普通的变量strongRefObject强引用它。此时,MyLargeObject实例是强可达的。
- 首先,我们创建了一个
-
SoftReference<MyLargeObject> softRef = new SoftReference<>(strongRefObject);- 接着,我们创建了一个
SoftReference对象,并将strongRefObject作为参数传给它的构造函数。这意味着softRef现在是一个软引用,指向MyLargeObject实例。 strongRefObject = null;这一步非常关键。我们将原来的强引用 strongRefObject 置为 null。此时,MyLargeObject 实例就只剩下 softRef 这一个软引用了。这意味着它现在是"软可达"的。
- 接着,我们创建了一个
-
MyLargeObject retrievedObject = softRef.get();- 通过
softRef.get()方法,我们可以尝试获取软引用指向的实际对象。 - 如果对象还没有被垃圾回收器回收,
get()方法会返回该对象。 - 如果对象已经被回收,
get()方法会返回null。
- 通过
-
System.gc();和模拟内存紧张System.gc()只是一个建议 JVM 执行垃圾回收的操作,JVM 不一定会立即执行,也不保证会回收软引用对象。- 为了更可靠地演示软引用在内存紧张时被回收的特性,我们通常需要模拟创建大量对象来耗尽内存。当 JVM 内存不足时,它会优先回收软引用指向的对象。
运行结果分析:
- 在
strongRefObject = null;之后,MyLargeObject实例就只剩下软引用了。 - 在内存充足的情况下,
softRef.get()仍然可以获取到MyLargeObject实例。 - 当 JVM 内存紧张(例如,通过创建大量对象来模拟),垃圾回收器会回收
MyLargeObject实例。此时,再次调用softRef.get()就会返回null。 finalize()方法会在对象被垃圾回收前调用,可以观察到其输出,进一步证明对象已被回收。
这个例子展示了软引用如何在内存紧张时自动释放对象,这对于实现缓存机制非常有用,既能提高性能,又能避免 OutOfMemoryError。
手动执行 strongRefObject = null; 之后,softRef.get() 获取到的不一定是 null。
让我来详细解释一下:
-
strongRefObject = null;的作用:- 这一行代码的作用是断开 strongRefObject 这个变量对 MyLargeObject 实例的强引用。
- 它不会立即触发垃圾回收。
- 它只是使得
MyLargeObject实例现在只剩下软引用softRef指向它 。此时,该MyLargeObject实例变为了"软可达"状态。
-
软引用对象的回收时机:
- 软引用指向的对象,在内存充足时不会被回收。
- 只有当 JVM 即将耗尽内存(即内存紧张)时,垃圾回收器才会考虑回收这些软引用对象。
-
softRef.get()的行为:softRef.get()方法会尝试获取软引用指向的实际对象。- 如果该对象尚未被垃圾回收器回收 (因为内存还充足,或者垃圾回收器还没来得及回收),那么
get()方法就会返回该对象实例。 - 只有当该对象已经被垃圾回收器回收 后,
get()方法才会返回null。
所以,在 strongRefObject = null; 之后,MyLargeObject 实例虽然没有了强引用,但它仍然存在于内存中,并且通过 softRef 仍然是可访问的,直到 JVM 认为内存不足并决定回收它。
在 strongRefObject = null; 之后,以及甚至在第一次 System.gc() 之后,只要内存还充足,softRef.get() 仍然能够获取到对象。只有当模拟了内存紧张,JVM 真正开始回收软引用对象时,softRef.get() 才会返回 null。