内存泄漏分析:OQL 对象查询语言
OQL(Object Query Language)是 MAT 提供的一种类 SQL 查询语言,用于在堆转储中查询对象。
当你用 Histogram 找不到问题时,OQL 可以帮你精准定位。
今天,我们来详细讲解 OQL 的使用方法。
一、OQL 基础语法
1.1 基本结构
sql
SELECT * FROM [ INSTANCEOF ] <class_name> [ WHERE <condition> ] [ ORDER BY <properties> ]1.2 简单查询
sql
-- 查询所有 String 对象
SELECT * FROM java.lang.String
-- 查询所有 HashMap
SELECT * FROM java.util.HashMap
-- 查询所有 ArrayList
SELECT * FROM java.util.ArrayList1.3 使用 WHERE 子句
sql
-- 查询长度大于 100 的字符串
SELECT * FROM java.lang.String s WHERE s.count > 100
-- 查询大小大于 1000 的 HashMap
SELECT * FROM java.util.HashMap WHERE size > 1000
-- 查询数组长度大于 100 的 byte[]
SELECT * FROM byte[] WHERE length > 1001.4 选择特定字段
sql
-- 只返回字符串的值
SELECT s.toString() FROM java.lang.String s WHERE s.count > 100
-- 返回 HashMap 的大小和负载因子
SELECT h.size, h.loadFactor FROM java.util.HashMap h WHERE h.size > 100二、常用查询示例
2.1 查找大对象
sql
-- 查找所有大于 10MB 的对象
SELECT * FROM java.lang.Object WHERE @retainedHeapSize > 10485760
-- @retainedHeapSize 是对象保留的内存大小(对象本身 + 所有引用)2.2 查找特定字符串
sql
-- 查找包含特定内容的字符串
SELECT s.toString() FROM java.lang.String s WHERE s.toString().indexOf("config") >= 0
-- 查找以特定前缀开头的字符串
SELECT s.toString() FROM java.lang.String s WHERE s.toString().matches("user_.*")2.3 查找集合内容
sql
-- 查找 HashMap 中的所有 key
SELECT k.toString() FROM java.util.HashMap h,
IN(h.table) t WHERE t.key != null
-- 查找 ArrayList 中的所有元素
SELECT e FROM java.util.ArrayList a, IN(a.elementData) e2.4 查找特定类加载器
sql
-- 查找所有自定义类加载器
SELECT * FROM java.lang.ClassLoader WHERE class.getName().indexOf("custom") >= 0
-- 查找加载类数量最多的类加载器
SELECT cl.loadedClassCount, cl.parent FROM java.lang.ClassLoader cl
WHERE cl.loadedClassCount > 10002.5 分析线程
sql
-- 查找所有线程
SELECT * FROM java.lang.Thread
-- 查找阻塞的线程
SELECT t.name, t.blockedTime FROM java.lang.Thread t WHERE t.blockedTime > 0
-- 查找持有锁的线程
SELECT t.name, t.lockedMonitors FROM java.lang.Thread t
WHERE t.lockedMonitors.length > 02.6 分析异常栈
sql
-- 查找所有异常对象
SELECT * FROM java.lang.Throwable
-- 查找特定类型的异常
SELECT * FROM java.lang.RuntimeException三、进阶查询技巧
3.1 使用对象属性
sql
-- 访问对象的属性
SELECT objectid, heapSize, GcRoots FROM java.lang.String
WHERE count > 100
-- 使用 @ 前缀的元属性
-- @objectid: 对象的唯一 ID
-- @heapSize: 对象的堆大小
-- @retainedHeapSize: 对象的保留大小
-- @class: 对象的类
-- @gcreacheroots: 是否是 GC Root3.2 使用函数
sql
-- 使用 count 函数
SELECT COUNT(*) FROM java.lang.String
-- 使用 sum 函数
SELECT SUM(@heapSize) FROM java.lang.String
-- 使用 group by
SELECT toString().substring(0,10) as prefix, COUNT(*) as cnt
FROM java.lang.String GROUP BY toString().substring(0,10)
ORDER BY cnt DESC3.3 使用子查询
sql
-- 查找持有大 HashMap 的对象
SELECT * FROM java.lang.Object o WHERE
EXISTS (SELECT * FROM java.util.HashMap h WHERE h = o)
-- 查找被多个对象引用的字符串
SELECT s.toString(), COUNT(SELECT * FROM java.lang.Object o WHERE @contains(o.ref, s)) as refCount
FROM java.lang.String s
WHERE refCount > 103.4 正则表达式
sql
-- 匹配特定模式
SELECT * FROM java.lang.String s
WHERE s.toString().matches("^user_\\d+$")
-- 使用 like
SELECT * FROM java.lang.String s
WHERE s.toString().like(".*error.*")四、实战案例
4.1 案例一:查找内存泄漏的缓存
问题:静态 HashMap 持续增长导致 OOM
sql
-- 首先查找最大的 HashMap
SELECT * FROM java.util.HashMap ORDER BY @retainedHeapSize DESC
-- 查看 HashMap 的 key
SELECT k.toString(), h.get(k) FROM java.util.HashMap h,
IN(h.table) t WHERE t.key != null4.2 案例二:查找重复字符串
问题:大量重复的字符串占用内存
sql
-- 查找重复字符串
SELECT s.toString(), COUNT(*) as cnt
FROM java.lang.String
GROUP BY s.toString()
HAVING cnt > 1
ORDER BY cnt DESC4.3 案例三:查找类加载器泄漏
问题:Tomcat 反复部署后类加载器未释放
sql
-- 查找所有类加载器
SELECT cl, cl.loadedClassCount FROM java.lang.ClassLoader cl
-- 查找加载类数量异常的类加载器
SELECT * FROM java.lang.ClassLoader cl
WHERE cl.loadedClassCount > 50004.4 案例四:查找大数组
问题:某个数组占用了大量内存
sql
-- 查找所有 byte[] 数组
SELECT @heapSize, @length FROM byte[] ORDER BY @heapSize DESC
-- 查找所有 Object[] 数组
SELECT @heapSize, @length FROM java.lang.Object[] ORDER BY @heapSize DESC五、OQL 与其他工具的配合
5.1 OQL 与 Histogram 配合
- 使用 Histogram 初步查看内存分布
- 发现可疑类型后,使用 OQL 深入分析
- 查看对象的引用关系
5.2 OQL 与 Dominator Tree 配合
- OQL 查找特定对象
- Dominator Tree 分析对象间的引用关系
- 定位内存泄漏的根源
5.3 OQL 与线程分析配合
sql
-- 查找线程相关的对象
SELECT t.name, t.stackTrace FROM java.lang.Thread t
WHERE t.name LIKE "pool-.*-thread-.*"六、常见问题
6.1 查询太慢
如果查询结果太多,可以:
- 添加 WHERE 条件限制
- 使用 LIMIT
- 先用 Histogram 筛选类型
6.2 语法错误
MAT OQL 语法与标准 SQL 有些不同:
- 使用
FROM class_name obj而不是FROM obj IN class_name - 对象属性访问使用
.而不是[] - 布尔值使用
=而不是IS
6.3 找不到对象
检查类名是否正确:
sql
-- 查看所有包含 "Map" 的类
SELECT * FROM ".*Map.*"七、OQL 快捷键和技巧
- Ctrl + F:打开 OQL 控制台
- Ctrl + Enter:执行查询
- Ctrl + L:清除结果
- 双击结果:查看对象详情
- 右键 → Path To GC Roots:查看引用链
总结
OQL 是 MAT 中强大的查询语言:
- 基本语法:
SELECT * FROM class WHERE condition - 常用查询:大对象、特定字符串、集合内容、类加载器
- 进阶技巧:聚合、排序、子查询
- 实战应用:配合 Histogram 和 Dominator Tree 使用
- 优化建议:添加条件限制,避免查询过慢
思考题
使用 OQL 查找内存泄漏时,@retainedHeapSize 和 @heapSize 有什么区别?为什么分析内存泄漏时应该关注 @retainedHeapSize?
提示:考虑一个对象被多个引用持有时的情况。
