Arthas OGNL表达式使用示例
一、引言
在 Java 应用的复杂运行时环境中,快速、精准地定位和解决问题是开发者面临的关键挑战。Arthas 作为一款强大的 Java 诊断工具,提供了一系列丰富的功能,其中 OGNL(Object-Graph Navigation Language)表达式的运用,极大地增强了 Arthas 在运行时诊断的能力。OGNL 表达式允许开发者在不重启应用的情况下,深入 Java 应用的内部,动态地访问和操作对象、调用方法、访问属性,从而实现对应用状态的实时洞察与调整,为高效排查线上问题提供了有力支持。 接下来,本文将通过丰富的示例,深入探讨 OGNL 在 Arthas 中的核心用法与实战场景。
二、核心语法与参数详解
(一)基础语法结构
Arthas 中 OGNL 表达式遵循ognl '@类全路径@静态成员或方法(参数)'
格式,以@
符号标识类路径与静态成员的访问起始。例如,要访问java.lang.System
类的静态方法currentTimeMillis()
获取当前时间戳,可以使用表达式ognl '@java.lang.System@currentTimeMillis()'
。这种语法不仅支持静态成员访问,还能方便地调用静态方法 ,为运行时获取类的全局状态或执行通用操作提供了便捷途径。此外,OGNL 表达式还能通过对象实例引用访问非静态成员,如对象实例.属性名
或对象实例.方法名(参数)
的形式,在结合 Arthas 的其他命令获取对象实例后,即可灵活运用 OGNL 表达式对对象进行深度操作。
[arthas@11999]$ ognl '@java.lang.System@currentTimeMillis()'
@Long[1753943030187]
(二)关键参数解析
ClassLoader 控制(-c):Java 应用中,类加载器机制复杂,不同的类加载器可能加载同名但内容不同的类。当目标类由非系统类加载器加载时,Arthas 默认使用系统类加载器可能导致类查找失败。此时,需通过sc -d 类全路径
命令获取 ClassLoader 的 hashcode。例如,对于com.vvhz.vlog.oss.util.OssUtil
类,若其由自定义类加载器加载,执行sc -d com.vvhz.vlog.oss.util.OssUtil
,输出信息中会包含classLoaderHash
字段,记录了该类加载器的唯一标识。使用 OGNL 表达式时,通过ognl -c 类加载器hashcode '@com.example.TestController@staticList'
指定正确的类加载器,确保能准确访问到目标类的静态成员staticList
,避免因类加载器不一致引发的ClassNotFoundException
等错误。
# 因为类加载器不同,导致类查找失败
[arthas@11999]$ ognl '@com.vvhz.vlog.oss.util.OssUtil@videoExtensions'
Failed to execute ognl, exception message: ognl.OgnlException: Could not get static field videoExtensions from class com.vvhz.vlog.oss.util.OssUtil [java.lang.ClassNotFoundException: Unable to resolve class: com.vvhz.vlog.oss.util.OssUtil], please check $HOME/logs/arthas/arthas.log for more details.
# 搜索该类的类加载器
[arthas@11999]$ sc -d com.vvhz.vlog.oss.util.OssUtil
class-info com.vvhz.vlog.oss.util.OssUtil
code-source file:/home/app/vlog-server/vlog-server-1.0.0-SNAPSHOT.jar!/BOOT-INF/classes!/
name com.vvhz.vlog.oss.util.OssUtil
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name OssUtil
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-org.springframework.boot.loader.LaunchedURLClassLoader@1593948d
+-jdk.internal.loader.ClassLoaders$AppClassLoader@277050dc
+-jdk.internal.loader.ClassLoaders$PlatformClassLoader@77b076be
classLoaderHash 1593948d
Affect(row-cnt:1) cost in 103 ms.
# 由上一步的输出可以看到,OssUtil类由springframework的类加载器加载,加上-c参数,指定类加载器id
[arthas@11999]$ ognl -c 1593948d '@com.vvhz.vlog.oss.util.OssUtil@videoExtensions'
@HashSet[
@String[mp4],
@String[wmv],
@String[flv],
@String[rmvb],
@String[avi],
@String[mov],
@String[mpeg],
@String[mkv],
@String[3gp],
@String[webm],
]
对象展开层次(-x):在 Java 对象模型中,对象往往包含嵌套结构,如对象的属性可能是另一个复杂对象或集合。-x
参数用于控制结果对象的展开层次,其默认值为 1,仅显示直接属性。例如,有一个复杂对象@com.arthas.example.OgnlTest@getComplexObject()
,它包含多层嵌套的子对象和集合属性。当执行ognl '@com.arthas.example.OgnlTest@getComplexObject()'
时,由于默认-x
为 1,只能看到最外层对象的直接属性。若想深入查看嵌套对象的内部结构,将-x
值增大,如ognl '@com.arthas.example.OgnlTest@getComplexObject()' -x 3
,则会逐层展开对象内的集合与子对象,最多展开到第三层,方便开发者全面了解对象的完整状态,对于分析复杂业务逻辑中的数据结构和对象关系非常关键。
[arthas@19137]$ ognl '@com.vvhz.arthas.UserService@getUser()' -x 3
@User[
id=@Integer[1],
userName=@String[张三],
address=@Address[
province=@Province[
name=@String[广东省],
code=@String[440],
],
detail=@String[广东省深圳市宝安区西乡街道],
],
]
三、常见使用场景与示例
(一)静态成员访问
获取静态属性:在 Arthas 中,获取静态属性非常直接,可通过@类全路径@属性名
的方式访问公共或私有静态属性。Arthas 突破了 Java 常规的访问权限限制,这意味着即使属性被声明为私有,也能通过这种方式获取其值。例如,我们有一个com.vvhz.arthas.UserService
类,其中包含一个私有静态属性private static String mysqlUrl = "127.0.0.1:3306";
,在 Arthas 中可执行ognl '@com.vvhz.arthas.UserService@mysqlUrl'
,就能获取到该集合的值,这在排查数据库连接问题时,快速确认配置是否正确加载非常关键。
[arthas@17487]$ ognl '@com.vvhz.arthas.UserService@mysqlUrl'
@String[http://127.0.0.1:3306]
调用静态方法:调用静态方法同样便捷,支持带参数的静态方法调用。参数传递时,字符串类型需用引号包裹,基础类型则直接写值。例如,有一个com.vvhz.arthas.MathUtil
类,包含一个静态方法public static int add(int a, int b) { return a + b; }
,在 Arthas 中可执行ognl '@com.arthas.example.MathUtils@add(3, 5)'
,即可得到两个数相加的结果 8。这种方式可用于快速验证一些工具类的静态方法逻辑是否正确,尤其是在涉及复杂业务逻辑的静态计算方法时,无需修改代码重新部署,就能在运行时进行测试。
[arthas@17487]$ ognl '@com.vvhz.arthas.MathUtil@add(3, 5)'
@Integer[8]
(二)复杂对象与集合操作
深度展开对象结构:当 Java 对象结构复杂,包含多层嵌套的对象和集合时,通过调整
-x
值,Arthas 的 OGNL 表达式能逐层解析返回结果,全面展示对象内部详情。假设一个方法@com.vvhz.arthas.UserService@getUser()
返回一个User
对象,User
类中有一个属性private Address addresse;
,每个Address
对象又包含详细的地址信息如街道、城市等。当执行ognl '@com.vvhz.arthas.UserService@getUser()'
时,默认-x
为 1,只能看到User
对象的直接属性,而看不到addresse
的具体内容。若执行ognl '@com.vvhz.arthas.UserService@getUser()' -x 3
,则会展开addresse
对象,显示每个Address
对象的详细属性,直至展开到第三层,便于开发者深入分析对象间的关系和数据完整性。
# 默认展开User对象的直接属性
[arthas@16821]$ ognl '@com.vvhz.arthas.UserService@getUser()'
@User[
id=@Integer[1],
userName=@String[张三],
address=@Address[com.vvhz.arthas.Address@4de68270],
]
# 增加 -x 3 参数,会展开详细属性
[arthas@16821]$ ognl '@com.vvhz.arthas.UserService@getUser()' -x 3
@User[
id=@Integer[1],
userName=@String[张三],
address=@Address[
province=@Province[
name=@String[广东省],
code=@String[440],
],
detail=@String[广东省深圳市宝安区西乡街道],
],
]
集合动态操作:在运行时,Arthas 的 OGNL 表达式支持对集合进行动态操作。例如获取集合长度,对于一个com.vvhz.arthas.UserService
类中的private static List<User> userList = new ArrayList<>();
,可通过ognl '@com.vvhz.arthas.UserService@userList.size()'
获取userList
的长度。此外,还能向集合中添加元素,如ognl '@com.vvhz.arthas.UserService@userList.add(new User())'
,但在生产环境中进行此类操作需格外谨慎,因为这会直接改变运行时的数据状态,可能影响业务逻辑的正确性和数据一致性,通常用于临时调试和验证某些数据处理逻辑 。
# userList初始长度为7
[arthas@19321]$ ognl '@com.vvhz.arthas.UserService@userList.size()'
@Integer[7]
# 动态添加一个User对象
[arthas@19321]$ ognl '@com.vvhz.arthas.UserService@userList.add(new com.vvhz.arthas.User())'
@Boolean[true]
# 再次查询userList长度
[arthas@19321]$ ognl '@com.vvhz.arthas.UserService@userList.size()'
@Integer[8]
(三)类实例检索与属性分析
通过vmtool
命令结合 OGNL 表达式检索 JVM 中指定类的所有实例,适用于分析单例模式有效性或实例数量异常问题。
--className
:指定目标类名
--action
是vmtool的操作,包括:
getInstances
:获取类的实例列表invoke
:调用类或实例的方法setField
:修改对象的字段值
--express
是用于对操作结果进行二次处理的表达式参数,它基于 OGNL 表达式语法,可以对 vmtool
获取到的对象(如类实例、方法返回值等)进行过滤、计算、属性提取等操作。--express
的使用依赖于 --action
的类型,不同 action
会提供不同的内置变量(如 #instances
、#result
等),以下是最常用的场景:
配合 --action getInstances
(获取类实例)
getInstances
action 返回结果绑定到instances
变量上,它是数组。可以通过--express
参数执行指定的表达式。
iterator.{? #this.id>1}'
是ognl迭代器投影语法
vmtool --className com.vvhz.arthas.User --action getInstances --express '@java.util.Arrays@stream(instances).iterator.{? #this.id>1}'
四、高级应用:与 Arthas 其他命令联动
(一)结合 tt 命令回放调用
在复杂的生产环境中,方法调用的上下文信息对于问题排查至关重要 。Arthas 的 tt(TimeTunnel)命令可以记录下指定方法每次调用的入参和返回信息,为后续分析提供了丰富的数据。而将 tt 命令与 OGNL 表达式相结合,能进一步挖掘这些记录数据的价值。
# 记录User类的setUserName方法的调用记录
# 系统调用量特别大时,防止内存溢出,一定要加-n参数,指定记录请求次数
# 查找类名时,可以用*进行匹配,防止类匹配过多,可以使用-m参数
# 当类中有重载方法时,我们可以根据方法的参数数量、参数类型过滤
# 例如:params.length==1,'params[1] instanceof Integer',params[0].mobile=="13989838402"
[arthas@19137]$ tt -t com.vvhz.arthas.*User setUserName -m 1 -n 3
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 67 ms, listenerId: 2
INDEX TIMESTAMP COST(ms) IS-RET IS-EXP OBJECT CLASS METHOD
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1001 2022-08-01 10:36:19.683 0.3758 true false 0x22a3f236 User setUserName
1002 2022-08-01 10:36:28.901 0.022251 true false 0x22a3f236 User setUserName
tt命令记录完调用记录之后,我们可以检索我们需要的记录
tt -s 'method.name=="setUserName"'
查看某条请求的详细调用信息
# -i指定编号为1001
# 从打印结果我们可以看出,这次调用setUserName方法的入参为String类型的"sss",返回参数为null
[arthas@19137]$ tt -i 1001
INDEX 1001
GMT-CREATE 2022-08-01 10:36:19.683
COST(ms) 0.3758
OBJECT 0x22a3f236
CLASS com.vvhz.arthas.User
METHOD setUserName
IS-RETURN true
IS-EXCEPTION false
PARAMETERS[0] @String[sss]
RETURN-OBJ null
如果需要对当前请求重新调用一次,使用-p参数,还可以指定调用次数--replay-times
,调用间隔--replay-interval
tt -i 1001 -p
最后一定要删除tt记录的请求
tt --delete-all
(二)处理特殊参数类型
在 Java 开发中,方法参数类型多种多样,当遇到自定义对象作为参数时,在 Arthas 中使用 OGNL 表达式传递参数会面临一定挑战。例如,有一个UserService
类,其中包含一个方法public void saveUser(User user)
,User
类是自定义的实体类,包含String name
、int age
等属性 。在 Arthas 中直接使用 OGNL 表达式传递User
对象参数并不直接支持常规的 JSON 写法。此时,可以借助 Fastjson 等 JSON 处理工具来解决。首先,将User
对象转换为 JSON 字符串,假设要创建一个User
对象,其name
为 "John",age
为 30 ,对应的 JSON 字符串为{"name":"John","age":30}
。然后,在 Arthas 中使用 Fastjson 的parseObject
方法将 JSON 字符串转为User
对象实例,通过 OGNL 表达式传递给方法。执行ognl '@com.alibaba.fastjson.JSON@parseObject("{\"name\":\"John\",\"age\":30}", @com.example.domain.User@class)'
,这会将 JSON 字符串解析为User
类的实例。若要调用saveUser
方法保存该用户,假设UserService
是一个 Spring Bean,可通过获取 Spring 上下文获取UserService
实例后调用方法,如tt -t com.example.service.UserService saveUser -n 1
记录方法调用,再通过tt -i 记录编号 -w '@org.springframework.beans.factory.BeanFactoryUtils@beanOfType(target.getApplicationContext(), @com.example.service.UserService@class).saveUser(@com.alibaba.fastjson.JSON@parseObject("{\"name\":\"John\",\"age\":30}", @com.example.domain.User@class))'
,实现对包含自定义对象参数方法的调用与分析 ,有效解决了特殊参数类型在 Arthas OGNL 表达式中的传递与使用问题,拓展了 Arthas 在复杂业务场景下的诊断能力。
五、注意事项
(一)风险控制
生产环境慎用写操作:在生产环境中,对集合进行动态修改(如添加、删除元素)或修改静态变量的值时,务必谨慎行事。这些操作会直接改变应用运行时的状态,可能引发一系列难以预料的问题。例如,在一个高并发的电商订单系统中,若错误地通过 OGNL 表达式修改了订单缓存集合,可能导致订单数据不一致,出现超卖、库存异常等严重问题,影响业务的正常运转 。因此,在生产环境中,除非有绝对的必要且经过充分的测试与评估,否则应避免此类写操作,更多地将 OGNL 表达式用于只读的诊断分析场景。
复杂表达式先测试:当编写复杂的 OGNL 表达式时,由于其语法的灵活性和复杂性,很容易出现语法错误或因类加载器路径问题导致表达式执行失败。例如,在一个包含多个模块的大型 Spring Boot 项目中,不同模块可能使用不同的类加载器加载类,若在表达式中未正确指定类加载器 hashcode,可能导致无法找到目标类,从而抛出
ClassNotFoundException
。为了避免在生产环境中出现这类问题,建议先在测试环境中对复杂表达式进行充分验证,确保语法正确、类加载器路径准确无误后,再应用到生产环境中,以降低因表达式错误引发生产故障的风险 。
(二)效率提升技巧
快速获取类加载器与成员信息:在使用 OGNL 表达式前,通过
sc -d 类全路径
命令,可以快速获取目标类的详细信息,包括类加载器的 hashcode 和类的成员信息。这在处理由非系统类加载器加载的类时尤为重要。例如,在一个基于 OSGi 的模块化应用中,各个模块都有自己独立的类加载器,通过sc -d com.example.osgi.module.MyService
,可以获取MyService
类的类加载器 hashcode,为后续使用 OGNL 表达式访问该类的静态成员或方法提供准确的类加载器信息,避免因类加载问题导致表达式执行失败,大大提高诊断效率。合理利用 -x 参数:
-x
参数用于控制结果对象的展开层次,在实际使用中,应根据需求逐步增加展开层次。从默认的-x 1
开始,若发现直接属性不足以满足分析需求,再逐步增大-x
值,如-x 2
或-x 3
。这样可以避免一开始就输出大量冗余信息,使诊断过程更加聚焦。例如,在分析一个复杂的用户对象时,先使用ognl '@com.example.UserService@getUser()'
查看最外层属性,若需要深入了解用户地址等嵌套属性,再使用ognl '@com.example.UserService@getUser()' -x 2
展开到第二层,确保在获取关键信息的同时,不会因过多的输出信息而干扰分析思路。