java agent

在构建Plumbr的多年中,我们遇到了许多具有挑战性的问题。 在其他方面,使Plumbr Java Agent可靠地执行而不会危及客户的应用程序,是一个特别棘手的问题。 从实时系统中安全地收集所有需要的遥测会带来很多问题。 其中一些非常简单,而另一些则非常不明显。

在此博客文章中,我们想与您分享一些示例,这些示例演示了在为我们的探员需要处理的一些看似简单的方面提供支持时遇到的复杂性。 这些示例进行了简化,但摘录自我们前一段时间需要解决的现实问题。 实际上,这些只是等待尝试使用字节码工具或JVMTI的人的冰山一角。

示例1:检测一个简单的Web应用程序

让我们从一个非常简单的hello world网络应用开始 :

@Controller
public class HelloWorldController {@RequestMapping("/hello")@ResponseBodyString hello() {return "Hello, world!";}
}

如果启动应用程序并访问相关的控制器,则会看到以下内容:

$ curl localhost:8080/hello
Hello, world!

作为一个简单的练习,让我们将返回值更改为“ Hello,transformed world”。 自然,我们真正的Java代理不会对您的应用程序执行此类操作:我们的目标是在不更改观察到的行为的情况下进行监视。 但是为了使这个演示简短而简洁,请与我们联系。 要更改返回的响应,我们将使用ByteBuddy :

public class ServletAgent {public static void premain(String arguments, Instrumentation instrumentation) { // (1)new AgentBuilder.Default().type(isSubTypeOf(Servlet.class)) // (2).transform((/* … */) ->builder.method(named("service")) // (3).intercept(MethodDelegation.to(Interceptor.class) // (4))).installOn(instrumentation); // (5)}}

这里发生了什么事:

  1. 与Java代理一样,我们提供了一个pre-main方法。 这将在实际应用程序启动之前执行。 如果您想了解更多信息,ZeroTurnaround上有一篇很好的文章,提供了有关检测Java代理如何工作的更多信息。
  2. 我们发现所有类都是Servlet类的子类。 Spring的魔力最终也将融入Servlet。
  3. 我们找到一种名为“服务”的方法
  4. 我们拦截对该方法的调用,并将其委托给我们的自定义拦截器,该拦截器仅显示“ Hello,transformed world!” 到ServletOutputStream。
  5. 最后,我们告诉ByteBuddy根据上面的规则来检测加载到JVM中的类

las,如果我们尝试运行此命令,则应用程序将不再启动,并引发以下错误:

java.lang.NoSuchMethodError: javax.servlet.ServletContext.getVirtualServerName()Ljava/lang/String;at org.apache.catalina.authenticator.AuthenticatorBase.startInternal(AuthenticatorBase.java:1137)at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)

发生了什么? 我们只触摸了“ Servlet”类上的“ service”方法,但是现在JVM无法在另一个类上找到另一个方法。 腥。 让我们尝试看看在两种情况下该类的加载位置。 为此,我们可以将-XX:+ TraceClassLoading参数添加到JVM启动脚本中。 如果没有Java代理,则从Tomcat加载有问题的类:

[Loaded javax.servlet.ServletContext from jar:file:app.jar!/BOOT-INF/lib/tomcat-embed-core-8.5.11.jar!/]

但是,如果再次启用Java代理,则会从其他位置加载它:

[Loaded javax.servlet.ServletContext from file:agent.jar]

啊哈! 实际上,我们的代理直接依赖于Gradle构建脚本中定义的servlet API:

agentCompile "javax.servlet:servlet-api:2.5"

可悲的是,此版本与Tomcat期望的版本不匹配,因此出现错误。 我们用这种依赖性指定哪些类仪器:isSubTypeOf(Servlet ),但是这也造成了我们加载的servlet库的不兼容版本。 要摆脱这种情况实际上并不那么容易:要检查我们尝试检测的类是否是另一种类型的子类型,我们必须知道其所有父类或接口。

尽管有关直接父代的信息存在于字节码中,但传递继承却不存在。 实际上,在进行检测时,相关的类甚至可能尚未加载。 要解决此问题,我们必须在运行时找出客户端应用程序的整个类层次结构。 有效地收集类层次结构是一项艰巨的任务,它本身就有很多陷阱,但是这里的教训很明显:规范不应加载客户端应用程序可能也要加载的类,尤其是来自不兼容版本的类。

这只是一条小小的龙,当您尝试使用字节码或尝试与类加载器混为一谈时,它已远离军团等待着您。 我们已经看到了许多其他问题:类装入死锁,验证程序错误,多个代理之间的冲突,本机JVM结构膨胀,就这样吧!

但是,我们的代理并不限于使用Instrumentation API。 要实现某些功能,我们必须更深入。

示例2:使用JVMTI收集有关类的信息

可以采用多种不同方法来确定类型层次结构,但是在本文中,我们仅关注其中之一-JVMTI (JVM工具接口)。 它使我们能够编写一些本机代码,以访问JVM的更底层的遥测和工具功能。 除其他外,可以为应用程序或JVM本身中发生的各种事件订阅JVMTI回调。 我们当前感兴趣的是ClassLoad回调。 这是一个如何使用它来订阅类加载事件的示例 :

static void register_class_loading_callback(jvmtiEnv* jvmti) {jvmtiEventCallbacks callbacks;jvmtiError error;memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));callbacks.ClassLoad = on_class_loaded;(*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));(*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, (jthread)NULL);
}

这将使JVM在类加载的早期阶段执行我们定义的on_class_loaded函数。 然后,我们可以编写此函数,以便它通过JNI调用代理的java方法,如下所示:

void JNICALL on_class_loaded(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, jclass klass) {(*jni)->CallVoidMethod(jni, agent_in_java, on_class_loaded_method, klass);
}

为了简单起见,在Java Agent中,我们将只打印类的名称:

public static void onClassLoaded(Class clazz) {System.out.println("Hello, " + clazz);
}

闭上你的眼睛一分钟,尝试想象这里可能出什么问题。

你们中许多人可能认为这将崩溃。 毕竟,您在本机代码中犯的每个错误都有可能通过段错误使整个应用程序崩溃。 但是,在这个特定示例中,我们实际上将获得一些JNI错误和一个Java异常:

Error: A JNI error has occurred, please check your installation and try again
Error: A JNI error has occurred, please check your installation and try again
Hello, class java.lang.Throwable$PrintStreamOrWriter
Hello, class java.lang.Throwable$WrappedPrintStream
Hello, class java.util.IdentityHashMap
Hello, class java.util.IdentityHashMap$KeySet
Exception in thread "main" java.lang.NullPointerExceptionAt JvmtiAgent.onClassLoaded(JvmtiAgent.java:23)

让我们暂时将JNI错误放在一边,然后集中讨论Java异常。 真令人惊讶 在这里什么可以为空? 选项不多,所以让我们检查一下并再次运行:

public static void onClassLoaded(Class clazz) {if(System.out == null) {throw new AssertionError("System.out is null");}if(clazz == null) {throw new AssertionError("clazz is null");}System.out.println("Hello, " + clazz);
}

但是,a,我们仍然会遇到相同的异常:

Exception in thread "main" java.lang.NullPointerExceptionAt JvmtiAgent.onClassLoaded(JvmtiAgent.java:31)

让我们稍等一下,然后对代码进行另一个简单的更改:

public static void onClassLoaded(Class clazz) {System.out.println("Hello, " + clazz.getSimpleName());
}

输出格式的这种看似微不足道的变化导致了行为上的巨大变化:

Error: A JNI error has occurred, please check your installation and try again
Error: A JNI error has occurred, please check your installation and try again
Hello, WrappedPrintWriter
Hello, ClassCircularityError
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  Internal Error (systemDictionary.cpp:806), pid=82384, tid=0x0000000000001c03
#  guarantee((!class_loader.is_null())) failed: dup definition for bootstrap loader?

啊,终于崩溃了! 真高兴! 实际上,这为我们提供了很多信息,有助于查明根本原因。 具体来说,现在明显的ClassCircularityError和内部错误消息非常明显。 如果要查看JVM源代码的相关部分,您会发现一个非常复杂且混合在一起的算法,用于解析类。 它确实可以单独运行,但仍然很脆弱,但是通过执行一些不寻常的操作(如覆盖ClassLoader.loadClass或抛出一些JVMTI回调)很容易被破坏。

我们在这里所做的是将类加载潜入加载类的中间,这似乎是一项冒险的业务。 跳过故障排除过程,而该故障排除过程将自己撰写一篇博客文章,涉及很多本机挖掘工作,让我们仅概述第一个示例中发生的事情:

  1. 我们尝试加载一个类,例如launcher.LauncherHelper
  2. 为了打印出来,我们尝试加载io.PrintStream类,递归到相同的方法。 由于递归是通过JVM内部以及JVMTI和JNI进行的,因此在任何堆栈跟踪中都看不到它。
  3. 现在也必须打印出PrintStream。 但是还没有完全加载,所以我们收到一个JNI错误
  4. 现在,我们继续尝试继续打印。 要连接字符串,我们需要加载lang.StringBuilder。 重复同样的故事。
  5. 最后,由于类加载不多,我们得到了一个空指针异常。

好吧,那很复杂。 但是毕竟,JVMTI文档非常明确地表示我们应该格外小心:

“此事件是在加载课程的早期阶段发送的。 因此,该类应谨慎使用。 请注意,例如,方法和字段尚未加载,因此对方法,字段,子类等的查询不会给出正确的结果。 请参见Java语言规范中的“类和接口的加载”。 对于大多数目的, ClassPrepare 事件将更加有用。”

确实,如果我们使用此回调,那么就不会有这样的困难。 但是,在设计用于监视目的的Java代理时,有时会被迫进入JVM的非常暗的区域以支持我们所需的产品功能,而开销却足以降低生产部署的成本。

带走

这些示例说明了一些看似无辜的设置和构建Java代理的幼稚方法如何以令人惊讶的方式让您大吃一惊。 实际上,以上内容几乎不涉及我们多年来发现的内容。

再加上数量众多的不同平台,此类代理将需要完美运行(不同的JVM供应商,不同的Java版本,不同的操作系统),并且本来就很复杂的任务变得更具挑战性。

但是,通过尽职调查和适当的监视,构建可靠的Java代理是一项可以由一组敬业工程师解决的任务。 我们在自己的产品中自信地运行Plumbr Agent,并且不会因此而睡不着。

翻译自: https://www.javacodegeeks.com/2017/06/shoot-foot-building-java-agent.html

java agent

java agent_如何脚踏实地构建Java Agent相关推荐

  1. oidc_使用Java EE和OIDC构建Java REST API

    oidc "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. ...

  2. 使用Java EE和OIDC构建Java REST API

    "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. Java ...

  3. 2018 二级java 9月时间_2018年9月计算机二级考试JAVA章节知识:构建java程序

    出国留学网在这里为考生们整理了"2018年9月计算机二级考试JAVA章节知识",希望有所帮助,想了解更多考试资讯,请关注小编的及时更新哦. 2018年9月计算机二级考试JAVA章节 ...

  4. 如何脚踏实地构建Java Agent

    在构建Plumbr的多年中,我们遇到了许多具有挑战性的问题. 在其他方面,使Plumbr Java Agent可靠地执行而又不危害客户的应用程序,是一个特别棘手的任务. 从实时系统中安全地收集所有需要 ...

  5. gradle java ide_使用Gradle构建Java项目

    使用Gradle构建Java项目 这个手册将通过一个简单的Java项目向大家介绍如何使用Gradle构建Java项目. 我们将要做什么? 我们将在这篇文档航中创建一个简单的Java项目,然后使用Gra ...

  6. flash java 6,为Flash构建 Java WebService

    4. 打开 toFlash.java 文件,对代码进行编辑.输入以下代码: package f2k4; /** * Title: Java For F2K4 WebService * Descript ...

  7. 《Java和Android开发实战详解》——2.2节构建Java应用程序

    本节书摘来自异步社区<Java和Android开发实战详解>一书中的第2章,第2.2节构建Java应用程序,作者 陈会安,更多章节内容可以访问云栖社区"异步社区"公众号 ...

  8. WAP 构建 Java 应用 和 WAP经验总结

    WAP 构建 Java 应用 充分利用现有的 EJB 使移动工作群体变得强大 文档选项 将此页作为电子邮件发送 未显示需要 JavaScript 的文档选项 级别: 初级 Aashish Patil ...

  9. java 自动化编译_构建Jenkins自动化编译管理环境

    今天研究了一下Jenkins,有了一个粗浅的认识,顺手把构建的过程说一下,后续慢慢补充: (1)Secure CRT 连接到Linux服务器 要注意的一点是,要搞好一个文件传输的路子,否则不好传东西. ...

最新文章

  1. 【深度学习】一文深度解读模型评估方法
  2. 我的地盘我做主——你必须遵守的Python编码规范
  3. 权限不同,设置标签绑定的事件生效与不生效(使事件不起作用)
  4. 番石榴条纹类的细粒度并发
  5. C语言_结构体与共用体
  6. CentOS 6.8内核版本升级(升级至3.10)(转)
  7. 初恋为何成为最美的爱情(下)
  8. cacti安装后修改说明
  9. cv::Mat转换RGB
  10. 《小欢喜》欢喜人生,人生如画,画里有话,话说人生
  11. 整数拼接(记忆化更新)
  12. 即时通讯IM技术领域基础篇
  13. 混合牛奶AcWing
  14. 如何将visio画的图转为eps格式?
  15. 从李嘉图定律看程序员的收入差距
  16. 迅镭激光20000瓦高速切割机顺利交付柳工,助力工程机械行业高速发展!
  17. windows 快速修复内存不能read
  18. iOS 解决苹果手机锁屏后APP退出的问题及app状态
  19. java 方法实现数学黑洞
  20. Shell中的基本命令

热门文章

  1. CF1322C:Instant Noodles
  2. 二分:[BJWC2008]秦腾与教学评估
  3. P1912-[NOI2009]诗人小G【四边形不等式,单调队列】
  4. P6088-[JSOI2015]字符串树【可持久化Trie,LCA】
  5. #6029. 「雅礼集训 2017 Day1」市场(势能,区间除)
  6. 吃豆人(luogu 7472/NOI Online 2021 普及组 T2)
  7. [XSY] 相似(DP套DP)
  8. 【开源组件】一份值得收藏的的 MySQL 规范
  9. Java进阶学习路线
  10. JavaFX图表(七)之散点图