MAT的OQL无法直接通过select from java.lang.Thread查看ThreadLocal持有关系,因threadLocals字段为package-private且类型为ThreadLocalMap;需用select from java.lang.ThreadLocal$ThreadLocalMap配合objects(map)反向定位线程,并遍历table中entry.value.@retainedHeap求和判断是否超10MB。

为什么直接用 select * from java.lang.Thread 找不到 ThreadLocal 持有关系
MAT 的 OQL 不是 Java 对象图的完整遍历引擎,java.lang.Thread 实例本身不直接暴露其 threadLocals 字段(它是 ThreadLocalMap 类型,且为 package-private 字段),OQL 默认无法访问非 public 成员。所以光查 Thread 类实例,看不到它持有的 ThreadLocal 数据,更没法累加 value 大小。
必须通过 java.lang.ThreadLocal$ThreadLocalMap 反向定位线程
ThreadLocalMap 实例的 table 字段(类型为 java.lang.ThreadLocal$Entry[])才是真正存储 key-value 的地方;而每个 Entry 的 value 字段可能引用大对象。关键在于:每个 ThreadLocalMap 实例的 reference 字段(在 JDK 8+ 中是 threadLocals 字段的持有者)指向所属的 Thread 实例 —— 但这个引用是隐式存在的,OQL 需靠「反向引用」机制抓取。
实操建议:
- 先用
select * from java.lang.ThreadLocal$ThreadLocalMap查出所有 map 实例 - 对每个 map,用
map.table获取 entry 数组,再遍历每个entry.value并计算其retainedHeap - 用
objects函数获取持有该 map 的Thread实例:objects(map)→ 返回所有直接/间接引用该 map 的对象,其中就包含对应的Thread - 注意:JDK 版本影响字段名 —— JDK 7 是
threadLocals,JDK 8+ 是inheritableThreadLocals和threadLocals,但 OQL 中统一用objects(map)更可靠
写一个能过滤总 value 大小 > 10MB 的 OQL 查询
不能用纯 OQL 做聚合求和(无 GROUP BY、SUM),必须靠 MAT 的「Inspector」视图配合脚本逻辑。最实用路径是:先导出候选线程,再用 OQL + 表格筛选组合完成。
推荐分步操作:
- 执行 OQL:
select t, map, map.table.length as entryCount from java.lang.ThreadLocal$ThreadLocalMap map left outer join objects(map) t where t instanceof java.lang.Thread - 在结果表格中右键 → 「Add Column」→ 添加表达式列:
map.table != null ? map.table.@retainedHeap : 0(粗略估算 map 整体 retained 大小) - 再添加一列,手动检查典型 entry 的 value 大小:
map.table[0].value.@retainedHeap(仅示意,实际需遍历;但可先按此列排序,快速定位高嫌疑 map) - 对
@retainedHeap > 10485760(即 10MB)的行,右键 → 「List objects」→ 「with incoming references」→ 确认是否只有 1 个Thread实例持有它 - 最终人工合并:同一个
Thread可能持有多张ThreadLocalMap(如普通 + inheritable),需把它们的table.@retainedHeap加总判断
容易被忽略的内存归属陷阱
ThreadLocal 的 value 对象,其 retained heap 往往远大于自身 shallow size,但 MAT 默认不自动展开 ThreadLocalMap 的 table 数组内容 —— 所以即使 value 是一个 50MB 的 byte[],若没主动点开 entry 或加列计算,它就“不可见”。
另一个坑是:GC 后残留的 Entry(key == null)仍占数组 slot,其 value 若未被回收,会阻止整个 value 对象释放,造成隐形内存泄漏。这种 case 的 value.@retainedHeap 依然有效,但对应 entry.key 为 null,需额外加条件过滤或标记。
真正要确认“该线程因 ThreadLocal 持有超 10MB”,得把同一 Thread 下所有非空 entry.value 的 @retainedHeap 手动加总 —— MAT 的 OQL 目前不支持跨数组元素聚合,这一步绕不开人工或外部脚本辅助。










