JavaAgent寄生在目标进程中引起的ClassNotFoundException
今天有解决方案部的小伙伴反映,我公司XWind产品在分析客户应用程序的潜在性能问题时,总是显现诊断任务异常,为了定位问题的根因,我们马上要求解决方案部的小伙伴提供XWind相关的日志,从日志中找到了如下报错信息:

可以看到Java经典的动态加载类错误,org.apache.naming.java.javaURLContextFactory类找不到。为了能更好的说明这个问题,有必要先介绍一下与问题相关的、XWind产品局部的技术架构,如下:

这里涉及到我们的JavaAgent和XPocket,我马上在本地的JavaAgent和XPocket项目源代码中排查了一下这个类,方法很简单,只是import一下即可。如果是我们项目本身需要依赖这个类,那么Maven项目肯定会将依赖的包都引入项目中,那问题很可能就是类加载器和相关包路径的问题导致了类找不到。但是这个类在两个项目中都找不到,那就说明这个类是目标客户应用程序中的类,我上网查了一下这个类,发现是Tomcat中的相关类,进一步确认了这不是我们项目中用到的类。
排查到这里,问题就变成了,为什么JMX突然要加载一个可能是客户应用中使用的类呢?
从上图的技术架构可以看到,JavaAgent是挂到客户应用进程上的,初步猜测是应用进程的某些配置影响到了JavaAgent。我根据抛出异常的调用栈信息在getInitialContext()方法中的672行打了断点,然后本地进行调试,代码并没有走到这个方法,那么肯定是客户机器上的配置影响到了JavaAgent代码的执行流程。getInitialContext()方法的实现如下:

看第672行代码,调用loadClass()方法加载className,毫无疑问,这里className变量的值肯定是org.apache.naming.java.javaURLContextFactory。那么这个className的值从哪里读取的呢?通过IDEA的Find Usages命令查找调用者,只有一个调用者。这个调用者的实现如下:
protected Context getDefaultInitCtx() throws NamingException{
if (!gotDefault) {
defaultInitCtx = NamingManager.getInitialContext(myProps);
gotDefault = true;
}
if (defaultInitCtx == null)
throw new NoInitialContextException();
return defaultInitCtx;
}
现在我们的任务就是追踪myProps变量了,这个变量的定义如下:
protected Hashtable<Object,Object> myProps = null;
同样通过IDEA的Find Usages命令查找使用情况,结果如下图所示。

在InitialContext类的addToEnvironment()方法中有唯一的put()方法,打断点后在本地调试,没有走put()方法。
那么就只可能在给myProps赋值时就已经含有了相应的值, 查看上图的init()方法,实现如下:
protected void init(Hashtable<?,?> environment)
throws NamingException
{
myProps = (Hashtable<Object,Object>)
ResourceManager.getInitialEnvironment(environment); // ...
}
myProps是调用ResourceManager.getInitialEnvironment()方法获取的值,于是我又查看了这个被调用的方法的实现,如下:
public static Hashtable<?, ?> getInitialEnvironment(Hashtable<?, ?> env)
throws NamingException
{
String[] props = VersionHelper.PROPS;
if (env == null) {
env = new Hashtable<>(11);
} String[] jndiSysProps = helper.getJndiProperties();
for (int i = 0; i < props.length; i++) {
Object val = env.get(props[i]);
if (val == null) {
val = (jndiSysProps != null)
? jndiSysProps[i]
: helper.getJndiProperty(i);
}
if (val != null) {
// 1、放入了额外的值
((Hashtable<String, Object>)env).put(props[i], val);
}
} // ... // 2、将getApplicationResources()方法获取的值存储到env中
mergeTables((Hashtable<Object, Object>)env, getApplicationResources());
return env;
}
代码虽然有些长,但是逻辑很简单,就是汇总值,将key-value对放到最终的Hashtable中返回。如上代码有2处地方可能为最终的env放入了值。在看第一处调用put()方法时,放到的键的名称来自VersionHelper.PROPS,而值来自于getJndiProperties()方法,查看这个方法,实现代码如下:
String[] getJndiProperties() {
Properties sysProps = AccessController.doPrivileged(
new PrivilegedAction<Properties>() {
public Properties run() {
try {
return System.getProperties();
} catch (SecurityException e) {
return null;
}
}
}
);
if (sysProps == null) {
return null;
}
String[] jProps = new String[PROPS.length];
for (int i = 0; i < PROPS.length; i++) {
jProps[i] = sysProps.getProperty(PROPS[i]);
}
return jProps;
}
从这里能看出,这里的值全部来自系统变量,而系统变量的key来自PROPS。PROPS的定义如下:
static final String[] PROPS = new String[]{
javax.naming.Context.INITIAL_CONTEXT_FACTORY,
// ...
};
其中javax.naming.Context.INITIAL_CONTEXT_FACTORY属性的值为java.naming.factory.initial。
联系了解决方案部的小伙伴,在目标客户机上执行命令jinfo -sysprop 客户应用进程pid,导出了所有的系统配置,其中有个系统变量的定义马上引起了我的注意,如下:

在本地启动应用挂上我们自己的JavaAgent后查看系统变量,没有发现这个变量,于是我用阿里的Arthas为本地系统配置了如上的系统变量,启动时报出了同样的错误,现在终于确定是客户应用程序的系统变量影响到我们的JavaAgent了。
到目前为止,引起问题的原因找到了,那么还有一个问题,为什么在目标客户机就找不到这个类呢?由于Tomcat自定义了类加载器来加载自己的类,而JavaAgent通常是由应用类加载器加载的,所以找不到也是理所当然了。
现在问题找到了,那么如何解决这个问题呢? 之前我们客户端连接JMX的代码如下:
String jmxURL = String.format("service:jmx:rmi:///jndi/rmi://localhost:%s/server", 1099);
JMXServiceURL url = new JMXServiceURL(jmxURL);
JMXConnector jmxConnector = JMXConnectorFactory.connect(url);
MBeanServerConnection msc = jmxConnector.getMBeanServerConnection();
if(!msc.isRegistered(new ObjectName("XPocket:name=xpocket"))){
System.out.println("连接出错");
}
查找了相关的API,发现connect()还有一个重载方法,可用来传递自定义的变量,于是修改后的代码如下:
String jmxURL = String.format("service:jmx:rmi:///jndi/rmi://localhost:%s/server", 1099);
JMXServiceURL url = new JMXServiceURL(jmxURL);
Map<String,Object> m = new HashMap<>();
m.put("java.naming.factory.initial","com.sun.jndi.rmi.registry.RegistryContextFactory");
JMXConnector jmxConnector = JMXConnectorFactory.connect(url,m);
MBeanServerConnection msc = jmxConnector.getMBeanServerConnection();
if(!msc.isRegistered(new ObjectName("XPocket:name=xpocket"))){
System.out.println("连接出错");
}
这样就能使用我们自定义的java.naming.factory.initial变量值了,同时也不会影响到客户应用程序的配置。
其实经常会遇到ClassNotFoundException或MethodNotFoundException等这类问题,归根结底是因为动态类加载造成的,动态类加载在实现AOP、优化反射调用速度、实现动态代理等方面发挥了巨大的作用,这可能是它的一些小小的副作用吧。
本人最近准备出一个手写Hotspot VM的课程,超级硬核,从0开始写HotSpot VM,将HotSpot VM所有核心的实现全部走一遍,如感兴趣,速速入群。
群里可讨论虚拟机和Java性能剖析与故障诊断等话题,欢迎加入。

JavaAgent寄生在目标进程中引起的ClassNotFoundException的更多相关文章
- VC中遍历目标进程中的模块
VC中遍历目标进程中的模块 MFC代码win32 也可以用 在下面代码进行修改转换就可以了CString strModule; 可以换成 char* 但是MODULEENTRY32结构中的szModu ...
- 使用ptrace向已运行进程中注入.so并执行相关函数
这个总结的很好,从前一个项目也用到这中技术 转自:http://blog.csdn.net/myarrow/article/details/9630377 1. 简介 使用ptrace向已运行进程中注 ...
- 使用ptrace向已运行进程中注入.so并执行相关函数(转)
1. 简介 使用ptrace向已运行进程中注入.so并执行相关函数,其中的“注入”二字的真正含义为:此.so被link到已运行进程(以下简称为:目标进程)空间中,从而.so中的函数在目标进程空间中有对 ...
- 测试:OGG初始化同步表,源端抽取进程scn<源端事务的start_scn时,这个变化是否会同步到目标库中?
一.测试目标 疑问,OGG初始化同步表,源端抽取进程开始抽取的scn<源端事务的start_scn时,这个变化是否会同步到目标库中? 二.实验测试 如下进行测试! session 1 SQL&g ...
- dll 在进程中怎么区分的
平时一直没想过这个问题,今天在测试输入法注入的时候才发现windows下dll在进程中是以名字区分的,即使是完全一模一样的DLL. 具体详情,容我慢禀 : 需求是这样的,只能含有一个a.DLL,这 ...
- 【旧文章搬运】再谈隐藏进程中的DLL模块
原文发表于百度空间,2009-09-17========================================================================== 相当老的话 ...
- 一文解读C# 动态拦截第三方进程中的方法函数(外挂必备)
一.前言 由于项目需要,最近研究了一下跨进程通讯改写第三方程序中的方法(运行中),把自己程序中的目标方法直接覆盖第三方程序中的方法函数:一直没有头绪,通过搜索引擎找了一大堆解决方案,资料甚是稀少,最后 ...
- 通过修改EIP寄存器实现强行跳转并且注入DLL到目标进程里
/* 描述 功能:通过修改EIP寄存器实现32位程序的DLL注入(如果是64位,记得自己对应修改汇编代码部分) 原理: 挂起目标进程,停止目标进程EIP的变换,在目标进程开启空间,然后把相关的指令机器 ...
- 【Win 10 应用开发】在App所在的进程中执行后台任务
在以往版本中,后台任务都是以独立的专用进程来运行,因此,定义后台任务代码的类型都要位于 Windows 运行时组件项目中. 不过,在14393中,SDK 作了相应的扩展,不仅支持以前的独立进程中运行后 ...
- 隐藏进程中的模块绕过IceSword的检测
标 题: [原创] 隐藏进程中的模块绕过IceSword的检测 作 者: xPLK 时 间: 2008-06-19,17:59:11 链 接: http://bbs.pediy.com/showthr ...
随机推荐
- ChatGPT 是否会夺走人们的工作
ChatGPT 是否会夺走人们的工作? 最近,以 ChatGPT 为代表的人工智能项目在自然语言处理这一领域得到了一些突破性的进展,重新引发了人们对于"人工智能会抢走人类工作机会" ...
- Galaxy生物信息分析平台的数据集对象清理
由于微信不允许外部链接,你需要点击文章尾部左下角的 "阅读原文",才能访问文中链接. Galaxy Project 是在云计算背景下诞生的一个生物信息学可视化分析开源项目.该项目由 ...
- Microsoft R 和 Open Source R,哪一个才最适合你?
由于微信不允许外部链接,你需要点击文章尾部左下角的 "阅读原文",才能访问文中链接. R 是一个开源统计软件,在分析领域普及的非常快. 在过去几年中,无论业务规模如何,很多公司都采 ...
- rust实现weatherforecast的获取天气webapi
rust用来写webapi可能有点大材小用,但是作为入门学习应该说是不错的选择. cargo new webapi创建一个webapi项目,在src下面新建handler文件夹和models文件夹. ...
- 2023-06-05:Redis官方为什么不提供 Windows版本?
2023-06-05:Redis官方为什么不提供 Windows版本? 答案2023-06-05: Redis官方没有提供Windows版本有几个原因. 1.Redis的开发团队规模较小,由三四名核心 ...
- Go语言中的init函数: 特点、用途和注意事项
1. 引言 在Go语言中,init()函数是一种特殊的函数,用于在程序启动时自动执行一次.它的存在为我们提供了一种机制,可以在程序启动时进行一些必要的初始化操作,为程序的正常运行做好准备. 在这篇文章 ...
- 传统软件如何SaaS化改造,10个问答带你掌握最优解
摘要:如果您所在企业希望实行SaaS化改造,可访问了解华为云开发者技术团队的SaaS支持计划. 本文分享自华为云社区<[云享问答]第1期:传统软件如何SaaS化改造,10个问答带你掌握最优解!& ...
- Ubuntu虚拟机教程
1.下载ubuntu镜像 可以去中科大镜像站下载(本次下载20.04版本,不同版本操作会有差异,建议保持一致) https://mirrors.ustc.edu.cn/ 点击如图所示的按钮下载 2.v ...
- 创建属于自己的github、使用git提交、更新代码至github、写好readme
1. 在github上创建一个Repository 点击github网站,你可以用你的邮箱先注册一个账号. 点击New,转到创建一个repository的界面,如下图所示,你可以填写你的Reposit ...
- Java使用qq邮箱发送邮件(可做验证码使用)
pom.xml中导入发邮件需要的jar包 <!-- 邮箱 --> <dependency> <groupId>javax.mail</groupId> ...