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

一.前言

???????在平常的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)等問題。
???????接下來(lái)就是主角登場(chǎng)了----JVMTI格仲。

二.JVMTI

a.簡(jiǎn)介

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

b.功能

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

c.兼容性

???????此功能需要僅針對(duì) Android 8.0 及更高版本提供的核心運(yùn)行時(shí)支持,設(shè)備制造商無(wú)需進(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í)咽袜,需要引入該文件來(lái)實(shí)現(xiàn)對(duì)應(yīng)的Capabilities。

d.API調(diào)用

???????在 Android 9.0枕稀,已將API添加到framework/base/core/java/android/os/Debug.java中询刹,對(duì)應(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);
        }
    }

???????接下來(lái)一起分析一下當(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)只能通過反射來(lá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層對(duì)應(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ù)來(lái)創(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)加載的本地庫(kù)內(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來(lái)從加載的庫(kù)中尋找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方法混驰,本地測(cè)試發(fā)現(xiàn),會(huì)回調(diào)Agent_OnAttach()方法皂贩,那我們就在Agent_OnAttach()內(nèi)部來(lái)初始化Jvmti的工作栖榨。
???????總結(jié)一下調(diào)用流程:

attach Agent.png

四.案例分析

???????本案例實(shí)現(xiàn)了對(duì)應(yīng)用內(nèi)部對(duì)象創(chuàng)建及釋放、方法進(jìn)入及退出事件的監(jiān)聽明刷。
???????由于需要將so作為agent進(jìn)行attach婴栽,所以涉及到j(luò)ni編程,生成so辈末,關(guān)于jni編程愚争,可以參考之前的一篇文章AndroidStudio 來(lái)編寫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()來(lái)進(jìn)行初始化捅彻,包括加載庫(kù)(后面補(bǔ)充一下so加載流程)、創(chuàng)建存放日志的目錄鞍陨;
???????2.通過getAgentLibPath()來(lái)獲取到so的路徑步淹;
???????3.在attachAgent()內(nèi)部直接或通過反射來(lái)attachAgent();
???????4.native方法agent_init()及agent_release()方法來(lái)開啟及暫停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) {
   //對(duì)象創(chuàng)建
}

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

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.獲取事件信息

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

void JNICALL objectAlloc(jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread,
                         jobject object, jclass object_klass, jlong size) {
    //給對(duì)象打tag,后續(xù)在objectFree()內(nèi)可以通過該tag來(lái)判斷是否成對(duì)出現(xiàn)釋放
    tag += 1;
    jvmti_env->SetTag(object, tag);
    //獲取線程信息
    jvmtiThreadInfo threadInfo;
    jvmti_env->GetThreadInfo(thread, &threadInfo);
    //獲得 創(chuàng)建的對(duì)象的類簽名
    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;
    //獲得方法對(duì)應(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來(lái)實(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í),用頁(yè)為單位提供
//內(nèi)存分頁(yè)大小 一分頁(yè)的大小
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長(zhǎng)度的數(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的使用過程及對(duì)對(duì)象創(chuàng)建釋放钞楼、方法進(jìn)入退出事件的監(jiān)聽,最后對(duì)事件信息存文件袄琳,這樣當(dāng)應(yīng)用因?yàn)閮?nèi)存使用不當(dāng)導(dǎo)致的問題询件,通過文件就可以分析出來(lái)燃乍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市雳殊,隨后出現(xiàn)的幾起案子橘沥,更是在濱河造成了極大的恐慌,老刑警劉巖夯秃,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件座咆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡仓洼,警方通過查閱死者的電腦和手機(jī)介陶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)色建,“玉大人哺呜,你說我怎么就攤上這事』粒” “怎么了某残?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)陵吸。 經(jīng)常有香客問我玻墅,道長(zhǎng),這世上最難降的妖魔是什么壮虫? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任澳厢,我火速辦了婚禮,結(jié)果婚禮上囚似,老公的妹妹穿的比我還像新娘剩拢。我一直安慰自己,他們只是感情好饶唤,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布徐伐。 她就那樣靜靜地躺著,像睡著了一般搬素。 火紅的嫁衣襯著肌膚如雪呵晨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天熬尺,我揣著相機(jī)與錄音,去河邊找鬼谓罗。 笑死粱哼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的檩咱。 我是一名探鬼主播揭措,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼胯舷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了绊含?” 一聲冷哼從身側(cè)響起桑嘶,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎躬充,沒想到半個(gè)月后逃顶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡充甚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年以政,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伴找。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡盈蛮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出技矮,到底是詐尸還是另有隱情抖誉,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布衰倦,位于F島的核電站袒炉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏耿币。R本人自食惡果不足惜梳杏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淹接。 院中可真熱鬧十性,春花似錦、人聲如沸塑悼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厢蒜。三九已至霞势,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斑鸦,已是汗流浹背愕贡。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巷屿,地道東北人固以。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親憨琳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诫钓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容