【问题标题】:Using Class.forName() in Java Instrumentation Agent在 Java Instrumentation Agent 中使用 Class.forName()
【发布时间】:2017-10-02 09:38:31
【问题描述】:

我的理解是,如果我使用:

Instrumentation#getAllLoadedClasses()

我确实选择了目标 JVM 加载的所有类。但如果我这样做:

Class.forName("my.class.name")

这将与 VM 加载的类不同。是的,我可以将这个特定的类添加为代理 MANIFEST.MF 类路径中的 jar - 但在我看来这与 getAllLoadedClasses() 不同。

有人可以确认这是否正确,即我在检测时无法使用Class.forName() 找到特定的类?我的目标不是使用 getAllLoadedClasses() 遍历所有加载的类 - 但如果没有其他选择,我想现在还可以。

** 更新

我写的错误是Boot-Class-Path,我现在在我的清单中更正了它。使用 -verbose:class 日志记录我设法看到我的 jar 被加载为

[Opened C:\fullpath\someother.jar]
[Opened C:\fullpath\another.jar]
[Opened C:\fullpath\different.jar]

但我没有看到任何相应的加载信息。我尝试添加 Class.forName("a.package.in.someother.jar.classname") 并得到 NoClassDefFoundError。一旦我跳入代理 jar,我就无法使用 Class.forName() 检查目标 VM 是否加载了该类。我收到 NoClassDefFoundError。

进一步更新

好的,我已经“增肥”了清单,以便在我的 WEB-INF/lib 和 tomcat 的 lib 目录中查找所有类。我可以看到如下:

1) 当我的自定义类 MyClass 第一次加载时。 -verbose 显示:

[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]

2) 如果我再次尝试加载该类,它会正确显示上述顺序。

3) 我的代理 jar 包含我的 tomcat lib 和我的 web-inf/lib 目录的所有类。而且我还可以确认加载程序正确地看到了罐子。

4) 现在我注入代理,并从代理类中调用Class.forName("my.pkg.MyClass")。我得到以下结果。

[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]

我承认,正如@RafaelWinterhalter 在他的一个答案中指出的那样,它是系统类加载器将其加载到我的代理代码中。有什么办法可以强制“委托”,以便不同的类加载器加载代理类,从而正确地重新定义一个类。

感谢任何帮助。

【问题讨论】:

  • 是什么让你说Class.forName("[...]") 找到了与Instrumentation.getAllLoadedClasses() 不同的类?您的目标类是否没有从正在加载的类中引用?
  • @diginoise 我是说根本找不到它们!如果我在注入代理之前加载一个类“my.package.MyClass” - 即使我已将相关 jar 添加到Boot-Class-Path,代理内部的Class.forName() 也看不到相同的类,我可能是错的,但有一个JDK页面上提供的检测信息的根本差距。我在系统中提出了一个错误供他们检查。
  • instrumentation 可以在加载类时观察(和修改)类这一事实并不意味着它的类加载器负责加载这些类。您的应用程序是 Web 应用程序吗?
  • @dignoise 是的,它是一个 web 应用程序,但我相信即使我有一个 helloworld 类型的应用程序也会发生这种情况。我想你可能已经从我的脑海中抢走了这个词。解决方案是强制 Instrumentation 使用 VM 的上下文类加载器?
  • 您可以创建自定义的 ClassLoader 并将其传递给您的 CLASSPATH

标签: java classloader instrumentation


【解决方案1】:

正如javadoc中所述:

调用此方法等效于: Class.forName(className, true, currentLoader) 其中currentLoader 表示定义类加载器 当前班级。

从源代码中也可以看到,该方法标记为@CallerSensitive,这意味着根据调用该方法的类加载器,您会得到不同的结果。

调用Instrumentation::getAllLoadedClasses 时,返回的数组包含任何类加载器的类,而不仅仅是当前类加载器的类,当前类加载器是运行Java 代理时的系统类加载器。因此:

for (Class<?> type : instrumentation.getAllLoadedClasses()) {
  assert type == Class.forName(type.getName());
}

通常不正确。

【讨论】:

  • 谢谢 - 那么仪表代理加载如何委托例如ParallelWebappClassLoader 可以正确加载我的 Web 应用程序中的所有类?我的代理已经在 web 应用程序 lib 目录中。我需要在 webapp 启动期间显式加载此代理吗?这违背了“在虚拟机启动后启动代理”的目的。
【解决方案2】:

经过一番折腾,感谢@Holger 提醒我问题出在哪里 - 类加载器不正确。

在注入代理之前,我做了以下工作:

// Get the current context class loader, which is app ext. classLoader
ClassLoader original = Thread.currentThread().getContextClassLoader().getSystemClassLoader();

// Set the system classloader to app classloader which won't delegate anything
Field scl = ClassLoader.class.getDeclaredFields();
scl.setAccessible(true);
scl.set(null, Thread.currentThread().getContextClassLoader());

// Now inject agent
try {
    vm.loadAgent(agentPath, args);
} catch (all sorts of errors/exceptions in chain) {
// Log them and throw them back up the stack.
} finally {
  vm.detach();
  // Put back the classLoader linkage
  sc.set(null, original);
}

我如何确认

  1. 当它进入我的代理类时 - Thread.currentThread().getContextClassLoader() 成为我的应用程序 extn 加载器。但是系统类加载器现在变成了 `ParallelWebappClassLoader"。

  2. 我假设它是这样工作的,但可能完全磨损:

    i) 当我说Class.forName("my.pkg") 时,它会检查系统类加载器,它现在指向我的加载器。如果找不到该类(即未加载),它将转到父母等。我相信这或多或少是委托模型。

    ii) 这样,类在 VM 中由同一个类加载器加载,在正常情况下,该类加载器也会在我的 webapp 中加载该类。

    iii) 除了我自己的类之外,我不会检测其他任何东西,因此类加载器将始终相同。

到目前为止,我还没有看到任何 LinkageError 发生。但是我还是觉得这太冒险了,如果我断开链接,我就完蛋了。

【讨论】:

    【解决方案3】:

    必须避免在 java profiler 中使用 Class.forName 以避免 NoClassDef 错误。 JVM 根据类路径设置和类文件需求,将类文件加载到不同级别的类加载器中。

    Java Cre 库 + 引导路径保护的库将在引导级别加载
    Java 代理将在系统级别加载并继续。 Class.forName() 将从父加载器中查找类文件,当前加载器不会检查子加载器(除非我们实现自己的加载器)

    您的应用程序代码可以访问 Java 核心类,但 Java 核心类无法访问我们的应用程序代码。它称为类加载器层次结构。

    您有三个选择。

    1. 从 Instrumentation.GetLoadedClassFiles() 中查找类文件
    2. 通过 Transformers,您可以获得所有加载器类,您可以跟踪它们并在每个加载器中查找您的类,直到找到为止。
    3. 将 Class.forname 实现放在层次结构的最低级别,以便它可以在内部访问所有路径。

    适当维护层次结构以避免太多奇怪的错误。

    【讨论】:

      猜你喜欢
      • 2022-01-18
      • 2018-01-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-23
      • 2014-04-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多