一、远程触发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触发前时候才会被回收)