- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
今天有解决方案部的小伙伴反映,我公司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的文章就讲到这里了,如果你想了解更多关于JavaAgent寄生在目标进程中引起的ClassNotFoundException的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
在我的项目中,我想分两个阶段转换字节码。顺序很重要。 首先,我需要更改方法定义 然后调用方法的方式 例如 从 String hello() 更改定义至 String hello(String s) 从
我通过 使用 javaagent(带有 Javaassit) premain(String agentArgs, Instrumentation inst) 方法,我很好奇为什么 ClassFileT
我们编写了一个 java 代理,其中使用 java.awt.TrayIcon 提供某种 GUI。当我们使用此代理时,例如Tomcat,我们遇到以下问题: 用户使用 shell 脚本启动 Tomcat
此调用有效。 java -ea -cp ~/Documents/workspace/export/testProject1/"*"-javaagent:/home/jack/Documents/wor
我正在编写一个 javaagent 来监视应用程序。它启动服务器并显示计算的指标。 如何配置我的代理,以便每当应用程序关闭时,都会执行一些清理操作,然后代理退出?我研究过关闭 Hook ,但它们需要访
关闭。这个问题是opinion-based 。目前不接受答案。 想要改进这个问题吗?更新问题,以便 editing this post 可以用事实和引文来回答它。 . 已关闭 7 年前。 Improv
我正在尝试使用一个名为 LoggerAgent 的 javaagent 来获取一些 Java 应用程序日志的详细信息。但是我收到了 ClassNotDefinedException: Exceptio
我正在尝试使用以下方法启动我的网络应用程序: java -jar application.jar 我已经嵌入了 Jetty 并使用了 Maven 程序集来构建所需的 jar,这一切都工作得很好,但是我
我像这样设置我的javaagent: -javaagent:/usr/pkg/tomcat/lib/aspectjweaver-1.7.3.jar 但它不起作用,所以我有兴趣查看日志。如果我想获取日志
我有一个要求,即当客户端连接到服务器时,服务器必须查明客户端是否使用 -javaagent: 运行?服务器和客户端都是用 java 编写的。有没有办法让服务器获取客户端正在运行的 java 选项? 最
是是否可以拦截方法调用并使用丰富的参数调用此拦截的方法?我的情况是我必须拦截例如logger.info("foo"),丰富消息参数,例如“foo.bar”并调用 logger.info(“foo.ba
如何检查 javaagent 是否已注入(inject) JVM 或禁用 javagent 的附加?由于安全原因,我试图防止我的应用程序在运行时被修改。 我知道如何防止 javaagent 在启动时加
我试图制作简单的 java 分析器并使用 javaagent 来监视内存使用情况。这是我的 javaagent 实现的一部分: import java.lang.instrument.ClassFil
您好,我正在尝试向我的 javaagent 添加调试点。我有两个单独的类用于 premain 方法和转换方法。为代理类添加的日志按预期打印。但在 ClassFileTransformer 类中,它会打
我一直在尝试从检测方法中获取值。使用 $1, $0 获取参数值和当前对象变量效果很好。但现在我想从该方法中使用的对象中获取值。 举个例子,假设我正在检测 org.h2.jdbc.JdbcPrepare
当我尝试使用 IBM JDK 运行简单的 jMockit/JUnit 测试时,出现以下异常。 java.lang.ExceptionInInitializerError at java.lan
我使用 ASM 为 javaagent 实现了一个 ClassFileTransformer。因为它有一些bug,我想为它写一个JUnit测试用例。我该怎么做? 使用我认为的伪代码: // Have
我正在尝试开发一个 javaagent,它可以在 asm-4 的帮助下检测代码。现在我遇到了一个非常基本的问题,javaagent 的类加载器没有看到 asm 依赖项,因此失败了。我是否必须提供一个包
我正在尝试使用 java.lang.instrument API 添加对 java.lang.Object 构造函数的静态方法调用。我知道我的基础知识是正确的,因为如果我调用 System.gc()
在实现真正的东西之前,我试图创建一个简单的 Java 代理程序。我无法让它运行。显然我有某种配置或类路径问题。再多的寻找和尝试也没有给我带来问题。 如果我运行: java -cp ./demoAgen
我是一名优秀的程序员,十分优秀!