目錄
- 基礎知識簡介
- Linux eventfd事件等待/響應機制
- Linux IO多路複用epoll
- Android消息機制Native層分析
- nativeInit流程
- nativePollOnce流程
- nativeWake流程
- 資料
- 收穫
上一篇中關於ThreadLocal的使用,遺漏了一個點:ThreadLocal的回收,使用不當會操作內存泄漏。通過上一篇的分析我們知道了ThreadLocalMap.Entry中的key時ThreadLocal的弱引用,而value需要在堆上分配的內存,當key的強引用斷開之後,在gc的時候會被回收,就過時key爲null,但是value還存在。如果處理不當,可能會引發內存泄露。
圖片來自:徹底搞清楚ThreadLocal與弱引用
那麼key爲null了value何時被清除吶?
ThreadLocal的設計者考慮到這個問題,在每次調用ThreadLocal的set/get方法時會清除key爲null的value。但是誰知道下次什麼時候調用set/get,這是一種被動的清除方式。
除此之外ThreadLocal還提供了remove方法,可以有我們自動的進行清除。當ThreadLocal不再使用的時候,要記得調用remove。示例如下
public class ExampleUnitTest {
private static ThreadLocal<String> sStrThreadlocal = new ThreadLocal<>();
private static ThreadLocal<Integer> sIntegerThreadLocal = new ThreadLocal<>();
@Test
public void testThreadLocal(){
//在主線程給Threadlocal賦值,請取出輸出
sStrThreadlocal.set("aaa");
String value = sStrThreadlocal.get();
sIntegerThreadLocal.set(1);
Integer intValue = sIntegerThreadLocal.get();
System.out.println("111 curThreadId="+Thread.currentThread()+" strthreadLocalValue="+value
+" intThreadLocalValue="+intValue);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//最後在輸出下主線程的ThreadLocal值
value = sStrThreadlocal.get();
intValue = sIntegerThreadLocal.get();
System.out.println("444 curThreadId="+Thread.currentThread()+" strthreadLocalValue="+value
+" intThreadLocalValue="+intValue);
//用完之後記得remove,雖然,ThreadLocalMap.Entry的key是ThreadLocal的弱引用,但是value佔用的內存空間還在。
//被回收的場景有兩個,再次調用set或者get方法是會堅持map中是否有key爲空的Entry(由於key是弱引用,外部強引用依賴斷開後,gc時就會回收該key,
// 回收後即可null),如果有則清除。
//但是不知道什麼時候再次調用set或者get,這種被動的方式只能說是做了個保證,如果沒有調用set/get,就可能引發內存泄露
//ThreadLocal提供了一個remove方法,由我們主動清除ThreadLocal對應的ThreadLocalMap.Entry中的堆內存
sStrThreadlocal.remove();
sIntegerThreadLocal.remove();
value = sStrThreadlocal.get();
intValue = sIntegerThreadLocal.get();
System.out.println("after remove 444 curThreadId="+Thread.currentThread()+" strthreadLocalValue="+value
+" intThreadLocalValue="+intValue);
}
}
對應的輸出爲
111 curThreadId=Thread[main,5,main] strthreadLocalValue=aaa intThreadLocalValue=1
444 curThreadId=Thread[main,5,main] strthreadLocalValue=aaa intThreadLocalValue=1
after remove 444 curThreadId=Thread[main,5,main] strthreadLocalValue=null intThreadLocalValue=null
下面開始本篇的分析學習
我們在前面兩篇關於Android消息機制的分析學習中瞭解到MessageQueue中涉及到何Native的交互,比如nativeInit初始化Native層的MessageQueue;nativePollOnce阻塞;nativeWake喚醒。
問題是,爲什麼要這樣設計?直接Java層不是一樣可以管理維護消息隊列以及消息的讀取分發嗎?nativePollOnce真正的意義是什麼?這個問題一致困撓着我。通過反覆查資料翻源碼,終於瞭解了其流程。在消息機制Native層實現之前我們先了解下其中用到的一些Linux基礎知識。否則的話,很容易被卡住。
一、 基礎知識簡介
1.1 Linux eventfd事件等待/響應機制
Linux常用的進程/線程間通信機制有管道、信號量、消息隊列、信號、共享內存、socket等等,其中主要作爲進程/線程間通知/等待的有管道pipe和socket。從Linux 2.6.27版本開始增加了eventfd,主要用於進程或者線程間的通信,eventfd用於進程/線程間通信,效率比pipe高.
1.2 Linux IO多路複用epoll
epoll是Linux中最高效的IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作
epoll有3個方法,epoll_create(), epoll_ctl(),epoll_wait()_
epoll_create :創建epoll句柄
int epoll_create(int size);
:用於創建一個epoll的句柄,size是指監聽的描述符個數, 現在內核支持動態擴展,該值的意義僅僅是初次分配的fd個數,後面空間不夠時會動態擴容。 當創建完epoll句柄後,佔用一個fd值.
使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。
epoll_ctl:執行對需要監聽的文件描述符(fd)的操作
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
用於執行對需要監聽的文件描述符(fd)的操作,比如EPOLL_CTL_ADD
fd:需要監聽的文件描述符;
epoll_event:需要監聽的事件
epoll_wait:等待事件的到來
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的到來,有三種情況會觸發返回
1. 發生錯誤 返回值爲負數
2. 等待超時(timeout)
3. 監測到某些文件句柄上有事件發生
epoll和eventfd結合使用
eventfd中無數據,線程觸發epoll_wait()的等待,該線程處於阻塞,另外一個線程通過往eventfd中write數據,使描述符可讀,epoll返回,這就達到了喚醒的目的。_
二、Android消息機制Native層簡單分析
我們先來回顧下消息機制中Java層MessageQueue的往隊列里加消息和從隊列裏面取消息的調用
//android.os.MessageQueue#enqueueMessage
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//如果消息鏈表爲空,或者插入的Message比消息鏈表第一個消息要執行的更早,直接插入到頭部
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//否則在鏈表中找到合適位置插入
//通常情況下不需要喚醒事件隊列,除非鏈表的頭是一個同步屏障,並且該條消息是第一條異步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
//具體實現如下,這個畫張圖來說明
//鏈表引入一個prev變量,該變量指向p也message(如果是for循環的內部第一次執行),然後把p進行向next移動,和需要插入的Message進行比較when
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
//如果插入的是異步消息,並且消息鏈表第一條消息是同步屏障消息。
//或者消息鏈表中只有剛插入的這一個Message,並且mBlocked爲true即,正在阻塞狀態,收到一個消息後也進入喚醒
喚醒誰?MessageQueue.next中被阻塞的nativePollOnce
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
//android.os.MessageQueue#next
Message next() {
//native層NativeMessageQueue的指針
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
......
for (;;) {
//阻塞操作,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒
//nativePollOnce用於“等待”, 直到下一條消息可用爲止. 如果在此調用期間花費的時間很長, 表明對應線程沒有實際工作要做,或者Native層的message有耗時的操作在執行
nativePollOnce(ptr, nextPollTimeoutMillis);
}
可以看到在MessageQueue#next會調用課程阻塞的native方法nativePollOnce
,在MessageQueue#enqueueMessage 中如果需要喚醒會調用native方法nativeWake
。
問題是怎麼阻塞的,怎麼喚醒的,爲什麼要這樣設計,直接在Java層完成處理不可以嗎?
帶着這樣的困惑,開始Native層消息機制的分析學習。
2.1 MessageQueue Init流程
圖片來自:Android消息機制2-Handler(Native層)
調用Native方法初始化,返回值爲native層的NativeMessageQueue指針地址
//android.os.MessageQueue#MessageQueue
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
//調用Native方法初始化,返回值爲native層的NativeMessageQueue指針地址
mPtr = nativeInit();
}
android_os_MessageQueue_nativeInit
//java native方法 nativeInit 的jni方法,返回類型long,即 mptr
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
//NativeMessageQueue是一個內部類
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
//返回爲NativeMessageQueue指針地址
......
return reinterpret_cast<jlong>(nativeMessageQueue);
}
NativeMessageQueue構造方法
會進行Native層的Looper創建。Java層創建Looper然後再創建MessageQueue,而在Native層則剛剛相反,先創建NativeMessageQueue然後再創建Looper。
// core/jni/android_os_MessageQueue.cpp
//NativeMessageQueue構造方法
//Java層創建Looper然後再創建MessageQueue,
//而在Native層則剛剛相反,先創建NativeMessageQueue然後再創建Looper
//MessageQueue是在Java層與Native層有着緊密的聯繫,
//但是Native層的Looper與Java層的Looper沒有任何關係
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
//Looper::getForThread()獲取當前線程的Looper,相當於Java層的Looper.myLooper()
mLooper = Looper::getForThread();
if (mLooper == NULL) {
//如果爲空, new一個native層的Looper
mLooper = new Looper(false);
//相當於java層的ThreadLocal.set() ?
Looper::setForThread(mLooper);
}
}
Natvie層的Looper的構造
MessageQueue是在Java層與Native層有着緊密的聯繫,但是Native層的Looper與Java層的Looper沒有任何關係
// libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
mNextRequestSeq(0),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
//eventfd事件句柄,負責線程通信,替換了之前版本的pipe 構造喚醒事件fd
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));
AutoMutex _l(mLock);
//進行epoll句柄的創建和初始化
rebuildEpollLocked();
}
epoll句柄的創建、添加喚醒事件句柄到epoll
//libutils/Looper.cpp
void Looper::rebuildEpollLocked() {
......
//epoll_create1創建一個epoll句柄實例,並註冊wake管道
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
......
//epoll事件結構體
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd.get();
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
//將喚醒事件句柄(request.fd),添加到epolled句柄(mEpollFd.get()),爲epoll添加一個喚醒機制
int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
......
}
}
Native層的init流程主要內容如下:
- NativeQueueMessage和Looper的初始化。
- 構建了epoll句柄,向epoll中添加epoll事件註冊
2.2 消息讀取流程
圖片來自:Android消息機制2-Handler(Native層)
nativePollOnce
//core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
//將Java層傳過來的mPtr 轉換爲 nativeMessageQueue
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
//調用nativeMessageQueue的pollOnce
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
NativeMessageQueue :: pollOnce
//core/jni/android_os_MessageQueue.cpp
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
......
//又調用了Natvie的Looper的pollOnce
mLooper->pollOnce(timeoutMillis);
......
}
Native層Looper::pollOnce
//libutils/Looper.cpp
/**
* timeoutMillis:超時時長
* outFd:發生事件的文件描述符
* outEvents:當前outFd上發生的事件,包含以下4類事件
EVENT_INPUT:可讀
EVENT_OUTPUT:可寫
EVENT_ERROR:錯誤
EVENT_HANGUP:中斷
*outData:上下文數據
**/
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
......
//關鍵實現在pollInner中
result = pollInner(timeoutMillis);
}
}
Looper::pollInner
先會調用epoll_wait進入阻塞專題,喚醒的場景 想epoll中添加的epollevent等待事件發生或者超時觸發nativeWake()方法,會向eventfd寫入字符,進行喚醒。
然後進性要處理的事件收集,然後在做處理。Natvie的消息的處理順序如下
- 處理Native的Message,調用Native的Handler來處理該Message
- 處理Resposne數組,POLL_CALLBACK類型的事件
//libutils/Looper.cpp
int Looper::pollInner(int timeoutMillis) {
......
// Poll.
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
// We are about to idle.
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//等待事件發生或者超時觸發nativeWake()方法,會向eventfd寫入字符
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// No longer idling.
mPolling = false;
// Acquire lock.
mLock.lock();
// Rebuild epoll set if needed.
if (mEpollRebuildRequired) {
mEpollRebuildRequired = false;
rebuildEpollLocked();
goto Done;
}
// Check for poll error.
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
an unexpected error: %s", strerror(errno));
result = POLL_ERROR;
goto Done;
}
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd.get()) {
//已經喚醒,如果是EPOLLIN類型事件,讀取並清空eventfd中的數據
if (epollEvents & EPOLLIN) {
awoken();
}
} else {
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
pushResponse(events, mRequests.valueAt(requestIndex));
}
}
}
//上面是收集,Done中是處理的部分,很多設計都是採用這種設計,邏輯分離
Done: ;
//1. 先處理Native的Message,調用Native的Handler來處理該Message
mNextMessageUptime = LLONG_MAX;
while (mMessageEnvelopes.size() != 0) {
......
if (messageEnvelope.uptime <= now) {
{
......
handler->handleMessage(message);
}
} else {
mNextMessageUptime = messageEnvelope.uptime;
break;
}
}
//2. 再處理Resposne數組,POLL_CALLBACK類型的事件,比如一些
// Invoke all response callbacks.
for (size_t i = 0; i < mResponses.size(); i++) {
Response& response = mResponses.editItemAt(i);
if (response.request.ident == POLL_CALLBACK) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
int callbackResult = response.request.callback->handleEvent(fd, events, data);
}
}
return result;
}
消息獲取流程主要如下
- 依此調用NativeMessageQueue和looper中的pollonce,最終調用到Looper的pollInner
- pollInner會調用epoll_wait進入阻塞,喚醒的條件是epoll中添加的事件響應了或者發生了超時等。_
- 先執行Native層的Message,再執行Native層的Resposne數組,POLL_CALLBACK類型的事件(比如一些按鍵事件等)_
- 返回到Java層後再調用Java層MessageQueue中的讀取Message。
2.3 消息發送流程
圖片來自:Android消息機制2-Handler(Native層)
nativeWake
//core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
//調用NativeMessageQueue的wake
nativeMessageQueue->wake();
}
NativeMessageQueue::wake
//core/jni/android_os_MessageQueue.cpp
void NativeMessageQueue::wake() {
//調用Native層Looper的wake
mLooper->wake();
}
Looper::wake
libutils/Looper.cpp
void Looper::wake() {
//write方法 向mWakeEventfd中寫一些內容,會把epoll_wait喚醒,線程就不阻塞了繼續發送
//Native層的消息,然後處理之前的addFd事件,然後處理Java層的消息
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
......
}
消息發送流程如下
- Java層的enqueueMessage想java層的MessageQueue添加消息
- 如果需要喚醒調用native方法nativeWake
- 依次調用NativeMessageQueue和Looper的wake方法
- 最終調用writ方法想eventfd中寫入一些內容進行epoll_wait的喚醒。
Android消息機制的分析到這裏就結束了,最後來張消息處理家族類的關係圖
圖片來自:圖書:《深入理解Android 卷3》
三、資料
- Android源碼
- 圖書:《深入理解Android 卷3》
- Android消息機制2-Handler(Native層)
- Android Handler機制10之Native的實現
- Android 中 MessageQueue 的 nativePollOnce
- IO多路複用之epoll總結
- select/poll/epoll對比分析
- 深入分析 ThreadLocal 內存泄漏問題
- 徹底搞清楚ThreadLocal與弱引用
四、收穫
- 瞭解了消息機制在Native層處理流程
- 通過IO多路複用的epoll和eventfd進行事件的等待和喚醒實現
- 每次獲取消息時,先執行Native層的Message,再執行Natvice層addfd的response,之後執行Java層的MessageQueue中消息
感謝你的閱讀
Android消息機制就到這篇了,下篇我們進入Binder的分析學習,歡迎關注公衆號“音視頻開發之旅”,一起學習成長。
歡迎交流