Android性能:远程触发GC

Posted by JackPeng on September 9, 2016

一、远程触发GC原理

我们都知道 GC 是java虚拟机释放内存的机制。

一般的在当前进程触发GC有两种方式:

  • 主动触发。调用System.gc()
  • 被动触发。预分配的内存不足 or OOM之前

有没有办法跨进程 做GC操作呢? 答案是肯定的。因为Android Moniter中就给我们提供了 Initiate GC 功能,可以对Debug版进程 远程触发GC动作:

这个功能是Android Studio中自带的, 到底是如何实现的呢? 通过阅读android源码, 发现了Android 框架中为每个进程都预留了 远程触发GC的接口:

// URL: http://androidxref.com/4.4.4_r1/xref/dalvik/vm/SignalCatcher.cpp#signalCatcherThreadStart
static void* signalCatcherThreadStart(void* arg)
{
// ... (略)
sigemptyset(&mask);
sigaddset(&mask, SIGUSR1);

// ... (略)

while (true) {
    // ...
    cc = sigwait(&mask, &rcvd);
    // ...
    if (gDvm.haltSignalCatcher)
        break;
    // ...
    switch (rcvd) {
    // ...
    case SIGUSR1:
        handleSigUsr1();
        break;
    // ...
    }
}

return NULL;
}

而 handleSigUsr1() 函数是干啥子的呢?

// http://androidxref.com/4.4.4_r1/xref/dalvik/vm/SignalCatcher.cpp#handleSigUsr1
static void handleSigUsr1()
{
  ALOGI("SIGUSR1 forcing GC (no HPROF)");
  dvmCollectGarbage(); /*GC函数*/
}

看到这里已经能确定, 可以通过发送信号 SIGUSR1 来触发GC了。 SIGUSR1对应的ID是多少呢?

//http://androidxref.com/4.4.4_r1/xref/bionic/libc/kernel/arch-arm/asm/signal.h#39
#define SIGUSR1 10

到此, 就确定了全程触发GC的方法: kill -10 PID

二、验证结论

俗话说,没有经过验证的结论都是耍流氓。 so, 本着严谨的态度, 写了一段代码来验证,如下:

public class MainActivity extends ActionBarActivity {

/*用来存放对象的List*/
static List<Object> list = new ArrayList<>();

/*用来创建软引用的类*/
static class SoftObj {
    public SoftObj() {
        System.out.println("SoftObj(" + this + ") inited");
    }

    @Override
    public void finalize() {
        System.out.println("SoftObj(" + this + ") finalize");
    }
}

 /*用来创建弱引用的类*/
static class WeakObj {
    public WeakObj() {
        System.out.println("WeakObj(" + this + ") inited");
    }

    @Override
    public void finalize() {
        System.out.println("WeakObj(" + this + ") finalize");
    }
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(com.ddlx.amaptesttools.R.layout.activity_main);

    /*创建 6 个软引用对象*/
    list.add(new SoftReference<Object>(new SoftObj()));
    list.add(new SoftReference<Object>(new SoftObj()));
    list.add(new SoftReference<Object>(new SoftObj()));
    list.add(new SoftReference<Object>(new SoftObj()));
    list.add(new SoftReference<Object>(new SoftObj()));
    list.add(new SoftReference<Object>(new SoftObj()));

    /*创建 6 个弱引用对象*/
    list.add(new WeakReference<Object>(new WeakObj()));
    list.add(new WeakReference<Object>(new WeakObj()));
    list.add(new WeakReference<Object>(new WeakObj()));
    list.add(new WeakReference<Object>(new WeakObj()));
    list.add(new WeakReference<Object>(new WeakObj()));
    list.add(new WeakReference<Object>(new WeakObj()));
}
}

编译成APK后,我在adbshell 中敲入了如下命令:

 C:\Users\sy101268>adb shell
shell@mx3:/ $ su
root@mx3:/ # ps | grep ddlx
u0_a316   188767 2136  923856 64372 ffffffff 4006f78c S com.ddlx
root@mx3:/ # kill -10 18767

通过Logcat 看到了如下日志:

// APK启动时输出的日志
07-19 16:03:22.290 18767-18767/com.ddlx  I/System.out:     SoftObj(com.ddlx.MainActivity$SoftObj@427d92c0) inited
07-19 16:03:22.290 18767-18767/com.ddlx  I/System.out: SoftObj(com.ddlx.MainActivity$SoftObj@427d9920) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: SoftObj(com.ddlx.MainActivity$SoftObj@427d9d08) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: SoftObj(com.ddlx.MainActivity$SoftObj@427da0f0) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: SoftObj(com.ddlx.MainActivity$SoftObj@427da4d8) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: SoftObj(com.ddlx.MainActivity$SoftObj@427da8c0) inited

07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427daf68) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427db428) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427db810) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427dbbf8) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427dbfe0) inited
07-19 16:03:22.295 18767-18767/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427dc3c8) inited

/*********************************************************/

// 调用 kill -10 命令后,输出的日志
07-19 16:04:25.795 18767-18772/com.ddlx  I/dalvikvm: SIGUSR1 forcing GC(no HPROF)
07-19 16:04:25.835 18767-18772/com.ddlx  D/dalvikvm: GC_EXPLICIT freed 94K, 5% free 17788K/18624K, paused 3ms+4ms, total 39ms

07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: SoftObj(com.ddlx.MainActivity$SoftObj@427d9920) finalize
07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: SoftObj(com.ddlx.MainActivity$SoftObj@427da0f0) finalize
07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: SoftObj(com.ddlx.MainActivity$SoftObj@427da8c0) finalize

07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427daf68) finalize
07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427db428) finalize
07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427db810) finalize
07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427dbbf8) finalize
07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427dbfe0) finalize
07-19 16:04:25.840 18767-18776/com.ddlx  I/System.out: WeakObj(com.ddlx.MainActivity$WeakObj@427dc3c8) finalize

至此, 验证通过了~,一切看上去很完美~。。。 等等, 为啥 SoftObj会被回收? 并且只回收了一半?Java书上不是这么说的哇

揭开这个谜底就需要对Android虚拟机有一定的了解了。 我们知道Android的虚拟机是定制的,基于移动平台的解决方案。移动平台上,内存往往并不那么充足,所以google改变了内存回收策略。

Android上对于软引用, 改成了每次GC都回收一半的策略。(在Java HotSpot虚拟机中, 软引用只有在OOM触发前时候才会被回收)