开发JDBC连接泄漏检测Java Agent时WeakReference使用是否正确?
结论先给
整体检测思路方向是对的,但WeakReference的用法有硬伤,直接照着现在的设计写,跑线上大概率出统计不准、误判漏判、想关泄漏连接都找不到实例的问题。
你设计里做对的部分
- 拦截JDBC连接创建、使用、关闭三个生命周期节点,记录创建栈、创建时间、最后活跃时间的思路,是业内做JDBC泄漏检测的标准路径,和主流连接池、APM探针的实现逻辑对齐,没有方向性问题。
- 考虑到Agent自身不能持有连接的强引用导致业务连接无法GC,这个意识非常好,很多新手写Agent最容易犯的错就是自己的元数据把业务对象给hold住导致内存泄漏。
- 支持查看统计、手动关闭泄漏连接的功能点很实用,刚好踩中线上排查连接泄漏的核心需求。
现在WeakReference用法的问题
- 最核心的矛盾:WeakReference的语义是「只要对象没有任何强/软引用关联,下次GC就会直接回收掉,不管内存够不够」。如果业务代码真的泄漏了连接——比如写代码的时候忘了写
close(),但业务代码里还一直拿着这个Connection的引用没丢(这才是绝大多数连接泄漏的场景),那弱引用永远能get到实例,这部分没问题;但如果业务代码更坑,用完连接直接把引用变量置空了,连close都没调,这时候只要GC跑一次,你WeakReference里的get()直接返回null,你连连接实例都摸不到,根本没法执行手动关闭,而且你根本分不清这个连接是正常被close了,还是被业务丢了引用等着GC,直接就漏判。 - 你没配ReferenceQueue。存连接元数据的Map里会堆大量已经被GC的连接的无效记录,Agent自己跑久了元数据先内存泄漏了。
- 现在大部分JDBC场景都是用连接池的,你拦截到的Connection大概率是连接池返回的代理对象,不是底层物理连接。业务调用close()本质是把代理连接归还给池,底层物理连接根本不会被回收,你预期的「正常关闭后弱引用不阻止GC」的效果在连接池场景下根本不成立,会把池里复用的空闲连接全误判成泄漏。
调整方案
- 别自己包个WeakReference当value存普通HashMap,直接用
WeakHashMap<Connection, LeakTrackMeta>存你的跟踪数据,key就是Connection实例,value是你记录的调用栈、创建时间、lastActiveTime这些元数据。WeakHashMap本身的key就是用WeakReference实现的,只要业务侧不再持有这个Connection的强引用,对应的entry就会被标记成可回收,不会出现Agent hold住连接不让GC的问题。 - 必须绑定
ReferenceQueue监听引用回收事件。一旦发现某个连接的引用被加入队列,第一时间清掉对应关联的元数据,同时查一下这个连接有没有被拦截到close事件:如果从来没触发过close,直接记一条泄漏告警——这种业务丢了引用没关连接的场景,也是实打实的泄漏。 - 泄漏判定规则别乱设,必须同时满足三个条件再判定为可干预的泄漏:距离lastActiveTime超过预设阈值、没有拦截到close事件、WeakReference.get()返回不为null。这时候你拿到的连接实例是真的还被业务持有但长时间没用,手动调用close()才有效;要是get()已经返回null了,说明业务已经把引用丢了,等GC自己回收就行,不用瞎处理。
- 拦截点别只拦驱动层的
Connection.close(),要把连接池归还连接的方法、Statement/ResultSet关闭的关联方法都拦上,不然连接池场景下会把归还到池里的空闲连接全误判成泄漏。
别用SoftReference代替WeakReference,SoftReference要等内存不足才会回收,会导致无效连接和元数据长时间占内存,完全不符合检测工具的定位。
内容的提问来源于stack exchange,提问作者WestFarmer
相关产品推荐
相关产品推荐

