7KOOMSource

Module Dependencies

graph TB
demo-->java-oom
java-oom-->koom-kwailinker
java-oom-->koom-xhook
java-oom-->koom-shark

LeakDetector Hierarchy

graph TB
LeakDetector-->ActivityLeakDetector
LeakDetector-->NativeAllocationRegistryLeakDetector
LeakDetector-->WindowLeakDetector
LeakDetector-->BitmapLeakDetector
LeakDetector-->FragmentLeakDetector

HprofStrip Detail

KOOM.init

/**
 * KOOM entry point, make sure be called in the main thread!
 *
 * @param application application needed
 */
public static void init(Application application) {
  if (koom == null) {
    koom = new KOOM(application);
  }
  koom.start();
}
private KOOM(Application application) {
  internal = new KOOMInternal(application);
}
public KOOMInternal(Application application) {
  KUtils.startup();

  buildConfig(application);

  heapDumpTrigger = new HeapDumpTrigger();
  heapAnalysisTrigger = new HeapAnalysisTrigger();

  ProcessLifecycleOwner.get().getLifecycle().addObserver(heapAnalysisTrigger);
}

buildConfig

private void buildConfig(Application application) {
  //setApplication must be the first
  KGlobalConfig.setApplication(application);
  KGlobalConfig.setKConfig(KConfig.defaultConfig());
}

new HeapDumpTrigger

implements KTrigger

  public HeapDumpTrigger() {
    monitorManager = new MonitorManager();
    monitorManager.addMonitor(new HeapMonitor());
    heapDumper = new ForkJvmHeapDumper();
  }

MonitorManager

public MonitorManager() {
    monitors = new ArrayList<>();
    monitorThread = new MonitorThread();
}
public void addMonitor(Monitor monitor) {
    monitors.add(monitor);
 }
MonitorThread
public MonitorThread() {
  thread = new HandlerThread("MonitorThread");
  thread.start();
  handler = new Handler(thread.getLooper());
}
HeapMonitor
/* HeapMonitor watch JVM heap running info,
* and trigger when heap is over threshold
* several times as HeapThreshold set. */
class HeapMonitor implements Monitor
  @Override
  public boolean isTrigger() {
 			return currentTimes >= heapThreshold.overTimes();
  }

new ForkJvmHeapDumper

// A jvm hprof dumper which use fork and don’t block main process.

  public ForkJvmHeapDumper() {
    soLoaded = KGlobalConfig.getSoLoader().loadLib("koom-java");
    if (soLoaded) {
      initForkDump();
    }
  }

new HeapAnalysisTrigger

implements KTrigger

两个Trigger监听前后台切换通知

startInKOOMThread

public void start() {
  internal.start();
}
  public void start() {
    HandlerThread koomThread = new HandlerThread("koom");
    koomThread.start();
    koomHandler = new Handler(koomThread.getLooper());
    startInKOOMThread();
  }
private void startInKOOMThread() {
  koomHandler.postDelayed(this::startInternal, KConstants.Perf.START_DELAY);
}
private void startInternal() {
    heapDumpTrigger.setHeapDumpListener(this);
    heapAnalysisTrigger.setHeapAnalysisListener(this);

    ReanalysisChecker reanalysisChecker = new ReanalysisChecker();
    if (reanalysisChecker.detectReanalysisFile() != null) {
      KLog.i(TAG, "detected reanalysis file");
      heapAnalysisTrigger.trigger(TriggerReason.analysisReason(TriggerReason.AnalysisReason.REANALYSIS));
      return;
    }

    heapDumpTrigger.startTrack();
}

setHeapDumpAndAnalysisListener

TryTriggerAnalysisWhenDetectReanalysisFile

heapDumpTrigger.startTrack()

@Override
public void startTrack() {
  monitorManager.start();
  monitorManager.setTriggerListener((monitorType, reason) -> {
    trigger(reason);
    return true;
  });
}

monitorManager.start

public void start() {
  monitorThread.start(monitors);
}
MonitorThread.start
public void start(List<Monitor> monitors) {
  stop = false;

  List<Runnable> runnables = new ArrayList<>();
  for (Monitor monitor : monitors) {
    monitor.start();
    runnables.add(new MonitorRunnable(monitor));
  }
  for (Runnable runnable : runnables) {
    handler.post(runnable);
  }
}
HeapMonitor.start
@Override
public void start() {
  started = true;
  if (heapThreshold == null) {
    heapThreshold = KGlobalConfig.getHeapThreshold();
  }
}
MonitorRunnable.run
class MonitorRunnable implements Runnable {
      @Override
    public void run() {
      if (monitor.isTrigger()) {
        stop = monitorTriggerListener.onTrigger(monitor.monitorType(), monitor.getTriggerReason());
      }
      if (!stop) {
        handler.postDelayed(this, monitor.pollInterval());
      }
    }
}
monitorManager.setTriggerListener

HeapDumpTrigger.trigger

@Override
public void trigger(TriggerReason reason) {
  if (triggered) {
    KLog.e(TAG, "Only once trigger!");
    return;
  }
  triggered = true;

  monitorManager.stop();

  KLog.i(TAG, "trigger reason:" + reason.dumpReason);
  if (heapDumpListener != null) {
    heapDumpListener.onHeapDumpTrigger(reason.dumpReason);
  }

  try {
    doHeapDump(reason.dumpReason);
  } catch (Exception e) {
    KLog.e(TAG, "doHeapDump failed");
    e.printStackTrace();
    if (heapDumpListener != null) {
      heapDumpListener.onHeapDumpFailed();
    }
  }

  KVData.addTriggerTime(KGlobalConfig.getRunningInfoFetcher().appVersion());
}
public void doHeapDump(TriggerReason.DumpReason reason) {
  KLog.i(TAG, "doHeapDump");

  KHeapFile.getKHeapFile().buildFiles();

  HeapAnalyzeReporter.addDumpReason(reason);
  HeapAnalyzeReporter.addDeviceRunningInfo();

  boolean res = heapDumper.dump(KHeapFile.getKHeapFile().hprof.path);

  if (res) {
    heapDumpListener.onHeapDumped(reason);
  } else {
    KLog.e(TAG, "heap dump failed!");
    heapDumpListener.onHeapDumpFailed();
    KHeapFile.delete();
  }
}

ForkJvmHeapDumper.dump

@Override
public boolean dump(String path) {
      boolean dumpRes = false;
    try {
      int pid = trySuspendVMThenFork();
      if (pid == 0) {
        Debug.dumpHprofData(path);
        KLog.i(TAG, "notifyDumped:" + dumpRes);
        //System.exit(0);
        exitProcess();
      } else {
        resumeVM();
        dumpRes = waitDumping(pid);
        KLog.i(TAG, "hprof pid:" + pid + " dumped: " + path);
      }

    } catch (IOException e) {
      e.printStackTrace();
      KLog.e(TAG, "dump failed caused by IOException!");
    }
    return dumpRes;
}

trySuspendVMThenFork

/**
 * First do suspend vm, then do fork.
 * @return result of fork
 */
private native int trySuspendVMThenFork();//C层对应_ZN3art3Dbg9SuspendVMEv这个符号方法调用以及fork系统调用

forked process

Debug.dumpHprofData
exitProcess

main Process

resumeVM
  /**
   * Resume the VM.
   */
  private native void resumeVM();//对应的方法符号为“_ZN3art3Dbg8ResumeVMEv”
waitPid
private boolean waitDumping(int pid) {
  waitPid(pid);
  return true;
}
 /**
   * Wait process exit.
   *
   * @param pid waited process.
   */
  private native void waitPid(int pid);//系统调用

heapDumpListener.onHeapDumped

KOOMInternal

@Override
public void onHeapDumped(TriggerReason.DumpReason reason) {
  KLog.i(TAG, "onHeapDumped");
  changeProgress(KOOMProgressListener.Progress.HEAP_DUMPED);

  //Crash cases need to reanalyze next launch and not do analyze right now.
  if (reason != TriggerReason.DumpReason.MANUAL_TRIGGER_ON_CRASH) {
    heapAnalysisTrigger.startTrack();
  } else {
    KLog.i(TAG, "reanalysis next launch when trigger on crash");
  }
}

heapAnalysisTrigger.startTrack

@Override
public void startTrack() {
  KTriggerStrategy strategy = strategy();
  if (strategy == KTriggerStrategy.RIGHT_NOW) {
    trigger(TriggerReason.analysisReason(TriggerReason.AnalysisReason.RIGHT_NOW));
  }
}
@Override
public void trigger(TriggerReason triggerReason) {
    doAnalysis(KGlobalConfig.getApplication());

HeapAnalyzeService.runAnalysis

public void doAnalysis(Application application) {
  HeapAnalyzeService.runAnalysis(application, heapAnalysisListener);
}
public static void runAnalysis(Application application,
    HeapAnalysisListener heapAnalysisListener) {
  KLog.i(TAG, "runAnalysis startService");
  Intent intent = new Intent(application, HeapAnalyzeService.class);
  IPCReceiver ipcReceiver = buildAnalysisReceiver(heapAnalysisListener);
  intent.putExtra(KConstants.ServiceIntent.RECEIVER, ipcReceiver);
  KHeapFile heapFile = KHeapFile.getKHeapFile();
  intent.putExtra(KConstants.ServiceIntent.HEAP_FILE, heapFile);
  application.startService(intent);
}
private static IPCReceiver buildAnalysisReceiver(HeapAnalysisListener heapAnalysisListener) {
  return new IPCReceiver(new IPCReceiver.ReceiverCallback() {
    @Override
    public void onSuccess() {
      KLog.i(TAG, "IPC call back, heap analysis success");
      heapAnalysisListener.onHeapAnalyzed();
    }

    @Override
    public void onError() {
      KLog.i(TAG, "IPC call back, heap analysis failed");
      heapAnalysisListener.onHeapAnalyzeFailed();
    }
  });
}

onHandleIntent

@Override
protected void onHandleIntent(Intent intent) {
  boolean res = false;
  beforeAnalyze(intent);
  res = doAnalyze();
  if (ipcReceiver != null) {
    ipcReceiver.send(res ? IPCReceiver.RESULT_CODE_OK
        : IPCReceiver.RESULT_CODE_FAIL, null);
  }
}

heapAnalyzer.analyze

/** run in the heap_analysis process */
private boolean doAnalyze() {
  return heapAnalyzer.analyze();
}

KHeapAnalyzer.analyze

public KHeapAnalyzer(KHeapFile heapFile) {
  leaksFinder = new SuspicionLeaksFinder(heapFile.hprof);
}
public boolean analyze() {
  KLog.i(TAG, "analyze");
  Pair<List<ApplicationLeak>, List<LibraryLeak>> leaks = leaksFinder.find();
  if (leaks == null) {
    return false;
  }

  //Add gc path to report file.
  HeapAnalyzeReporter.addGCPath(leaks, leaksFinder.leakReasonTable);

  //Add done flag to report file.
  HeapAnalyzeReporter.done();
  return true;
}

SuspicionLeaksFinder.find

public SuspicionLeaksFinder(KHeapFile.Hprof hprof) {
  leakingObjects = new HashSet<>();
  leakDetectors = new ArrayList<>();
  computeGenerations = new HashSet<>();
  this.hprofFile = hprof;
}
public Pair<List<ApplicationLeak>, List<LibraryLeak>> find() {
  boolean indexed = buildIndex();//just as shark in leakcannary
  if (!indexed) {
    return null;
  }

  initLeakDetectors();

  findLeaks();

  return findPath();
}

buildIndex

……

initLeakDetectors

private void initLeakDetectors() {
  addDetector(new ActivityLeakDetector(heapGraph));
  addDetector(new FragmentLeakDetector(heapGraph));
  addDetector(new BitmapLeakDetector(heapGraph));
  addDetector(new NativeAllocationRegistryLeakDetector(heapGraph));
  addDetector(new WindowLeakDetector(heapGraph));
  ClassHierarchyFetcher.initComputeGenerations(computeGenerations);
  leakReasonTable = new HashMap<>();
}
private void addDetector(LeakDetector leakDetector) {
  leakDetectors.add(leakDetector);
  computeGenerations.add(leakDetector.generation());
}

findLeaks

//use for findLeaks, corresponding to find the leak instance during monitoring in leakcanary
public void findLeaks() {
  KLog.i(TAG, "start find leaks");
  //遍历镜像的所有instance
  Sequence<HeapObject.HeapInstance> instances = heapGraph.getInstances();
  Iterator<HeapObject.HeapInstance> instanceIterator = instances.iterator();

  while (instanceIterator.hasNext()) {
    HeapObject.HeapInstance instance = instanceIterator.next();
    if (instance.isPrimitiveWrapper()) {
      continue;
    }

    ClassHierarchyFetcher.process(instance.getInstanceClassId(),
        instance.getInstanceClass().getClassHierarchy());

    for (LeakDetector leakDetector : leakDetectors) {
      if (leakDetector.isSubClass(instance.getInstanceClassId())
          && leakDetector.isLeak(instance)) {
        ClassCounter classCounter = leakDetector.instanceCount();
        if (classCounter.leakInstancesCount <=
            SAME_CLASS_LEAK_OBJECT_GC_PATH_THRESHOLD) {
          leakingObjects.add(instance.getObjectId());
          leakReasonTable.put(instance.getObjectId(), leakDetector.leakReason());
        }
      }
    }
  }

  //关注class和对应instance数量,加入json
  HeapAnalyzeReporter.addClassInfo(leakDetectors);

  findPrimitiveArrayLeaks();
  findObjectArrayLeaks();
}

findPath

public Pair<List<ApplicationLeak>, List<LibraryLeak>> findPath() {
  KLog.i(TAG, "findPath object size:" + leakingObjects.size());
  HeapAnalyzer.FindLeakInput findLeakInput = new HeapAnalyzer.FindLeakInput(heapGraph,
      AndroidReferenceMatchers.Companion.getAppDefaults(),
      false, Collections.emptyList());
  kotlin.Pair<List<ApplicationLeak>, List<LibraryLeak>> pair =
      new HeapAnalyzer(step -> KLog.i(TAG, "step:" + step.name()))
          .findLeaks(findLeakInput, leakingObjects, true);
  return new Pair<>((List<ApplicationLeak>) pair.getFirst(),
      (List<LibraryLeak>) pair.getSecond());
}

HeapAnalyzeReporter.addGCPath

HeapAnalyzeReporter.done

onSuccess–>heapAnalysisListener.onHeapAnalyzed

参考

KGlobalConfig

public static String getReportDir() {
  return reportDir = getRootDir() + File.separator + REPORT_DIR;
}

public static String getHprofDir() {
  return hprofDir = getRootDir() + File.separator + HPROF_DIR;
}

//* See KHeapFile, each contains a pair of hprof and report which file prefix is same.

ReanalysisChecker

public KHeapFile detectReanalysisFile() {
  File reportDir = new File(KGlobalConfig.getReportDir());
  File[] reports = reportDir.listFiles();
  if (reports == null) {
    return null;
  }
  for (File report : reports) {
    HeapReport heapReport = loadFile(report);
    if (analysisNotDone(heapReport)) {
      if (!overReanalysisMaxTimes(heapReport)) {
        KLog.i(TAG, "find reanalyze report");
        return buildKHeapFile(report);
      } else {
        KLog.e(TAG, "Reanalyze " + report.getName() + " too many times");
        //Reanalyze too many times, and the hporf is abnormal, so delete them.
        File hprof = findHprof(getReportFilePrefix(report));
        if (hprof != null) {
          hprof.delete();
        }
        report.delete();
      }
    }
  }
  return null;
}

buildKHeapFile

private KHeapFile buildKHeapFile(File report) {
  String reportPrefix = getReportFilePrefix(report);
  File hprof = findHprof(reportPrefix);
  if (hprof != null) {
    return KHeapFile.buildInstance(hprof, report);
  } else {
    KLog.e(TAG, "Reanalyze hprof file not found!");
    report.delete();
  }
  return null;
}

KHeapFile

implements Parcelable
public Hprof hprof;
public Report report;

buildInstance

public static KHeapFile buildInstance(File hprof, File report) {
  kHeapFile = getKHeapFile();
  kHeapFile.hprof = new Hprof(hprof.getAbsolutePath());
  kHeapFile.report = new Report(report.getAbsolutePath());
  return kHeapFile;
}

HeapAnalyzer

FindLeakInput.findLeaks

//corresponding to shark in leakcanary
//增加同类对象数量阈值剪枝
public fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>, enableSameInstanceThreshold: Boolean): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
  SharkLog.d { "start findLeaks" }

  val pathFinder = PathFinder(graph, listener, referenceMatchers, enableSameInstanceThreshold)
  val pathFindingResults =
      pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)

  SharkLog.d { "Found ${leakingObjectIds.size} retained objects" }

  return buildLeakTraces(pathFindingResults)
}

ClassHierarchyFetcher

// key: current class ; value: subclass according to every detector class
private Map<Long, List<ClassGeneration>> classGenerations;

getIdOfGeneration

public static long getIdOfGeneration(long classId, int generation) {
  List<ClassGeneration> generations = getClassGenerations().get(classId);
  if (generations == null) {
    return 0;
  }
  for (ClassGeneration classGeneration : generations) {
    if (classGeneration.generation == generation) {
      return classGeneration.id;
    }
  }
  return 0;
}

LeakDetector

isSubClass

/**
 * LeakDetector try's to find the detect class's leaked instance and first we need to judge
 * whether the object's class is sub class of LeakDetector's detect class.
 * <p>
 * See generation(), if the instance's class is sub class of detect class, then it's
 * 'Generation' position class id  is same with the detect class.
 * <p>
 * Using generation to judge subclass instance is the relatively more efficient way.
 *
 * @param classId instance class id
 * @return whether the instance class id is sub class of this leak detector's detect class.
 */
boolean isSubClass(long classId) {
  return ClassHierarchyFetcher.getIdOfGeneration(classId, generation()) == classId();
}

Hprof_dump.cpp

JNIEXPORT jboolean JNICALL
Java_com_kwai_koom_javaoom_dump_ForkJvmHeapDumper_initForkDump(JNIEnv *env, jobject jObject) {
  return initForkVMSymbols();
}

bool initForkVMSymbols() {
  bool res = false;

  void *libHandle = kwai::linker::DlFcn::dlopen("libart.so", RTLD_NOW);

  suspendVM = (void (*)())kwai::linker::DlFcn::dlsym(libHandle, "_ZN3art3Dbg9SuspendVMEv");

  resumeVM = (void (*)())kwai::linker::DlFcn::dlsym(libHandle, "_ZN3art3Dbg8ResumeVMEv");

  kwai::linker::DlFcn::dlclose(libHandle);
  return suspendVM != nullptr && resumeVM != nullptr;
}

exitProcess/waitPid

JNIEXPORT void JNICALL
Java_com_kwai_koom_javaoom_dump_ForkJvmHeapDumper_exitProcess(JNIEnv *env, jobject jObject) {
  _exit(0);
}

JNIEXPORT void JNICALL Java_com_kwai_koom_javaoom_dump_ForkJvmHeapDumper_waitPid(JNIEnv *env,
                                                                                 jobject jObject,
                                                                                 jint pid) {
  int status;
  waitpid(pid, &status, 0);
}

initStripDump(used by StripHprofHeapDumper)

JNIEXPORT void JNICALL
Java_com_kwai_koom_javaoom_dump_StripHprofHeapDumper_initStripDump(JNIEnv *env, jobject jObject) {
  hprofFd = -1;
  hprofName = nullptr;
  isDumpHookSucc = false;

  xhook_enable_debug(0);

  /**
   *
   * android 7.x,write方法在libc.so中
   * android 8-9,write方法在libart.so中
   * android 10,write方法在libartbase.so中
   * libbase.so是一个保险操作,防止前面2个so里面都hook不到(:
   *
   * android 7-10版本,open方法都在libart.so中
   * libbase.so与libartbase.so,为保险操作
   */
  xhook_register("libart.so", "open", (void *)hook_open, nullptr);
  xhook_register("libbase.so", "open", (void *)hook_open, nullptr);
  xhook_register("libartbase.so", "open", (void *)hook_open, nullptr);

  xhook_register("libc.so", "write", (void *)hook_write, nullptr);
  xhook_register("libart.so", "write", (void *)hook_write, nullptr);
  xhook_register("libbase.so", "write", (void *)hook_write, nullptr);
  xhook_register("libartbase.so", "write", (void *)hook_write, nullptr);

  xhook_refresh(0);
  xhook_clear();
}

hook_write

ssize_t hook_write(int fd, const void *buf, size_t count) {
  
    if (fd != hprofFd) {
    ssize_t total_write = write(fd, buf, count);
    return total_write;
  }

  //每次hook_write,初始化重置
  reset();

  const unsigned char tag = ((unsigned char *)buf)[0];
  //删除掉无关record tag类型匹配,只匹配heap相关提高性能
  switch (tag) {
  case HPROF_TAG_HEAP_DUMP:
  case HPROF_TAG_HEAP_DUMP_SEGMENT: {
    processHeap(buf, HEAP_TAG_BYTE_SIZE + RECORD_TIME_BYTE_SIZE + RECORD_LENGTH_BYTE_SIZE, count,
                heapSerialNum, 0);
    heapSerialNum++;
  } break;
  default:
    break;
  }
  
  //根据裁剪掉的zygote space和image space更新length
  int recordLength = 0;
  if (tag == HPROF_TAG_HEAP_DUMP || tag == HPROF_TAG_HEAP_DUMP_SEGMENT) {
    recordLength =
        getIntFromBytes((unsigned char *)buf, HEAP_TAG_BYTE_SIZE + RECORD_TIME_BYTE_SIZE);
    recordLength -= stripBytesSum;
    int index = HEAP_TAG_BYTE_SIZE + RECORD_TIME_BYTE_SIZE;
    ((unsigned char *)buf)[index] =
        (unsigned char)(((unsigned int)recordLength & 0xff000000u) >> 24u);
    ((unsigned char *)buf)[index + 1] =
        (unsigned char)(((unsigned int)recordLength & 0x00ff0000u) >> 16u);
    ((unsigned char *)buf)[index + 2] =
        (unsigned char)(((unsigned int)recordLength & 0x0000ff00u) >> 8u);
    ((unsigned char *)buf)[index + 3] = (unsigned char)((unsigned int)recordLength & 0x000000ffu);
  }
  
  ssize_t total_write = 0;
  int startIndex = 0;
  for (int i = 0; i < stripIndex; i++) {
    //将裁剪掉的区间,通过写时过滤掉, that is, only write stripIndexListPair[i * 2 + 1] —— stripIndexListPair[(i + 1) * 2] area
    void *writeBuf = (void *)((unsigned char *)buf + startIndex);
    auto writeLen = (size_t)(stripIndexListPair[i * 2] - startIndex);
    if (writeLen > 0) {
      total_write += write(fd, writeBuf, writeLen);
    } else if (writeLen < 0) {
      __android_log_print(ANDROID_LOG_ERROR, "HprofDump", "hook_write array i:%d writeLen<0:%lu", i,
                          writeLen);
    }
    startIndex = stripIndexListPair[i * 2 + 1];
  }
  //write tail bytes
  auto writeLen = (size_t)(count - startIndex);
  if (writeLen > 0) {
    void *writeBuf = (void *)((unsigned char *)buf + startIndex);
    total_write += write(fd, writeBuf, count - startIndex);
  }
  
  hookWriteSerialNum++;

  if (total_write != count) {
    __android_log_print(ANDROID_LOG_INFO, "HprofDump", "hook write, hprof strip happens");
  }

  return count;
}

processHeap

int processHeap(const void *buf, int firstIndex, int maxLen, int heapSerialNo, int arraySerialNo) {
    if (firstIndex >= maxLen) {
    return arraySerialNo;
  }

  const unsigned char subtag = ((unsigned char *)buf)[firstIndex];
  switch (subtag) {
    case HPROF_OBJECT_ARRAY_DUMP: {
    int length = getIntFromBytes((unsigned char *)buf, firstIndex + HEAP_TAG_BYTE_SIZE +
                                                           OBJECT_ID_BYTE_SIZE +
                                                           STACK_TRACE_SERIAL_NUMBER_BYTE_SIZE);

    //裁剪掉system space
    //stripBytesSum = 本次记录的stripIndexListPair区间(待裁剪掉的区域)长度,
    //stripIndex++固定一次;
    if (isCurrentSystemHeap) {
      stripIndexListPair[stripIndex * 2] = firstIndex;
      stripIndexListPair[stripIndex * 2 + 1] = firstIndex + HEAP_TAG_BYTE_SIZE +
                                               OBJECT_ID_BYTE_SIZE +
                                               STACK_TRACE_SERIAL_NUMBER_BYTE_SIZE + U4 /*Length*/
                                               + CLASS_ID_BYTE_SIZE + U4 /*Id*/ * length;
      stripIndex++;

      stripBytesSum += HEAP_TAG_BYTE_SIZE + OBJECT_ID_BYTE_SIZE +
                       STACK_TRACE_SERIAL_NUMBER_BYTE_SIZE + U4 /*Length*/
                       + CLASS_ID_BYTE_SIZE + U4 /*Id*/ * length;
    }

    arraySerialNo = processHeap(buf,
                                firstIndex + HEAP_TAG_BYTE_SIZE + OBJECT_ID_BYTE_SIZE +
                                    STACK_TRACE_SERIAL_NUMBER_BYTE_SIZE + U4 /*Length*/
                                    + CLASS_ID_BYTE_SIZE + U4 /*Id*/ * length,
                                maxLen, heapSerialNo, arraySerialNo);
  } break;
  }
}

reset

const int STRIP_LIST_LENGTH = 65536 * 2 * 2 + 2;
int stripIndexListPair[STRIP_LIST_LENGTH];//stripIndex*2 —— stripIndex*2+1 indicate that area need to be stripped
int stripIndex = 0;
int stripBytesSum = 0;

void reset() {
  stripIndex = 0;
  stripBytesSum = 0;
}