Android JVMTI實(shí)現(xiàn)應(yīng)用內(nèi)存動(dòng)態(tài)檢測

一.前言

???????在平常的Android應(yīng)用開發(fā)中,經(jīng)常會(huì)遇到應(yīng)用因內(nèi)存問題導(dǎo)致的異常,可能大家第一反應(yīng)是:分析log及堆棧信息;但是我們知道堆棧信息只是最后的結(jié)果表現(xiàn)而已,真正出問題的地方或原因是之前由于不正常的內(nèi)存操作,導(dǎo)致內(nèi)存一直占用沒有被釋放,出現(xiàn)內(nèi)存泄露,最后OOM。
???????為了解決上述問題,最直接有效的方式是:動(dòng)態(tài)內(nèi)存分配監(jiān)聽
???????記錄程序執(zhí)行過程中的動(dòng)態(tài)內(nèi)存分配,當(dāng)發(fā)生OOM時(shí),就能夠分析記錄信息掌握內(nèi)存使用情況;如:是否存在內(nèi)存泄露、內(nèi)存抖動(dòng)等問題。
???????接下來就是主角登場了----JVMTI。

二.JVMTI

a.簡介

???????Java虛擬機(jī)工具接口,是Java虛擬機(jī)提供的一整套后門,通過這套后門可以對虛擬機(jī)方方面面進(jìn)行監(jiān)控,它可以監(jiān)控jvm內(nèi)部事件的執(zhí)行,包括內(nèi)存申請、線程創(chuàng)建、類加載、GC信息、方法執(zhí)行等,也可以控制JVM的某些行為。具體可以參考o(jì)racle 的文檔:JVM Tool Interface
???????JVMTI 本質(zhì)上是在JVM內(nèi)部的許多事件進(jìn)行了埋點(diǎn),通過這些埋點(diǎn)可以給外部提供當(dāng)前上下文的一些信息,甚至可以接受外部的命令來改變下一步的動(dòng)作。
???????外部程序一般利用C/C++實(shí)現(xiàn)一個(gè)JVMTI Agent,JVMTI Agent的啟動(dòng)需要虛擬機(jī)的支持,在Agent里面注冊一些JVM事件的回調(diào)。當(dāng)事件發(fā)生時(shí)JVMTI調(diào)用這些回調(diào)方法。Agent可以在回調(diào)方法里面實(shí)現(xiàn)自己的邏輯。
???????JVMTI Agent是以動(dòng)態(tài)鏈接庫的形式被虛擬機(jī)加載的, Agent和虛擬機(jī)運(yùn)行在同一個(gè)進(jìn)程中,虛擬機(jī)通過dlopen打開Agent動(dòng)態(tài)鏈接庫。

b.功能

???????一些重要的功能包括:
??????????????1.重新定義類
??????????????2.跟蹤對象分配和垃圾回收過程
??????????????3.遵循對象的引用樹,遍歷堆中的所有對象
??????????????4.檢查 Java 調(diào)用堆棧
??????????????5.暫停(和恢復(fù))所有線程
???????不同版本的 Android 可能會(huì)提供不同的功能

c.兼容性

???????此功能需要僅針對 Android 8.0 及更高版本提供的核心運(yùn)行時(shí)支持,設(shè)備制造商無需進(jìn)行任何更改即可實(shí)現(xiàn)此功能,它是 AOSP 的一部分。
???????從 Android 8.0 開始,Android ART已經(jīng)加入了JVMTI的相關(guān)功能。目錄位于art/runtime/openjdkjvmti下,從Android.bp可以看到,編譯會(huì)生成libopenjdkjvmtid.so、libopenjdkjvmti.so文件,其中核心文件是jvmti.h文件,里面定義了一些核心方法和結(jié)構(gòu)體。本地實(shí)現(xiàn)時(shí),需要引入該文件來實(shí)現(xiàn)對應(yīng)的Capabilities。

d.API調(diào)用

???????在 Android 9.0,已將API添加到framework/base/core/java/android/os/Debug.java中,對應(yīng)API如下:

    /**
     * Attach a library as a jvmti agent to the current runtime, with the given classloader
     * determining the library search path.
     * <p>
     * Note: agents may only be attached to debuggable apps. Otherwise, this function will
     * throw a SecurityException.
     *
     * @param library the library containing the agent.
     * @param options the options passed to the agent.
     * @param classLoader the classloader determining the library search path.
     *
     * @throws IOException if the agent could not be attached.
     * @throws SecurityException if the app is not debuggable.
     */
    public static void attachJvmtiAgent(@NonNull String library, @Nullable String options,
            @Nullable ClassLoader classLoader) throws IOException {
        Preconditions.checkNotNull(library);
        Preconditions.checkArgument(!library.contains("="));

        if (options == null) {
            VMDebug.attachAgent(library, classLoader);
        } else {
            VMDebug.attachAgent(library + "=" + options, classLoader);
        }
    }

???????接下來一起分析一下當(dāng)app調(diào)用完attachJvmtiAgent()后,源碼的執(zhí)行流程,本文以Android 8.1作為源碼進(jìn)行分析。

三.源碼分析

???????通過上面的分析可以看到,在attachJvmtiAgent()內(nèi)部,會(huì)調(diào)用VMDebug類內(nèi)部的attachAgent()方法,由于attachJvmtiAgent()是在Android 9.0才加入的,那么在Android 8.1平臺(tái)只能通過反射來執(zhí)行,直接反射VMDebug的attachAgent()方法。

a.VMDebug.java

???????該類路徑:libcore/dalvik/src/main/java/dalvik/system/VMDebug.java

/**
 * Attaches an agent to the VM.
 *
 * @param agent The path to the agent .so file plus optional agent arguments.
 */
public static native void attachAgent(String agent) throws IOException;

???????從上面可以看到,該類是native方法,會(huì)通過Jni調(diào)用到native層對應(yīng)的實(shí)現(xiàn)方法,該實(shí)現(xiàn)在dalvik_system_VMDebug.cc內(nèi)部。

b.dalvik_system_VMDebug.cc

???????該類路徑:art/runtime/native/dalvik_system_VMDebug.cc

static void VMDebug_attachAgent(JNIEnv* env, jclass, jstring agent) {
  if (agent == nullptr) {
    ScopedObjectAccess soa(env);
    ThrowNullPointerException("agent is null");
    return;
  }

  if (!Dbg::IsJdwpAllowed()) {
    ScopedObjectAccess soa(env);
    ThrowSecurityException("Can't attach agent, process is not debuggable.");
    return;
  }

  std::string filename;
  {
    ScopedUtfChars chars(env, agent);
    if (env->ExceptionCheck()) {
      return;
    }
    filename = chars.c_str();
  }

  Runtime::Current()->AttachAgent(filename);
}

???????在VMDebug_attachAgent()內(nèi)部首先判斷傳入so的路徑是否為空,然后判斷是否為debug模式,以上兩個(gè)條件都滿足[注意jvmti只適應(yīng)于debug版本],最后會(huì)調(diào)用AttachAgent()方法,該方法實(shí)現(xiàn)是在runtime.cc內(nèi)部。

c.runtime.cc

???????該類路徑:art/runtime/runtime.cc

// Attach a new agent and add it to the list of runtime agents
//
// TODO: once we decide on the threading model for agents,
//   revisit this and make sure we're doing this on the right thread
//   (and we synchronize access to any shared data structures like "agents_")
//
void Runtime::AttachAgent(const std::string& agent_arg) {
  std::string error_msg;
  if (!EnsureJvmtiPlugin(this, &plugins_, &error_msg)) {
    LOG(WARNING) << "Could not load plugin: " << error_msg;
    ScopedObjectAccess soa(Thread::Current());
    ThrowIOException("%s", error_msg.c_str());
    return;
  }

  ti::Agent agent(agent_arg);

  int res = 0;
  ti::Agent::LoadError result = agent.Attach(&res, &error_msg);

  if (result == ti::Agent::kNoError) {
    agents_.push_back(std::move(agent));
  } else {
    LOG(WARNING) << "Agent attach failed (result=" << result << ") : " << error_msg;
    ScopedObjectAccess soa(Thread::Current());
    ThrowIOException("%s", error_msg.c_str());
  }
}

???????在AttachAgent()內(nèi)部,會(huì)根據(jù)傳入?yún)?shù)來創(chuàng)建Agent,然后執(zhí)行Attach()方法,該方法是在agent.h內(nèi)部。

d.agent.h

???????該類路徑:art/runtime/ti/agent.h

LoadError Attach(/*out*/jint* call_res, /*out*/std::string* error_msg) {
    VLOG(agents) << "Attaching agent: " << name_ << " " << args_;
    return DoLoadHelper(true, call_res, error_msg);
}

bool IsStarted() const {
    return dlopen_handle_ != nullptr;
}

???????在Attach()內(nèi)部會(huì)調(diào)用DoLoadHelper(),該方法位于agent.cc內(nèi)部。

e.agent.cc

???????該類路徑:art/runtime/ti/agent.cc

Agent::LoadError Agent::DoLoadHelper(bool attaching,
                                     /*out*/jint* call_res,
                                     /*out*/std::string* error_msg) {
  ......
  //如果打開過,就不會(huì)再打開了,IsStarted()方法在agent.h方法內(nèi)部判斷
  if (IsStarted()) {
    *error_msg = StringPrintf("the agent at %s has already been started!", name_.c_str());
    VLOG(agents) << "err: " << *error_msg;
    return kAlreadyStarted;
  }
  //調(diào)用DoDlOpen()
  LoadError err = DoDlOpen(error_msg);
  if (err != kNoError) {
    VLOG(agents) << "err: " << *error_msg;
    return err;
  }
  AgentOnLoadFunction callback = attaching ? onattach_ : onload_;
  if (callback == nullptr) {
    *error_msg = StringPrintf("Unable to start agent %s: No %s callback found",
                              (attaching ? "attach" : "load"),
                              name_.c_str());
    VLOG(agents) << "err: " << *error_msg;
    return kLoadingError;
  }
  // Need to let the function fiddle with the array.
  std::unique_ptr<char[]> copied_args(new char[args_.size() + 1]);
  strlcpy(copied_args.get(), args_.c_str(), args_.size() + 1);
  //回調(diào)加載的本地庫內(nèi)部的Agent_OnAttach()方法
  *call_res = callback(Runtime::Current()->GetJavaVM(),
                       copied_args.get(),
                       nullptr);
  if (*call_res != 0) {
    *error_msg = StringPrintf("Initialization of %s returned non-zero value of %d",
                              name_.c_str(), *call_res);
    VLOG(agents) << "err: " << *error_msg;
    return kInitializationError;
  } else {
    return kNoError;
  }
}

???????內(nèi)部調(diào)用方法DoDlOpen():

Agent::LoadError Agent::DoDlOpen(/*out*/std::string* error_msg) {
  DCHECK(error_msg != nullptr);

  DCHECK(dlopen_handle_ == nullptr);
  DCHECK(onload_ == nullptr);
  DCHECK(onattach_ == nullptr);
  DCHECK(onunload_ == nullptr);
  //調(diào)用dlopen()
  dlopen_handle_ = dlopen(name_.c_str(), RTLD_LAZY);
  if (dlopen_handle_ == nullptr) {
    *error_msg = StringPrintf("Unable to dlopen %s: %s", name_.c_str(), dlerror());
    return kLoadingError;
  }
  //通過FindSymbol來從加載的庫中尋找Agent_xx方法
  onload_ = reinterpret_cast<AgentOnLoadFunction>(FindSymbol(AGENT_ON_LOAD_FUNCTION_NAME));
  if (onload_ == nullptr) {
    VLOG(agents) << "Unable to find 'Agent_OnLoad' symbol in " << this;
  }
  onattach_ = reinterpret_cast<AgentOnLoadFunction>(FindSymbol(AGENT_ON_ATTACH_FUNCTION_NAME));
  if (onattach_ == nullptr) {
    VLOG(agents) << "Unable to find 'Agent_OnAttach' symbol in " << this;
  }
  onunload_= reinterpret_cast<AgentOnUnloadFunction>(FindSymbol(AGENT_ON_UNLOAD_FUNCTION_NAME));
  if (onunload_ == nullptr) {
    VLOG(agents) << "Unable to find 'Agent_OnUnload' symbol in " << this;
  }
  return kNoError;
}

???????內(nèi)部調(diào)用方法FindSymbol():

void* Agent::FindSymbol(const std::string& name) const {
  CHECK(IsStarted()) << "Cannot find symbols in an unloaded agent library " << this;
  return dlsym(dlopen_handle_, name.c_str());
}

???????通過以上調(diào)用關(guān)系可以看到,當(dāng)我們加載完本地so后,然后調(diào)用Debug.attachJvmtiAgent()[Android 9.0]或反射調(diào)用VMDebug.attachAgent()[Android 8.1],會(huì)回調(diào)so內(nèi)部的Agent_XX方法,本地測試發(fā)現(xiàn),會(huì)回調(diào)Agent_OnAttach()方法,那我們就在Agent_OnAttach()內(nèi)部來初始化Jvmti的工作。
???????總結(jié)一下調(diào)用流程:

attach Agent.png

四.案例分析

???????本案例實(shí)現(xiàn)了對應(yīng)用內(nèi)部對象創(chuàng)建及釋放、方法進(jìn)入及退出事件的監(jiān)聽。
???????由于需要將so作為agent進(jìn)行attach,所以涉及到j(luò)ni編程,生成so,關(guān)于jni編程,可以參考之前的一篇文章AndroidStudio 來編寫jni及生成so。本文就略過了,直接上代碼。

a.Monitor.java
public class Monitor {

    private static final String LIB_NAME = "monitor_agent";

    public static void init(Context application) {
        //最低支持Android 8.0
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            return;
        }

        //獲取so的地址后加載
        String agentPath = getAgentLibPath(application);
        System.load(agentPath);

        //加載jvmti
        attachAgent(agentPath, application.getClassLoader());
       
        //開啟jvmti事件監(jiān)聽
        agent_init(root.getAbsolutePath());
    }

    private static void attachAgent(String agentPath, ClassLoader classLoader) {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                Debug.attachJvmtiAgent(agentPath, null, classLoader);
            } else {
                Class vmDebugClazz = Class.forName("dalvik.system.VMDebug");
                Method attachAgentMethod = vmDebugClazz.getMethod("attachAgent", String.class);
                attachAgentMethod.setAccessible(true);
                attachAgentMethod.invoke(null, agentPath);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private static String getAgentLibPath(Context context) {
        try {
            ClassLoader classLoader = context.getClassLoader();
            Method findLibrary = ClassLoader.class.getDeclaredMethod("findLibrary", String.class);
            //so的地址
            String jvmtiAgentLibPath = (String) findLibrary.invoke(classLoader, LIB_NAME);
            //將so拷貝到程序私有目錄 /data/data/packageName/files/monitor/agent.so
            File filesDir = context.getFilesDir();
            File jvmtilibDir = new File(filesDir, "monitor");
            if (!jvmtilibDir.exists()) {
                jvmtilibDir.mkdirs();
            }
            File agentLibSo = new File(jvmtilibDir, "agent.so");
            if (agentLibSo.exists()) {
                agentLibSo.delete();
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                Files.copy(Paths.get(new File(jvmtiAgentLibPath).getAbsolutePath()), Paths.get((agentLibSo).getAbsolutePath()));
            }
            return agentLibSo.getAbsolutePath();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static void release() {
        agent_release();
    }

    private native static void agent_init();
    private native static void agent_release();
}

???????1.application內(nèi)部通過init()來進(jìn)行初始化,包括加載庫(后面補(bǔ)充一下so加載流程)、創(chuàng)建存放日志的目錄;
???????2.通過getAgentLibPath()來獲取到so的路徑;
???????3.在attachAgent()內(nèi)部直接或通過反射來attachAgent();
???????4.native方法agent_init()及agent_release()方法來開啟及暫停jvmti。
???????so的加載會(huì)調(diào)用System.load(path)或System.loadLibrary(name),兩者最后調(diào)用的都是同一個(gè)方法,執(zhí)行流程如下:

load lib.png

b.agentlib.cpp
#include <jni.h>
#include <string>
#include "jvmti.h"
#include "utils.h"

jvmtiEnv *mJvmtiEnv = NULL;
jlong tag = 0;

//初始化工作
extern "C"
JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM *vm, char *options, void *reserved) {
    //準(zhǔn)備jvmti環(huán)境
    vm->GetEnv(reinterpret_cast<void **>(&mJvmtiEnv), JVMTI_VERSION_1_2);

    //開啟jvmti的能力
    jvmtiCapabilities caps;
    //獲取所有的能力
    mJvmtiEnv->GetPotentialCapabilities(&caps);
    mJvmtiEnv->AddCapabilities(&caps);
    return JNI_OK;
}

//調(diào)用System.Load()后會(huì)回調(diào)該方法
extern "C"
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;
    }
    return JNI_VERSION_1_6;
}

void JNICALL objectAlloc(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread,
                         jobject object, jclass object_klass, jlong size) {
   //對象創(chuàng)建
}

void JNICALL objectFree(jvmtiEnv *jvmti_env, jlong tag) {
 //對象釋放
}

void JNICALL methodEntry(jvmtiEnv *jvmti_env,JNIEnv* jni_env,jthread thread,jmethodID method) {
   //方法進(jìn)入
}

void JNICALL methodExit(jvmtiEnv *jvmti_env,JNIEnv* jni_env,jthread thread,jmethodID method,jboolean was_popped_by_exception,
        jvalue return_value) {
    //方法退出
}

extern "C"
JNIEXPORT void JNICALL
Java_com_hly_memorymonitor_Monitor_agent_1init(JNIEnv *env, jclass jclazz) {

    //開啟jvm事件監(jiān)聽
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.MethodEntry = &methodEntry;
    callbacks.MethodExit = &methodExit;
    callbacks.VMObjectAlloc = &objectAlloc;
    callbacks.ObjectFree = &objectFree;

    //設(shè)置回調(diào)函數(shù)
    mJvmtiEnv->SetEventCallbacks(&callbacks, sizeof(callbacks));

    //開啟監(jiān)聽
    mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
    mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_OBJECT_FREE, NULL);
    mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, NULL);
    mJvmtiEnv->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, NULL);

    env->ReleaseStringUTFChars(_path, path);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_hly_memorymonitor_Monitor_agent_1release(JNIEnv *env, jclass clazz) {
    mJvmtiEnv->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, NULL);
    mJvmtiEnv->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_OBJECT_FREE, NULL);
    mJvmtiEnv->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_ENTRY, NULL);
    mJvmtiEnv->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_EXIT, NULL);
}

???????1.在Agent_OnAttach()內(nèi)部初始化,準(zhǔn)備jvmti環(huán)境,開啟及獲取能力;
???????2.在xx_agent_1init()內(nèi)部,開啟jvmti事件監(jiān)聽,設(shè)置需要關(guān)注的回調(diào)(該回調(diào)在jvmti.h內(nèi)部有詳細(xì)的定義,設(shè)置需要關(guān)注的即可,本案例關(guān)注了JVMTI_EVENT_VM_OBJECT_ALLOC、JVMTI_EVENT_OBJECT_FREE、JVMTI_EVENT_OBJECT_FREE、JVMTI_EVENT_METHOD_EXIT),執(zhí)行SetEventNotificationMode JVMTI_ENABLE 開啟監(jiān)聽;
???????3.在xx_agent_1release()內(nèi)部,執(zhí)行SetEventNotificationMode JVMTI_DISABLE 關(guān)閉監(jiān)聽;

c.獲取事件信息

???????在對指定的事件監(jiān)聽之后,需要提取到需要的信息,比如:創(chuàng)建了什么對象、釋放了什么對象、進(jìn)入了哪個(gè)方法、退出了哪個(gè)方法等等。
???????附加一下獲取信息的方法:

void JNICALL objectAlloc(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread,
                         jobject object, jclass object_klass, jlong size) {
    //給對象打tag,后續(xù)在objectFree()內(nèi)可以通過該tag來判斷是否成對出現(xiàn)釋放
    tag += 1;
    jvmti_env->SetTag(object, tag);
    //獲取線程信息
    jvmtiThreadInfo threadInfo;
    jvmti_env->GetThreadInfo(thread, &threadInfo);
    //獲得 創(chuàng)建的對象的類簽名
    char *classSignature;
    jvmti_env->GetClassSignature(object_klass, &classSignature, nullptr);
    //獲得堆棧信息
    char *stackInfo = createStackInfo(jvmti_env, jni_env, thread, 10);
    ALOGE("object alloc, Thread is %s, class is %s, size is %s, tag is %lld, stackInfo is %s", threadInfo.name, classSignature, size, tag, stackInfo);
}
void JNICALL methodEntry(jvmtiEnv *jvmti_env,JNIEnv* jni_env,jthread thread,jmethodID method) {
    jclass clazz;
    char *signature;
    char *methodName;
    //獲得方法對應(yīng)的類
    jvmti_env->GetMethodDeclaringClass(method, &clazz);
    //獲得類的簽名
    jvmti_env->GetClassSignature(clazz, &signature, 0);
    //獲得方法名字
    jvmti_env->GetMethodName(method, &methodName, NULL, NULL);
    ALOGE("methodEntry method name is %s", methodName);
    jvmti_env->Deallocate((unsigned char *)methodName);
    jvmti_env->Deallocate((unsigned char *)signature);
}
d.存文件

???????為了效率性,可以通過mmap來實(shí)現(xiàn)文件的寫入,代碼如下:

#include <cstdint>
#include <unistd.h>
#include <cstring>
#include <fcntl.h>
#include <sys/mman.h>
#include "MemoryFile.h"

//系統(tǒng)給我們提供真正的內(nèi)存時(shí),用頁為單位提供
//內(nèi)存分頁大小 一分頁的大小
int32_t DEFAULT_FILE_SIZE = getpagesize();

MemoryFile::MemoryFile(const char *path) {
    m_path = path;
    m_fd = open(m_path, O_RDWR | O_CREAT, S_IRWXU);
    m_size = DEFAULT_FILE_SIZE;
    //將文件設(shè)置為m_size大小
    ftruncate(m_fd, m_size);
    //mmap內(nèi)存映射
    m_ptr = static_cast<int8_t *>(mmap(0, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));
    //初始化m_actualSize為0
    m_actualSize = 0;
}

MemoryFile::~MemoryFile() {
    munmap(m_ptr, m_size);
    close(m_fd);
}

void MemoryFile::write(char *data, int dataLen) {
    if (m_actualSize + dataLen >= m_size) {
        resize(m_actualSize + dataLen);
    }
    //將data的dataLen長度的數(shù)據(jù) 拷貝到 m_ptr + m_actualSize;
    memcpy(m_ptr + m_actualSize, data, dataLen);//操作內(nèi)存,通過內(nèi)存映射就寫入文件了
    //重新設(shè)置最初位置
    m_actualSize += dataLen;
}

void MemoryFile::resize(int32_t needSize) {
    int32_t oldSize = m_size;
    do {
        m_size *= 2;
    } while (m_size < needSize);
    //設(shè)置文件大小
    ftruncate(m_fd, m_size);
    //解除映射
    munmap(m_ptr, oldSize);
    //重新進(jìn)行mmap內(nèi)存映射
    m_ptr = static_cast<int8_t *>(mmap(0, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0));
}

???????本文介紹了jvmti的使用過程及對對象創(chuàng)建釋放、方法進(jìn)入退出事件的監(jiān)聽,最后對事件信息存文件,這樣當(dāng)應(yīng)用因?yàn)閮?nèi)存使用不當(dāng)導(dǎo)致的問題,通過文件就可以分析出來。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容