Java应用程序运行时监控方法(一)——JVMTI的应用

本文介绍了JVMTI作为Java程序监控工具的重要接口,详细阐述了如何使用JVMTI进行字节码增强、方法执行性能监控、线程监视和堆栈跟踪。通过示例展示了JVMTI在不同场景下的应用,包括修改类字节码、记录方法执行时间、监控线程生命周期和获取堆栈信息。
摘要由CSDN通过智能技术生成

The JVM Tool Interface (JVMTI) 是一个由JVM提供的用于开发针对Java程序开发与监控工具的编程接口,通过JVMTI接口(Native API)可以创建代理程序(Agent)以监视和控制 Java 应用程序,包括剖析、调试、监控、分析线程等。著名的JProfiler利用该项技术实现其对Java程序的运行态监控与性能分析。

值得注意的是JVMTI 并不一定在所有的 Java 虚拟机上都得到实现,目前Oracle(SUN)、IBM、OpenJDK以及一些开源的如 Apache Harmony DRLVM均对其进行了标准实现 。

由于JVMTI 是一套Native接口,因此使用 JVMTI 需要我们使用C/C++ 操纵JNI。

JVMTI程序通常通过Agent方式在JVM OnLoad phase(启动时)Start-Up,这个加载处于虚拟机初始化的早期,此时所有的 Java 类都未被初始化、所有的对象实例也都未被创建(也支持Live phase(运行时)的Start-Up)。在启动Java应用程序时,需加入以下JVM参数:

-agentlib:agent-lib-name=options
-agentpath:path-to-agent=options

JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会主动调用一些事件的回调接口,这些接口可以供开发者扩展自己的逻辑,实际上,对于JVMTI程序的Load过程可以遵循一种模板式的流程框架来完成:

(1)获取JVMTI环境(JVMTI Environment)

(2)注册所需的功能(Capabilities)

(3)注册事件通知(Event Notification)

(4)指定事件回调函数(Callback Method)

可以通过http://docs.oracle.com/javase/8/docs/platform/jvmti/jvmti.html 和https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/ 进一步了解相关知识。

接下来,我们通过举例的方式,看看JVMTI能够为Java应用监测带来些什么?

测试程序

我们首先编写一个简单的测试程序,用于展示我们举例中JVMTI Agent程序的功能,程序清单参考如下:

(1)Foo类

package org.xreztento.tester;

public class Foo {
   
	public void bar() throws InterruptedException {
   
			Thread.sleep(500);
	      System.out.println("Executing Foo.bar()");
	   }
	   public void baz() {
   
	      System.out.println("Executing Foo.baz()");
	   }
}

(2)Main类

package org.xreztento.tester;

public class Main {
   
	public static void main(String[] args) throws InterruptedException{
   
		Thread[] threads = new Thread[5];
		Foo foo = new Foo();
		foo.bar();
		foo.baz();
		for(int i = 0; i < threads.length; i++){
   
			threads[i] = new Thread(new Runnable(){
   

				@Override
				public void run() {
   
					
					System.out.println(Thread.currentThread().getName());
				}
				
			});
		}
		
		for(Thread thread : threads){
   
			thread.start();
			thread.join();
		}
		
	}
}

我们将项目打包为tester.jar包,运行后输出结果如下:

Bytecode Instrumentation

使用 Instrumentation开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。

利用Instrumentation实现字节码增强是许多监控工具针对Java应用程序实现非“侵入式”监控技术的基础,JVMTI为其提供了Native接口,Java SE 5将其从本地代码中解放出来通过JavaAgent利用该本地接口实现了Java语言层级的接口。

我们这里先不讨论JavaAgent的上层实现方式,你可以直接利用JVMTI的Native接口完成class字节码加载时的字节码修改增强。在JVM加载class字节码时会产生一个JVMTI_EVENT_CLASS_FILE_LOAD_HOOK事件,你可以通过ClassFileLoadHook回调函数完成新字节码的定义工作。

需要特别注意的地方是,对字节码的修改需要开辟出一块新的内存空间,因此就像向操作系统申请内存空间使用如malloc一样,你需要使用(*jvmti)->Allocate在JVM内部申请出一块内存空间,参考如下代码:

#include <stdio.h>
#include <memory.h>
#include <string.h>
#include <jvmti.h>

void  JNICALL callbackClassFileLoadHook(jvmtiEnv *jvmti,
                                  JNIEnv *jni,
                                  jclass class_being_redefined,
                                  jobject loader,
                                  const char *name,
                                  jobject protection_domain,
                                  jint class_data_len,
                                  const unsigned char *class_data,
                                  jint *new_class_data_len,
                                  unsigned char **new_class_data) {
   


    jvmtiError error;
    if(strcmp(name, "org/xreztento/tester/Foo") == 0){
   
      printf("loaded class name=%s\n ", name);

      jint size = class_data_len;
      *new_class_data_len = size;

      //为新的class字节码数据区分配JVM内存
      error = (*jvmti)->Allocate(jvmti, size, new_class_data);

      memset(*new_class_data, 0, size);


      if(error != JVMTI_ERROR_NONE) {
   
        fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
      }

      int i;
      //遍历旧的字节码字符,将E字符修改为P
      for(i = 0; i < size; i++){
   
        if(class_data[i] == 'E'){
   
          (*new_class_data)[i] = 'P';
        } else {
   
          (*new_class_data)[i] = class_data[i];
        }
      }
    }
}

JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char *options, void *reserved){
   

  jvmtiEnv *jvmti = NULL;
  jvmtiError error;

  //获取JVMTI environment
  error = (*vm)->GetEnv(vm, (void **)&jvmti, JVMTI_VERSION_1_1);
  if (error != JNI_OK) {
   
      fprintf(stderr, "ERROR: Couldn't get JVMTI environment");
      return JNI_ERR;
  }

  //注册功能
  jvmtiCapabilities capabilities;
  (void)memset(&capabilities, 0, sizeof(jvmtiCapabilities));

  capabilities.can_generate_all_class_hook_events  =  1 ;
  capabilities.can_retransform_classes             =  1 ;
  capabilities.can_retransform_any_class           =  1 ;


  error = (*jvmti)->AddCapabilities(jvmti, &capabilities);

  if(error != JVMTI_ERROR_NONE) {
   
    fprintf(stderr, "ERROR: Unable to AddCapabilities JVMTI");
    return  error;
  }


  //设置JVM事件回调
  jvmtiEventCallbacks callbacks;
  callbacks.ClassFileLoadHook = &callbackClassFileLoadHook;
  error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
  if(error != JVMTI_ERROR_NONE) {
   
    fprintf(stderr, "ERROR: Unable to SetEventCallbacks JVMTI!");
    return error;
  }

  //设置事件通知
  error = 
众所周知,Java编译后的Jar包和Class文件,可以轻而易举的使用反编译工具(如JD-GUI)进行反编译,拿到源码。为了保护自己发布的Jar包和Class文件,采用的方式大多是混淆方式,这种方式对于Class文件的加密是不彻底的,还是能够通过分析得出核心算法。本工具是采用jvmti方式对Class文件进行加密,使用C++生成加密和解密库,先用加密库对Jar包进行加密,将加密后的Jar包及解密库文件发布出去,执行时候需要JVM引入解密库文件,解密后执行。c++的.dll文件和.so文件的破解难度是很大的,这就能有效的保护软件和代码的知识产权. 使用方法: 1.打开windows命令行(运行=>cmd=>回车),在命令行中 进入 EncryptJar目录 2.执行 java -jar encrypt.jar 3.输入h,然后回车,可以看到帮助菜单 4.输入3,然后按回车键,进入加入jar文件功能 5.输入要加密的jar文件的路径 6.提示输入秘钥(key)的时候,直接回车,不要输入任何字符(否则后面classhook将不可解密加密后的jar包) 7.输入目标路径(加密后的jar文件路径,此处要注意:jar文件名要保持相同,将加密后的文件保存到不同的目录) 8.将加密后的jar包,替换原来的没有加密的jar包,与要发布的程序一起进行发布.(一般替换lib目录下对应的jar包即可) 9.加密后的jar包运行方法: windows下: 拷贝libClassHook.dll文件到程序的根目录(通常为要执行的jar程序的根目录) 使用以下命令启动程序: java -agentlib:libClassHook -jar xxxxxxxxxxx.jar 则在运行过程中会自动进行解密操作(解密过程是运行过程中用c++的dll进行解密的,可以有效防止破解class文件) 如果执行过程报错,可将程序根目录添加到环境变量path中去 Linux下: 拷贝libClassHook.so到程序的根目录(通常为要执行的jar程序的根目录) 使用以下命令启动程序: java -agentlib:ClassHook -jar xxxxxxxxxxx.jar (这里要删除掉lib,linux系统下会自动补全) 则在运行过程中会自动进行解密操作(解密过程是运行过程中用c++的dll进行解密的,可以有效防止破解class文件) 如果执行过程报错,可以在程序根目录下执行以下语句:export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH 或将libClassHook.so 拷贝到/usr/lib目录中去。 支持操作系统:加密请在windows64位系统并安装了64位jdk环境下进行。 需要解密运行的程序支持LINUX(64位)和windows(64位)安装了JDK1.8以上的系统。 测试程序: (t_lib目录下的jar包为经过加密的jar包) java -agentlib:libClassHook -jar test.jar
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值