8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
本文是本人对于 LeakCanary 团队的一篇分析内存泄漏的文章的意译。水平有限,如有不够准确之处,敬请包涵。
主旨:在Lollipop之前的版本,Dialog可能导致内存泄漏。
引言
LeakCanary 提示存在内存泄漏:
GC ROOT thread com.squareup.picasso.Dispatcher.DispatcherThread.
references android.os.Message.obj
references com.example.MyActivity$MyDialogClickListener.this$0
leaks com.example.MyActivity.MainActivity instance `
这段报告是说:一个 Picasso 线程正持有一个位于栈中的 Message 实例的局部变量,而 Message 持有 DialogInterface.OnClickListener 的引用,而 DialogInterface.OnClickListener 又持有一个被销毁 Activity 的引用。
局部变量由于仅存在于栈内,通常存活时间较短。当线程调用某个方法,系统就会为其分配栈帧。当方法返回,栈帧也会随之被销毁,栈内所有局部变量都会被回收。如果局部变量导致了内存泄漏,一般意味着线程死循环或者阻塞了,而且线程在这种状态时持有着 Message 实例的引用。
于是 Dimitris 和我都去 Picasso 源码中一探究竟:
Dispatcher.DispatcherThread 是一个简单的 HandlerThread:
static class DispatcherThread extends HandlerThread {
DispatcherThread() {
super(Utils.THREAD_PREFIX + DISPATCHER_THREAD_NAME, THREAD_PRIORITY_BACKGROUND);
}
}
这个线程通过 Handler 接收 Message,很标准的实现方式:
private static class DispatcherHandler extends Handler {
private final Dispatcher dispatcher;
public DispatcherHandler(Looper looper, Dispatcher dispatcher) {
super(looper);
this.dispatcher = dispatcher;
}
@Override public void handleMessage(final Message msg) {
switch (msg.what) {
case REQUEST_SUBMIT: {
Action action = (Action) msg.obj;
dispatcher.performSubmit(action);
break;
}
// ... handles other types of messages
}
}
}
显然 Dispatcher.DispatcherHandler.handleMessage() 里面没有明显会让本地变量持有 Message 引用的 Bug。
后来出现了越来越多内存泄漏的报告,这些报告不仅来自 Picasso,各种各样线程中的局部变量都存在内存泄漏,而且这些内存泄漏往往和 Dialog 的 click listener 有关。发生内存泄漏的线程有一个共同的特性:它们都是worker thread,而且通过某种阻塞队列接收各自的工作。
看来问题来自于Handler 和 Thread 的工作机制中。
Handler+Thread 工作原理
HandlerThread也是内部封装了Handler的Thread,让我们看看它的工作原理:
for (;;) {
Message msg = queue.next(); //从消息队列中取出消息,可能阻塞
if (msg == null) {
return;
}
msg.target.dispatchMessage(msg);//对应handler处理消息
msg.recycleUnchecked();//清空msg内容、放回消息池中
}
确实有一个本地变量持有 Message 的引用,但它的生命周期本应很短,而且在循环结束时被清除。
我们尝试通过利用阻塞队列实现一个简单的工作者线程来重现这个 Bug,它只发送一个 Message:
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what =