>  기사  >  Java  >  동적 마운팅을 사용하여 Java에서 버그 핫픽스를 구현하는 방법에 대한 자세한 설명(그림)

동적 마운팅을 사용하여 Java에서 버그 핫픽스를 구현하는 방법에 대한 자세한 설명(그림)

黄舟
黄舟원래의
2017-03-18 10:50:361919검색

대부분의 JVM에는 Java의 HotSwap 기능이 있으며 대부분의 개발자는 이를 단지 디버깅 도구로 생각합니다. 이 기능을 사용하면 Java 프로세스를 다시 시작하지 않고도 Java 메소드 구현을 변경할 수 있습니다. 대표적인 예가 IDE를 이용한 코딩입니다. 그러나 HotSwap은 프로덕션 환경에서 이 기능을 구현할 수 있습니다. 이러한 방식으로 실행 중인 프로그램을 중지하지 않고도 온라인 애플리케이션을 확장하거나 실행 중인 프로젝트에서 사소한 버그를 수정할 수 있습니다. 이 기사에서는 동적 바인딩을 시연하고, 바인딩을 위한 런타임 코드 변경 사항을 적용하고, 일부 API 코드 변경 사항을 보다 편리하게 제공하는 API 도구와 Byte Buddy 라이브러리를 소개합니다.

HTTP 요청에서 X-Priority 헤더의 유효성을 검사하여 서버에서 특수 처리를 수행하는 실행 중인 애플리케이션이 있다고 가정합니다. 이 확인은 다음 도구 클래스를 사용하여 구현됩니다.

class HeaderUtility {

    static boolean isPriorityCall(HttpServletRequest request) {
        return request.getHeader("X-Pirority") != null;
    }

}

오류를 찾으셨나요? 이러한 오류는 특히 테스트 코드에서 재사용하기 위해 상수 값을 정적 필드로 분해할 때 흔히 발생합니다. 이상적이지 않은 시나리오에서는 제품이 설치될 때만 오류가 발견됩니다. 여기서 헤더는 다른 애플리케이션에 의해 생성되고 오타가 없습니다.

이런 오류를 수정하는 것은 어렵지 않습니다. 지속적 전달 시대에는 버튼 하나만 클릭하면 새 버전을 재배포할 수 있습니다. 그러나 다른 경우에는 변경이 그렇게 간단하지 않을 수 있고 재배포 프로세스가 복잡할 수 있으며 가동 중지 시간이 허용되지 않고 오류가 있는 상태로 실행하는 것이 더 나을 수 있습니다. 그러나 HotSwap은 또 다른 옵션을 제공합니다. 즉, 애플리케이션을 다시 시작하지 않고 작은 변경을 수행하는 것입니다.

Attach API: 동적 첨부를 사용하여 다른 JVM 침투

실행 중인 Java 프로그램을 수정하려면 먼저 실행 중인 상태에 있을 수 있는 메서드가 필요합니다. JVM이 통신하는 방식. Java의 가상 머신 구현은 관리형 시스템이므로 이러한 작업을 수행하기 위한 표준 API가 있습니다. 질문에 포함된 API를 attachment API라고 하며 는 공식 Java 도구의 일부입니다. 실행 중인 JVM에 의해 노출된 이 API를 사용하면 두 번째 Java 프로세스가 JVM과 통신할 수 있습니다.

사실 우리는 이미 이 API를 사용하고 있습니다. VisualVM 또는 Java Mission Control과 같은 디버깅 및 시뮬레이션 도구를 통해 이미 구현되어 있습니다. 이러한 첨부 파일을 적용하기 위한 API는 일상적으로 사용되는 표준 Java API와 함께 패키지되어 있지 않고, 가상 머신의 JDK만 포함된 tools.jar, 라는 특수 파일에 패키지되어 있습니다. 더 나쁜 점은 이 JAR 파일의 위치가 설정되지 않았다는 점입니다. Windows, Linux, 특히 Macintosh의 VM에서는 파일 위치뿐만 아니라 파일 이름도 다릅니다. . 일부 배포판에서는 classes.jar이라고 합니다. 마지막으로 IBM은 이 JAR에 포함된 일부 클래스의 이름을 변경하기로 결정하고 모든 com.sun 클래스를 com.ibm 네임스페이스 로 이동하여 또 다른 혼란을 추가했습니다. Java 9에서는 혼란스러운 부분이 마침내 정리되었고 tools.jar가 Jigsaw의 모듈 jdk.attach로 대체되었습니다.

API의 JAR(또는 모듈)을 찾은 후에는 첨부 프로세스에서 이를 사용할 수 있도록 해야 합니다. OpenJDK에서 다른 JVM에 연결하는 데 사용되는 클래스는 VirtualMachine이라고 하며, 이는 동일한 물리적 시스템에서 JDK 또는 일반 HtpSpot JVM이 실행하는 모든 VM에 대한 진입점을 제공합니다. 프로세스 ID를 통해 다른 가상 머신에 연결한 후 대상 VM이 지정한 스레드에서 JAR 파일을 실행할 수 있습니다.

// the following strings must be provided by us
String processId = processId();
String jarFileName = jarFileName();
VirtualMachine virtualMachine = VirtualMachine.attach(processId);
try {
    virtualMachine.loadAgent(jarFileName, "World!");
} finally {
    virtualMachine.detach();
}

JAR 파일을 받은 후 대상 가상 머신은 JAR의 프로그램 매니페스트 설명을 봅니다. 파일(매니페스트)을 찾아 Premain-Class 속성 아래에서 클래스를 찾습니다. 이는 VM이 ​​기본 메소드를 실행하는 방법과 매우 유사합니다. Java 에이전트를 사용하면 VM과 지정된 프로세스 ID는 지정된 스레드의 원격 프로세스에 의해 실행될 수 있는 Agentmain이라는 메서드를 찾을 수 있습니다.

public class HelloWorldAgent {

    public static void agentmain(String arg) {
        System.out.println("Hello, " + arg);
    }

}

이 API를 사용하면 JVM의 프로세스 ID에 대해 코드를 실행하고 Hello, World! 메시지를 인쇄할 수 있습니다. 연결된 VM이 tools.jar에 액세스하는 JDK 설치 프로그램이라면 JDK 배포판의 일부에 익숙하지 않은 JVM과 통신하는 것도 가능합니다.

Instrumentation API:修改目标 VM 的程序

到目前来看一切顺利。但是除了成功地同目标 VM 建立起了通信之外,我们还不能够修改目标 VM 上的代码以及 BUG。后续的修改,Java 代理可以定义第二参数来接收一个 Instrumentation 的实例 。稍后要实现的接口提供了向几个底层方法的访问途径,它们中的一个就能够对已经加载的代码进行修改。

为了修正 “X-Pirority” 错字,我们首先来假设为 HeaderUtility 引入了一个修复类,叫做 typo.fix,就在我们下面所开发的 BugFixAgent 后面的代理的 JAR 文件中。此外,我们需要给予代理通过向 manifest 文件添加 Can-Redefine-Classes: true 来替换现有类的能力。有了现在这些东西,我们就可以使用 instrumentation 的 API 来对类进行重新定义,该 API 会接受一对已经加载的类以及用来执行类重定义的字节数组

public class BugFixAgent {

    public static void agentmain(String arg, Instrumentation inst)
            throws Exception {
        // only if header utility is on the class path; otherwise,
        // a class can be found within any class loader by iterating
        // over the return value of Instrumentation::getAllLoadedClasses
        Class<?> headerUtility = Class.forName("HeaderUtility");

        // copy the contents of typo.fix into a byte array
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        try (InputStream input =
                BugFixAgent.class.getResourceAsStream("/typo.fix")) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = input.read(buffer)) != -1) {
                output.write(buffer, 0, length);
            }
        }

        // Apply the redefinition
        instrumentation.redefineClasses(
                new ClassDefinition(headerUtility, output.toByteArray()));
    }

}

运行上述代码后,HeaderUtility 类会被重定义以对应其修补的版本。对 isPrivileged 的任何后续调用现在将读取正确的头信息。作为一个小的附加说明,JVM 可能会在应用类重定义时执行完全的垃圾回收,并且会对受影响的代码进行重新优化。 总之,这会导致应用程序性能的短时下降。然而,在大多数情况下,这是较之完全重启进程更好的方式。

当应用代码更改时,要确保新类定义了与它替换的类完全相同的字段、方法和修饰符。 尝试修改任何此类属性的类重定义行为都会导致 UnsupportedOperationException。现在 HotSpot 团队正试图去掉这个限制。此外,基于 OpenJDK 的动态代码演变虚拟机支持预览此功能。

使用 Byte Buddy 来追踪内存泄漏

一个如上述示例的简单的 BUG 修复代理在你熟悉了 instrumentation 的 API 的时候是比较容易实现的。只要更加深入一点,也可以在运行代理的时候,无需手动创建附加的 class 文件,而是通过重写现有的 class 来应用更多通用的代码修改。

字节码操作

编译好的 Java 代码所呈现的是一系列字节码指令。从这个角度来看,一个 Java 方法无非就是一个字节数组,其每一个字节都是在表示一个向运行时发出的指令,或者是最近一个指令的参数。每个字节对应其意义的映射在《Java 虚拟机规范》中进行了定义,例如字节 0xB1 就是在指示 VM 从一个带有 void 返回类型的方法返回。因此,对字节码进行增强就是对一个方法的字节数字进行扩展,将我们想要应用的表示额外的业务逻辑指令包含进去。

当然,逐个字节的操作会特别麻烦,而且容易出错。为了避免手工的处理,许多的库都提供了更高级一点的 API,使用它们不需要我们直接同 Java 字节码打交道。这样的库其中就有一个叫做 Byte Buddy (当然我就是该库的作者)。它的功能之一就是能够定义可以在方法原来的代码之前和之后被执行的模板方法。

위 내용은 동적 마운팅을 사용하여 Java에서 버그 핫픽스를 구현하는 방법에 대한 자세한 설명(그림)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.