1、四大引用
在Java中,引用分为四类;强引用、软引用、弱引用、虚引用;此类文章非常多,故不在此赘述;这里简单描述一下;
强引用
强引用(Strong Reference): 最常见的一种方式,没有任何修饰;这种引用方式在GC的时候不能被释放,一旦释放,会导致程序崩溃; 举例
Student stu = new Studnet();
stu就是一个强引用;
软引用
软引用(Soft Reference):由SoftReference修饰,在堆内内存不足时,触发FullGC时,若无其他强引用,会将其回收掉;
比如:下文中 如果stu=null, 那么Student对象就只有softRef这一个软引用,如果触发FGC,则会将Student对象给回收掉;
举例:
Student stu = new Student();
SoftReference softRef = new SoftReference(stu);
stu = null; // 这里就把强引用给回收了,只有软引用指向Student对象了;
softRef就是一种软引用方式;
进阶版:
增加引用队列,如果GC,则会将引用对象放入队列中;
Student stu = new Student();
ReferenceQueue refQueue = new ReferenceQueue()
SoftReference softRef = new SoftReference(stu, refQueue);
stu = null; // 这里就把强引用给回收了,只有软引用指向Student对象了;
弱引用
弱引用(Weak Reference): 由WeakReference修饰,当发生GC(YGC/FGC),若无其他强引用,都会将其修饰的对象给回收掉;
使用方式和上面的softReference相似,只不过不同点在于回收的时机不同;
虚引用
虚引用(Phantom Reference):由PhantomReference修饰,这个回收时机是随时发生的;
四种引用方式对比
这里的对比点,只有这一种引用的情况下;
主要的区别点在于是否被引用的对象被回收、触发回收的时机点
- 强引用 即使FGC,该引用的对象也不会被回收;一旦回收,会导致整个程序的崩溃;
- 软引用,内存不足、触发FGC、没有其他强引用时,该引用的对象才会被回收;
- 弱引用,触发GC(YGC/FGC)都会导致该引用的对象被回收;
- 虚引用,回收时机是随机,由JVM控制;
四种引用方式的使用场景
- 强引用:随处可见,作为一个对象的指针;
- 软引用:一般做缓存,当内存不足的时候,及时将其清理掉即可;
- 弱引用:缓存、资源清理、资源检测,样例参看下文中的开源组件使用WeakReference解析
- 虚引用:基本不用
这里一般弱引用使用场景较多,所以以此作为展开点;
2、使用样例
// 1. 定义强引用对象;
Student stu = new Student();
// 2. 定义弱引用对象;
ReferenceQueue refQueue = new ReferenceQueue();
// 3. 如有场景需要,将其加入到引用队列中
WeakReference<Student> softRef = new WeakReference<Student>(stu, refQueue);
// 4. 将强引用对象回收
stu = null; // 这里就把强引用给回收了,只有软引用指向Student对象了;
System.out.println("before gc, SoftReference content = " + softRef.get());
System.out.println("before gc, referenceQueue content = " + refQueue.poll());
// 5. 触发GC,触发对象清理
System.gc();
TimeUnit.SECONDS.sleep(2);
// 6. 观测效果
System.out.println("-----------------------After YGC-------------------------------");
System.out.println("after gc, SoftReference content = " + softRef.get());
System.out.println("after gc, referenceQueue content = " + refQueue.poll());
3、整体流程
4、开源组件使用WeakReference解析
4.1 WeakReference在JDK ThreadLocal中的作用
- ThreadLocal : 线程局部变量,具有安全、效率高等特点
- 如果内存不足且ThreadLocal对象没有强引用了,那么此时在GC时,可以将threadLocal对象进行自行释放;以缓解内存不足带来的压力
threadlocal如果不及时释放,可能会带来内存泄露的问题? 这个问题如何带来的呢?
以一个线程模型为例;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
那么根据这个引用图可以了解到如果
引用1断掉; 那么就只剩下弱引用Entry, 当发生GC时,threadlocal被回收掉,那么就会形成如下所示图:
那么对象T就没有办法被扫描到,进而无法被回收,最终造成内存泄露
如何解决?
- 在使用threadlocal后,记得及时清理;尤其是在threadlocal定义在局部变量的时候;
- 定义在全局变量,如果没有变动,其实不会存在强引用被删除,也就不会存在内存泄漏的问题了;
4.2 WeakReference在Netty DefaultResourceLeak的作用
- DefaultResourceLeak 持有了ByteBuf的弱引用,其作用是为了做内存检测;当发生GC时,若ByteBuf对象已经被回收,就会将DefaultResourceLeak加入到refQueue中,在遍历refQueue便可以知道到底有哪些ByteBuf对象被回收了;从这些被回收的ByteBuf对象中判断是否被release;
- 注:Netty为了提速,默认申请堆外内存,堆外内存的生命周期(申请、使用、释放)都需要自己管理;所以newDirectBuffer,就必须执行release操作;
详细解析可看另一篇文章:Netty随笔集 -- ResourceLeakDetector
首先看下DefaultResourceLeak的定义
- 在创建DefaultResourceLeak对象时,会将ByteBuf对象赋值给其referent引用;
private static final class DefaultResourceLeak<T>
extends WeakReference<Object> implements ResourceLeakTracker<T>, ResourceLeak {
DefaultResourceLeak(
Object referent,
ReferenceQueue<Object> refQueue,
Set<DefaultResourceLeak<?>> allLeaks) {
super(referent, refQueue);
......
trackedHash = System.identityHashCode(referent);
allLeaks.add(this);
// Create a new Record so we always have the creation stacktrace included.
headUpdater.set(this, new Record(Record.BOTTOM));
this.allLeaks = allLeaks;
}
}
ResourceLeakDetector.reportLeak
- 在上报内存泄露报告的时候,会从refQueue中弹出已经被GC的对象;
- 如果对象为空,且不应该被dispose,就continue;
- 否则执行ref(DefaultResourceLeak对象).toString,在这个toString方法中,通过判断headUpdater==null;因为headUpdater=null,说明这个ByteBuf对象指向的堆外内存就已经被释放了;
private void reportLeak() {
.....
// Detect and report previous leaks.
for (;;) {
@SuppressWarnings("unchecked")
DefaultResourceLeak ref = (DefaultResourceLeak) refQueue.poll();
if (ref == null) {
break;
}
if (!ref.dispose()) {
continue;
}
String records = ref.toString();
if (reportedLeaks.putIfAbsent(records, Boolean.TRUE) == null) {
if (records.isEmpty()) {
reportUntracedLeak(resourceType);
} else {
reportTracedLeak(resourceType, records);
}
}
}
}
4.3 ConfigAdaptor cleanup
首先介绍下ConfigAdaptor是什么? 这是一个配置动态更新的框架;能够做到配置动态更新、资源链式依赖、配置链式传递、资源自动释放等能力;
那如何做到资源自动释放的呢? 比如:DB配置更新了,旧连接资源就需要释放掉?那什么时候进行释放呢?
- 固定时间进行释放? 这样万一有long sql,岂不是会导致业务请求失败嘛? 显然不可行;
- 所以这里也是使用了
GC 引用通知的能力: 只不过这里使用的虚引用(PhantomReference);因为没有用的资源,就让他随时清理掉就好了; 这里他使用了一个REFERENCE_QUEUE用于存放被回收PhantomReference对象, 用一个Map<Reference,Consumer> FINALIZER_MAP来保存对应PhantomReference对象会回收以后,后置的清理动作;
static void doCleanUp() {
Reference<?> ref = REFERENCE_QUEUE.poll();
while (ref != null) {
Collection<Runnable> finalizers = FINALIZER_MAP.removeAll(ref);
if (finalizers != null) {
finalizers.forEach(finalizer -> {
try {
finalizer.run();
} catch (Throwable t) {
logger.error("config adaptor auto close [{}] fail!", finalizer, t);
}
});
}
ref = REFERENCE_QUEUE.poll();
}
}
5 收获
- 在本地缓存、资源清理、资源检测等方面可以使用虚引用、弱引用;
- 在内存要求比较敏感的场景,可以使用虚引用进行随时清理资源;
- 在使用引用对象也需要重点注意内存泄露的场景;