聊聊Java中引用类型-引用类型应用与内存泄漏
共 1344字,需浏览 3分钟
·
2022-01-04 22:00
本文将接着上一篇文章内容,聊聊Java中引用使用以及可能产生的内存泄漏。
Java程序员是幸福的,不用过多考虑内存申请和释放,Jvm在Java与C++之间构建一堵由内存动态分配和垃圾收集技术所围成的高墙,是的Java程序员能全身心投入到实际开发当中,是否会有墙外面人想进去,墙里面的人却想出来呢?
内存溢出和内存泄漏:
内存溢出:俗称OOM,指JVM无法申请到足够内存空间或者GC失败,而抛出的Error,OOM造成的后果十分严重,使得应用无法对外提供服务。
内存泄漏:部分内存已经没有用了,但是却没有被回收,对于Java而言,就是GC无法回收这部分本应该被回收的内存。
Obeject 的 finalize
Object 的finalize方法,当对象即将被回收时,可以被执行finalize方法,但是并不能依赖这个方法来清除,否则将造成内存泄漏。例如当进行socket编程时,需要在finally块中执行close方法,从而释放资源,如果忘记释放了,则可能会造成内存泄漏,例如在 重写了finalize方法:
/**
* Cleans up if the user forgets to close it.
*/
protectedvoid finalize()throwsIOException{
close();
}
方法注释也很简洁明了,所以在释放这一类工具类时,一定要手动执行close方法,而不是交给finalize去替我们释放,这样容易引发内存泄漏。
ThreadLocal
ThreadLocal很常见,原理不难理解,在Thread类中有两个ThreadLocalMap变量,维护着多个本地线程变量:Thread:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals =null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals =null;
ThreadLocalMap结构:
privatestaticfinalint INITIAL_CAPACITY =16;
privateEntry[] table;// 存放元素的数组
privateint size =0;// 大小
privateint threshold;// Default to 0
其Entry为一个WeakReference子类,reference为对应的ThreadLocal 实例,即如果没有其他引用,就会被回收:
staticclassEntryextendsWeakReference<ThreadLocal>>{
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k,Object v){
super(k);
value = v;
}
}
Entry 由于没有使用引用队列,故一旦没有引用,则会直接变为inactive状态,从而被gc回收。但是另一方面,ThreadLocalMap 中 Entry[] 为一个引用,所以事实上,就算ThreadLocal没有其他地方使用,也会被Thread引用,所以只要Thread不销毁,Entry 并不会因WeakReference特性而销毁。另一方面,由于Entry 中key(ThreadLocal)是弱引用类型,所以一旦ThreadLocal没有被引用,那么ThreadLocal将会在下次gc被回收,造成的效果为:
Entry 不为null。
Entry实例的get方法获取为null,因为ThreadLocal已经被回收了,但是value仍然存在,这就造成了泄漏。
在ThreadLocalMap的set操作过程,虽然在检查到上述第二种情况,并且会尝试将数组内所有满足该情况的元素节点的value都设置为null。这也是为啥追求极致性能的netty会自己造一个FastThreadLocal来取代TheadLocal:https://blog.csdn.net/anLA_/article/details/110777608所以在使用ThreadLocal时,在用完时,一定要执行remove方法,清除对应引用。
Netty中内存泄漏检测
netty中通过BufAllocator使用ByteBuf时,都会包装一层校验内存泄漏的逻辑:
protectedstaticByteBuf toLeakAwareBuffer(ByteBuf buf){
ResourceLeakTracker<ByteBuf> leak;
switch(ResourceLeakDetector.getLevel()){
case SIMPLE:
leak =AbstractByteBuf.leakDetector.track(buf);
if(leak !=null){
buf =newSimpleLeakAwareByteBuf(buf, leak);
}
break;
case ADVANCED:
case PARANOID:
leak =AbstractByteBuf.leakDetector.track(buf);
if(leak !=null){
buf =newAdvancedLeakAwareByteBuf(buf, leak);
}
break;
default:
break;
}
return buf;
}
上述会将ByteBuf封装一层 DefaultResourceLeak
,而 DefaultResourceLeak
则是一个WeakReference对象:
DefaultResourceLeak(
Object referent,
ReferenceQueue<Object> refQueue,
Set<DefaultResourceLeak>> allLeaks){
super(referent, refQueue);
assert referent !=null;
// Store the hash of the tracked object to later assert it in the close(...) method.
// It's important that we not store a reference to the referent as this would disallow it from
// be collected via the WeakReference.
trackedHash =System.identityHashCode(referent);
allLeaks.add(this);
// Create a new Record so we always have the creation stacktrace included.
headUpdater.set(this,newRecord(Record.BOTTOM));
this.allLeaks = allLeaks;
}
在应用中,对返回的ByteBuf对象进行操作时,都会间接调用 ResourceLeakDetector
的 reportLeak
方法:
privatevoid reportLeak(){
if(!logger.isErrorEnabled()){
clearRefQueue();
return;
}
// Detect and report previous leaks.
for(;;){
@SuppressWarnings("unchecked")
DefaultResourceLeakref=(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);
}
}
}
}
上述方法有以下要点:
如果进入refQueue,则说明有弱引用被回收,如果dispose了,则说明回收之前执行release,释放了资源,否则没有释放。
报告泄漏最近调用路径
使用MAT进行堆dump分析
Java引用类型,还有一个用途点,就是在使用eclipse mat工具时,由于Java对象通过引用链接,所以当找到一个大对象时,可以过滤其他引用类型,直接选择强引用,从而一步一步最终拿到当时调用链路栈:这样就能轻而易举解决多数OOM问题的根源。
总结
Java引用类型很强大,可以来实现一些高校的应用内缓存,可以监听gc动作等。
根据部分类文档及结合代码来确定 使用时要注意是否需要手动释放。
觉得对你有帮助?不如关注博主公众号: 六点A君