android jvmti 应用之计算object占用内存大小

3,773 阅读2分钟

前天在知乎看见这样一个问题 :Android Studio 3.5提供的”Apply Changes“是什么原理?有个回答了JVMTI ,但是答主并未给出代码,在github看见https://github.com/AndroidAdvanceWithGeektime/JVMTI_Sample 大神的这个项目,然后便有了这篇实践博客

0. 先来看看效果吧

图片描述

1. 首先android p(9.0)以后开始支持JVMTI

所以尽量在9.0的官方模拟器上测试

2. 应用 jvmti原理计算object

代码地址:github.com/zjw-swun/JV…
这里再次感谢 github.com/AndroidAdva… dodola大佬

首先要明白jvmti支持哪些功能,需要看jvmti.h这个提供了哪些功能函数,其实早在java 1.6的时代就已经支持自定义JVMTI agent了,只不过android没有实现,这次在android studio 3.5”Apply Changes“ 特性中使用了,对应就让android p(9.0)开始支持这个特性,低版本是没有这个特性的

android中没提供计算object大小的api,但是熟悉java的朋友应该知道Instrument 这类有getObjectSize这个方法,这里android虽然也有同名类,但是两者功能不一样,要实现getObjectSize这个方法,直到这次android p(9.0)以后开始支持JVMTI 才提供,这里getObjectSize方法也可以自行计算,计算规则参考www.jb51.net/article/598…,继续看jvmti.h,搜索关键词getObjectSize,发现目标代码

jvmtiError GetObjectSize(jobject object,
            jlong* size_ptr) {
    return functions->GetObjectSize(this, object, size_ptr);
  }

在大佬源库的基础上改造,native-lib.cpp中添加GetObjectSize功能的支持,详细可以参考git提交记录,以下做点简单介绍

extern "C" JNIEXPORT jlong JNICALL tempGetObjectSize(JNIEnv *env,
                                                   jclass clazz,
                                                   jobject obj) {
}

static JNINativeMethod methods[] = {
      {"getObjectSize",      "(Ljava/lang/Object;)J", reinterpret_cast<jlong *>(tempGetObjectSize)},
      {"retransformClasses", "([Ljava/lang/Class;)V", reinterpret_cast<void *>(tempRetransformClasses)}
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
  JNIEnv *env;
  if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
      return JNI_ERR;
  }
  ALOGI("==============library load====================");
  jclass clazz = env->FindClass("com/dodola/jvmtilib/JVMTIHelper");
  env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0]));
  return JNI_VERSION_1_6;
}

在JNI_OnLoad中动态注册一个{"getObjectSize", "(Ljava/lang/Object;)J", reinterpret_cast<jlong *>(tempGetObjectSize)},的占位函数,然后在JvmTINativeMethodBind函数前添加以下语句

extern "C" JNIEXPORT jlong JNICALL getObjectSize(JNIEnv *env, jclass clazz, jobject obj) {
  jlong size;
  jvmtiError result = localJvmtiEnv->GetObjectSize(obj, &size);
  ALOGI("==========getObjectSize %d=======", size);
  if (result != JVMTI_ERROR_NONE) {
      char *err;
      localJvmtiEnv->GetErrorName(result, &err);
      printf("Failure running GetObjectSize: %s\n", err);
      localJvmtiEnv->Deallocate(reinterpret_cast<unsigned char *>(err));
      return -1;
  }
  return size;
}

JvmTINativeMethodBind函数里面添加以下语句

 jmethodID methodid2 = jni_env->GetStaticMethodID(clazz, "getObjectSize", "(Ljava/lang/Object;)J");
  if (methodid2 == method) {
      *new_address_ptr = reinterpret_cast<jlong *>(&getObjectSize);
  }

这里就完成了加载jvmti.so之后就能调用getObjectSize

当然JVMTIHelper.java类别忘记添加 JNI native 函数

public static native long getObjectSize(Object obj);

然后效果就和上面效果图一样了

3. jvmti.h 还有很多功能函数有空再讲别的吧

总之jvmti.h最值得期待的 RedefineClassesRetransformClasses 这个是实现 Apply Changes 的关键