问题
什么是强引用、软引用、弱引用和虚引用?
答案
核心概念
Java提供了四种不同的引用类型:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。这些引用类型具有不同的强度,在垃圾回收时有不同的行为,为Java程序提供了灵活的内存管理机制。
强引用(Strong Reference)
1. 基本特性
强引用是最常见的引用类型,我们日常使用的99%的引用都是强引用。只要存在强引用指向对象,垃圾回收器就永远不会回收该对象。
public class StrongReferenceExample {
public static void main(String[] args) {
// 强引用:普通的对象引用
Object obj = new Object(); // obj是强引用
String str = "Hello"; // str是强引用
// 只要强引用存在,对象就不会被回收
obj = null; // 解除强引用,对象才可能被回收
str = null;
}
public static void demonstrateStrongReference() {
List<String> list = new ArrayList<>();
// list中的元素都是强引用
list.add("元素1");
list.add("元素2");
// 即使list本身被回收,其中的对象引用仍会阻止GC
list = null; // 只有list和其中所有元素的强引用都解除,对象才能被回收
}
}
2. 强引用的内存泄漏风险
public class MemoryLeakExample {
// 静态集合中的强引用可能导致内存泄漏
private static final Map<String, Object> CACHE = new HashMap<>();
public static void addToCache(String key, Object value) {
CACHE.put(key, value); // 强引用,对象永远不会被回收
}
// 更好的做法:使用WeakHashMap或定期清理
public static void memoryLeakScenario() {
List<Object> objects = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
objects.add(new LargeObject()); // 强引用,对象不会被回收
}
// 如果不清理objects,会造成内存泄漏
}
}
软引用(Soft Reference)
1. 基本特性
软引用用来描述一些还有用但非必需的对象。在系统内存充足时,软引用对象不会被回收;只有在内存不足时,才会回收这些对象。
import java.lang.ref.SoftReference;
import java.lang.ref.ReferenceQueue;
public class SoftReferenceExample {
public static void main(String[] args) {
// 创建软引用
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj);
// 通过软引用获取对象
Object retrievedObj = softRef.get(); // 如果对象未被回收,返回对象
System.out.println("Retrieved object: " + (retrievedObj != null));
// 清除强引用,对象只通过软引用引用
obj = null;
// 模拟内存不足的情况
System.gc(); // 可能不会立即回收
createMemoryPressure(); // 创建内存压力,触发软引用回收
retrievedObj = softRef.get(); // 可能返回null
System.out.println("After GC: " + (retrievedObj != null));
}
private static void createMemoryPressure() {
try {
// 创建大量对象消耗内存
List<byte[]> memoryConsumer = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
memoryConsumer.add(new byte[1024 * 1024]); // 1MB
}
} catch (OutOfMemoryError e) {
System.out.println("内存不足,软引用可能被回收");
}
}
}
2. 软引用的实际应用
public class SoftReferenceApplications {
// 1. 缓存实现
public static class SoftCache<K, V> {
private final Map<K, SoftReference<V>> cache = new HashMap<>();
public void put(K key, V value) {
cache.put(key, new SoftReference<>(value));
}
public V get(K key) {
SoftReference<V> ref = cache.get(key);
return ref != null ? ref.get() : null;
}
public void cleanUp() {
cache.entrySet().removeIf(entry -> entry.getValue().get() == null);
}
}
// 2. 图片缓存
public static class ImageCache {
private static final Map<String, SoftReference<byte[]>> imageCache = new HashMap<>();
public static byte[] getImage(String imagePath) {
SoftReference<byte[]> ref = imageCache.get(imagePath);
byte[] imageData = ref != null ? ref.get() : null;
if (imageData == null) {
// 从文件系统加载图片
imageData = loadImageFromFile(imagePath);
imageCache.put(imagePath, new SoftReference<>(imageData));
}
return imageData;
}
private static byte[] loadImageFromFile(String path) {
// 模拟图片加载
return new byte[1024 * 100]; // 100KB
}
}
// 3. 数据库连接池
public static class SoftConnectionPool {
private final Queue<SoftReference<Connection>> pool = new LinkedList<>();
public Connection getConnection() {
while (!pool.isEmpty()) {
SoftReference<Connection> ref = pool.poll();
Connection conn = ref.get();
if (conn != null && !conn.isClosed()) {
return conn;
}
}
return createNewConnection();
}
public void releaseConnection(Connection conn) {
if (conn != null && !conn.isClosed()) {
pool.offer(new SoftReference<>(conn));
}
}
private Connection createNewConnection() {
// 创建新连接
return null;
}
}
}
弱引用(Weak Reference)
1. 基本特性
弱引用的强度比软引用更弱,无论内存是否充足,只要垃圾回收器运行,就会回收只被弱引用引用的对象。
import java.lang.ref.WeakReference;
import java.lang.ref.ReferenceQueue;
public class WeakReferenceExample {
public static void main(String[] args) {
// 创建弱引用
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
System.out.println("Before GC: " + (weakRef.get() != null));
// 清除强引用
obj = null;
// 触发垃圾回收
System.gc();
// 弱引用对象被回收
System.out.println("After GC: " + (weakRef.get() != null));
}
// 使用ReferenceQueue监控对象回收
public static void demonstrateReferenceQueue() {
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj, queue);
obj = null; // 清除强引用
System.gc(); // 触发GC
try {
// 检查引用队列,如果对象被回收,引用会被加入队列
Reference<? extends Object> ref = queue.remove(1000);
if (ref != null) {
System.out.println("对象已被回收");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
2. WeakHashMap的实际应用
import java.util.WeakHashMap;
public class WeakHashMapExample {
public static void main(String[] args) {
// WeakHashMap使用弱引用作为键
WeakHashMap<Key, Value> map = new WeakHashMap<>();
Key key1 = new Key("key1");
Key key2 = new Key("key2");
map.put(key1, new Value("value1"));
map.put(key2, new Value("value2"));
System.out.println("Before GC: " + map.size());
// 清除强引用
key1 = null;
System.gc(); // 触发GC
// key1对应的条目被自动移除
System.out.println("After GC: " + map.size());
}
static class Key {
private String name;
public Key(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
static class Value {
private String value;
public Value(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
}
3. 弱引用的高级应用
public class WeakReferenceApplications {
// 1. 观察者模式中的弱引用
public static class WeakObserver {
private final List<WeakReference<EventListener>> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(new WeakReference<>(listener));
}
public void notifyListeners(Event event) {
Iterator<WeakReference<EventListener>> iterator = listeners.iterator();
while (iterator.hasNext()) {
WeakReference<EventListener> ref = iterator.next();
EventListener listener = ref.get();
if (listener != null) {
listener.onEvent(event);
} else {
// 监听器已被回收,移除引用
iterator.remove();
}
}
}
}
// 2. 线程本地存储的清理
public static class WeakThreadLocal<T> {
private static final Map<Thread, WeakReference<T>> threadMap = new WeakHashMap<>();
public void set(T value) {
threadMap.put(Thread.currentThread(), new WeakReference<>(value));
}
public T get() {
WeakReference<T> ref = threadMap.get(Thread.currentThread());
return ref != null ? ref.get() : null;
}
}
// 3. 对象池的弱引用实现
public static class WeakObjectPool<T> {
private final Queue<WeakReference<T>> pool = new LinkedList<>();
private final Supplier<T> factory;
public WeakObjectPool(Supplier<T> factory) {
this.factory = factory;
}
public T acquire() {
while (!pool.isEmpty()) {
WeakReference<T> ref = pool.poll();
T obj = ref.get();
if (obj != null) {
return obj;
}
}
return factory.get();
}
public void release(T obj) {
if (obj != null) {
pool.offer(new WeakReference<>(obj));
}
}
}
}
虚引用(Phantom Reference)
1. 基本特性
虚引用是最弱的引用类型,无法通过虚引用获取对象实例。虚引用的唯一目的是在对象被回收时收到系统通知,通常用于跟踪对象的回收状态。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceExample {
public static void main(String[] args) {
ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
// 创建虚引用
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, queue);
System.out.println("Phantom reference get: " + phantomRef.get()); // 总是返回null
// 清除强引用
obj = null;
System.gc(); // 触发GC
try {
// 虚引用会在对象被回收后加入队列
Reference<? extends Object> ref = queue.remove(1000);
if (ref != null) {
System.out.println("对象已被回收,虚引用进入队列");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
2. 虚引用的实际应用
public class PhantomReferenceApplications {
// 1. 直接内存管理
public static class DirectMemoryManager {
private final ReferenceQueue<ByteBuffer> queue = new ReferenceQueue<>();
private final Map<PhantomReference<ByteBuffer>, Cleaner> cleaners = new ConcurrentHashMap<>();
public ByteBuffer allocateDirect(int size) {
ByteBuffer buffer = ByteBuffer.allocateDirect(size);
PhantomReference<ByteBuffer> ref = new PhantomReference<>(buffer, queue);
// 创建清理器
Cleaner cleaner = Cleaner.create(buffer, () -> {
System.out.println("清理直接内存: " + size + " bytes");
// 这里可以执行实际的资源清理
});
cleaners.put(ref, cleaner);
// 启动清理线程
startCleanupThread();
return buffer;
}
private void startCleanupThread() {
Thread cleanupThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
Reference<? extends ByteBuffer> ref = queue.remove();
if (ref != null) {
cleaners.remove(ref);
System.out.println("检测到直接内存对象被回收");
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
cleanupThread.setDaemon(true);
cleanupThread.start();
}
}
// 2. 资源清理跟踪
public static class ResourceTracker {
private static final ReferenceQueue<Object> queue = new ReferenceQueue<>();
private static final Map<PhantomReference<Object>, String> resources = new ConcurrentHashMap<>();
static {
// 启动监控线程
new Thread(ResourceTracker::monitorResources).start();
}
public static void track(Object resource, String description) {
PhantomReference<Object> ref = new PhantomReference<>(resource, queue);
resources.put(ref, description);
}
private static void monitorResources() {
try {
while (!Thread.currentThread().isInterrupted()) {
PhantomReference<?> ref = (PhantomReference<?>) queue.remove();
String description = resources.remove(ref);
System.out.println("资源被回收: " + description);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 3. 对象生命周期监控
public static class ObjectLifecycleMonitor {
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
private final Map<PhantomReference<Object>, Long> creationTimes = new ConcurrentHashMap<>();
public void monitor(Object obj) {
PhantomReference<Object> ref = new PhantomReference<>(obj, queue);
creationTimes.put(ref, System.currentTimeMillis());
startMonitoring();
}
private void startMonitoring() {
Thread monitorThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
PhantomReference<?> ref = (PhantomReference<?>) queue.remove();
Long createTime = creationTimes.remove(ref);
if (createTime != null) {
long lifetime = System.currentTimeMillis() - createTime;
System.out.println("对象生命周期: " + lifetime + "ms");
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
monitorThread.setDaemon(true);
monitorThread.start();
}
}
}
四种引用对比总结
1. 引用强度对比
public class ReferenceStrengthComparison {
public static void demonstrateReferenceHierarchy() {
Object obj = new Object();
// 强引用:永远不会被回收
Object strongRef = obj;
// 软引用:内存不足时才被回收
SoftReference<Object> softRef = new SoftReference<>(obj);
// 弱引用:GC运行时就被回收
WeakReference<Object> weakRef = new WeakReference<>(obj);
// 虚引用:无法获取对象,仅用于跟踪回收
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, new ReferenceQueue<>());
// 引用强度:强引用 > 软引用 > 弱引用 > 虚引用
}
}
2. 使用场景总结
public class ReferenceUsageSummary {
/*
* 强引用 (Strong Reference):
* - 用途:普通对象引用
* - 回收时机:永不回收(除非所有强引用都解除)
* - 使用场景:日常编程,确保对象不被意外回收
* - 注意:容易造成内存泄漏
*/
/*
* 软引用 (Soft Reference):
* - 用途:缓存实现
* - 回收时机:内存不足时
* - 使用场景:图片缓存、数据缓存、连接池
* - 优点:平衡内存使用和性能
*/
/*
* 弱引用 (Weak Reference):
* - 用途:临时对象引用
* - 回收时机:下次GC时
* - 使用场景:WeakHashMap、观察者模式、对象池
* - 优点:及时回收,避免内存泄漏
*/
/*
* 虚引用 (Phantom Reference):
* - 用途:对象回收跟踪
* - 回收时机:对象被回收后
* - 使用场景:直接内存管理、资源清理、生命周期监控
* - 特点:无法通过虚引用获取对象
*/
}
面试要点总结
- 四种引用强度:强引用 > 软引用 > 弱引用 > 虚引用
- 回收时机:强引用(永不)、软引用(内存不足)、弱引用(GC时)、虚引用(回收后通知)
- 使用场景:强引用(普通对象)、软引用(缓存)、弱引用(临时对象)、虚引用(资源跟踪)
- ReferenceQueue:用于监控对象回收状态
- 实际应用:缓存实现、WeakHashMap、直接内存管理、对象生命周期监控
- 性能考虑:合理使用引用类型可以优化内存使用,避免内存泄漏
关键理解:
- 不同引用类型提供了灵活的内存管理策略
- 软引用和弱引用是实现高效缓存的重要工具
- 虚引用用于跟踪对象回收,而不是访问对象
- 合理使用引用类型可以在性能和内存使用之间找到平衡
编程建议:
- 缓存系统优先考虑软引用
- 临时对象使用弱引用避免内存泄漏
- 资源清理可以使用虚引用跟踪
- 注意强引用可能导致的内存泄漏问题
这四种引用类型是Java内存管理的重要组成部分,对于编写高效的Java程序非常重要。