国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频

大廠干掉 OOM 的套路,你知道幾個(gè) !

共 16112字,需瀏覽 33分鐘

 ·

2022-05-23 21:54

點(diǎn)擊上方“碼農(nóng)突圍”,馬上關(guān)注
這里是碼農(nóng)充電第一站,回復(fù)“666”,獲取一份專屬大禮包

真愛,請(qǐng)?jiān)O(shè)置“星標(biāo)”或點(diǎn)個(gè)“在看”

文章來源:https://c1n.cn/5ug0H


目錄
  • 前言

  • OOM 問題分類

  • 線程數(shù)太多

  • 打開太多文件

  • 內(nèi)存不足

  • 總結(jié)


前言


隨著項(xiàng)目不斷壯大,OOM(Out Of Memory)成為奔潰統(tǒng)計(jì)平臺(tái)上的疑難雜癥之一。


大部分業(yè)務(wù)開發(fā)人員對(duì)于線上 OOM 問題一般都是暫不處理:

  • 一方面是因?yàn)?OOM 問題沒有足夠的 log,無法在短期內(nèi)分析解決。

  • 另一方面可能是忙于業(yè)務(wù)迭代、身心疲憊,沒有精力去研究 OOM 的解決方案。


這篇文章將以線上 OOM 問題作為切入點(diǎn),介紹常見的 OOM 類型、OOM 的原理、大廠 OOM 優(yōu)化黑科技、以及主流的 OOM 監(jiān)控方案。文章較長(zhǎng),請(qǐng)備好小板凳!


OOM 問題分類


很多人對(duì)于 OOM 的理解就是 Java 虛擬機(jī)內(nèi)存不足,但通過線上 OOM 問題分析,OOM 可以大致歸為以下 3 類:

  • 線程數(shù)太多

  • 打開太多文件

  • 內(nèi)存不足


接下來將分別圍繞這三類問題進(jìn)行展開分析。


線程數(shù)太多


| 報(bào)錯(cuò)信息

pthread_create?(1040KB?stack)?failed:?Out?of?memory


這個(gè)是典型的創(chuàng)建新線程觸發(fā)的 OOM 問題。

| 源碼分析

pthread_create 觸發(fā)的 OOM 異常,源碼(Android 9)位置如下:?
http://androidxref.com/9.0.0_r3/xref/art/runtime/thread.cc


void?Thread::CreateNativeThread(JNIEnv*?env,?jobject?java_peer,?size_t?stack_size,?bool?is_daemon)?{
??...
??pthread_create_result?=?pthread_create(...)
??//創(chuàng)建線程成功
??if?(pthread_create_result?==?0)?{
??????return;
??}
??//創(chuàng)建線程失敗
??...
??{
????std::string?msg(child_jni_env_ext.get()?==?nullptr??
????????StringPrintf("Could?not?allocate?JNI?Env:?%s",?error_msg.c_str())?:
????????StringPrintf("pthread_create?(%s?stack)?failed:?%s",
?????????????????????????????????PrettySize(stack_size).c_str(),?strerror(pthread_create_result)));
????ScopedObjectAccess?soa(env);
????soa.Self()->ThrowOutOfMemoryError(msg.c_str());
??}
}


pthread_create 里面會(huì)調(diào)用 Linux 內(nèi)核創(chuàng)建線程,那什么情況下會(huì)創(chuàng)建線程失敗呢?


查看系統(tǒng)對(duì)每個(gè)進(jìn)程的線程數(shù)限制:

cat?/proc/sys/kernel/threads-max


不同設(shè)備的 threads-max 限制是不一樣的,有些廠商的低端機(jī)型 threads-max 比較小,容易出現(xiàn)此類 OOM 問題。


查看當(dāng)前進(jìn)程運(yùn)行的線程數(shù):

cat?proc/{pid}/status


當(dāng)線程數(shù)超過 /proc/sys/kernel/threads-max 中規(guī)定的上限時(shí)就會(huì)觸發(fā) OOM。


既然系統(tǒng)對(duì)每個(gè)進(jìn)程的線程數(shù)有限制,那么解決這個(gè)問題的關(guān)鍵就是盡可能降低線程數(shù)的峰值。


| 線程優(yōu)化

①禁用 new Thread


解決線程過多問題,傳統(tǒng)的方案是禁止使用 new Thread,統(tǒng)一使用線程池,但是一般很難人為控制, 可以在代碼提交之后觸發(fā)自動(dòng)檢測(cè),有問題則通過郵件通知對(duì)應(yīng)開發(fā)人員。


不過這種方式存在兩個(gè)問題:

  • 無法解決老代碼的 new Thread

  • 對(duì)于第三方庫無法控制


②無侵入性的 new Thread 優(yōu)化


Java 層的 Thread 只是一個(gè)普通的對(duì)象,只有調(diào)用了 start 方法,才會(huì)調(diào)用 native 層去創(chuàng)建線程。


所以理論上我們可以自定義 Thread,重寫 start 方法,不去啟動(dòng)線程,而是將任務(wù)放到線程池中去執(zhí)行,為了做到無侵入性,需要在編譯期通過字節(jié)碼插樁的方式,將所有 new Thread 字節(jié)碼都替換成 new 自定義 Thread。


步驟如下:


創(chuàng)建一個(gè) Thread 的子類叫 ShadowThread 吧,重寫 start 方法,調(diào)用自定義的線程池 CustomThreadPool 來執(zhí)行任務(wù)。
public?class?ShadowThread?extends?Thread?{

????@Override
????public?synchronized?void?start()?{
????????Log.i("ShadowThread",?"start,name="+?getName());
????????CustomThreadPool.THREAD_POOL_EXECUTOR.execute(new?MyRunnable(getName()));
????}

????class?MyRunnable?implements?Runnable?{

????????String?name;
????????public?MyRunnable(String?name){
????????????this.name?=?name;
????????}

????????@Override
????????public?void?run()?{
????????????try?{
????????????????ShadowThread.this.run();
????????????????Log.d("ShadowThread","run?name="+name);
????????????}?catch?(Exception?e)?{
????????????????Log.w("ShadowThread","name="+name+",exception:"+?e.getMessage());
????????????????RuntimeException?exception?=?new?RuntimeException("threadName="+name+",exception:"+?e.getMessage());
????????????????exception.setStackTrace(e.getStackTrace());
????????????????throw?exception;
????????????}
????????}
????}
}


在編譯期,hook 所有 new Thread 字節(jié)碼,全部替換成我們自定義的 ShadowThread,這個(gè)難度應(yīng)該不大,按部就班,我們先確認(rèn) new Thread 和 new ShadowThread 對(duì)應(yīng)字節(jié)碼差異。


可以安裝一個(gè) ASM Bytecode Viewer 插件,如下所示:
通過字節(jié)碼修改,你可以簡(jiǎn)單理解為做如下替換:

由于將任務(wù)放到線程池去執(zhí)行,假如線程奔潰了,我們不知道是哪個(gè)線程出問題,所以自定義 ShadowThread 中的內(nèi)部類 MyRunnable 的作用是:在線程出現(xiàn)異常的時(shí)候,將異常捕獲,還原它的名字,重新拋出一個(gè)信息更全的異常。


測(cè)試代碼:
????private?fun?testThreadCrash()?{
????????Thread?{
????????????val?i?=?9?/?0
????????}.apply?{
????????????name?=?"testThreadCrash"
????????}.start()
????}


開啟一個(gè)線程,然后觸發(fā)奔潰,堆棧信息如下:

可以看到原本的 new Thread 已經(jīng)被優(yōu)化成了 CustomThreadPool 線程池調(diào)用,并且奔潰的時(shí)候不用擔(dān)心找不到線程是哪里創(chuàng)建的,會(huì)還原線程名。


當(dāng)然這種方式有一個(gè)小問題,應(yīng)用正常運(yùn)行的情況下,如果你想要收集所有線程信息,那么線程名可能不太準(zhǔn)確,因?yàn)橥ㄟ^ new Thread 去創(chuàng)建線程,已經(jīng)被替換成線程池調(diào)用了,獲取到的線程名是線程池中的線程的名字。


數(shù)據(jù)對(duì)比:同個(gè)場(chǎng)景簡(jiǎn)單測(cè)試了一下 new Thread 優(yōu)化前后線程數(shù)峰值對(duì)比如下圖。

對(duì)于不同 App,優(yōu)化效果會(huì)有一些不同,不過可以看到這個(gè)優(yōu)化確實(shí)是有效的。


③無侵入的線程池優(yōu)化


隨著項(xiàng)目引入的 SDK 越來越多,絕大部分 SDK 內(nèi)部都會(huì)使用自己的線程池做異步操作,線程池的參數(shù)如果設(shè)置不對(duì),核心線程空閑的時(shí)候沒有釋放,會(huì)使整體的線程數(shù)量處于較高位置。
????public?ThreadPoolExecutor(int?corePoolSize,
??????????????????????????????int?maximumPoolSize,
??????????????????????????????long?keepAliveTime,
??????????????????????????????TimeUnit?unit,
??????????????????????????????BlockingQueue?workQueue,
??????????????????????????????ThreadFactory?threadFactory)
?
{
????????this(corePoolSize,?maximumPoolSize,?keepAliveTime,?unit,?workQueue,
?????????????threadFactory,?defaultHandler);
????}


線程池幾個(gè)參數(shù):
  • corePoolSize:核心線程數(shù)量。核心線程默認(rèn)情況下即使空閑也不會(huì)釋放,除非設(shè)置 allowCoreThreadTimeOut 為 true。

  • maximumPoolSize:最大線程數(shù)量。任務(wù)數(shù)量超過核心線程數(shù),就會(huì)將任務(wù)放到隊(duì)列中,隊(duì)列滿了,就會(huì)啟動(dòng)非核心線程執(zhí)行任務(wù),線程數(shù)超過這個(gè)限制就會(huì)走拒絕策略。

  • keepAliveTime:空閑線程存活時(shí)間。

  • unit:時(shí)間單位。

  • workQueue:隊(duì)列。任務(wù)數(shù)量超過核心線程數(shù),就會(huì)將任務(wù)放到這個(gè)隊(duì)列中,直到隊(duì)列滿,就開啟新線程,執(zhí)行隊(duì)列第一個(gè)任務(wù)。

  • threadFactory:線程工廠。實(shí)現(xiàn) new Thread 方法創(chuàng)建線程。


通過線程池參數(shù),我們可以找到優(yōu)化點(diǎn)如下:
  • 限制空閑線程存活時(shí)間,keepAliveTime?設(shè)置小一點(diǎn),例如 1-3s

  • 允許核心線程在空閑時(shí)自動(dòng)銷毀
executor.allowCoreThreadTimeOut(true)


如何做呢?為了做到無侵入性,依然采用 ASM 操作字節(jié)碼,跟 new Thread 的替換基本同理。


在編譯期,通過 ASM,做如下幾個(gè)操作:
  • 將調(diào)用 Executors 類的靜態(tài)方法替換為自定義 ShadowExecutors 的靜態(tài)方法,設(shè)置 executor.allowCoreThreadTimeOut(true);

  • 將調(diào)用 ThreadPoolExecutor 類的構(gòu)造方法替換為自定義 ShadowThreadPoolExecutor 的靜態(tài)方法,設(shè)置 executor.allowCoreThreadTimeOut(true);

  • 可以在 Application 類的?()?中調(diào)用我們自定義的靜態(tài)方法 ShadowAsyncTask.optimizeAsyncTaskExecutor()?來修改 AsyncTask 的線程池參數(shù),調(diào)用 executor.allowCoreThreadTimeOut(true);


你可以簡(jiǎn)單理解為做如下替換:?
詳細(xì)代碼可以參考 booster。
https://booster.johnsonlee.io/zh/guide/performance/multithreading-optimization.html#%E7%BA%BF%E7%A8%8B%E7%AE%A1%E7%90%86%E9%9D%A2%E4%B8%B4%E7%9A%84%E6%8C%91%E6%88%98


| 線程監(jiān)控

假如線程優(yōu)化后還存在創(chuàng)建線程 OOM 問題,那我們就需要監(jiān)控是否存在線程泄漏的情況。


①線程泄漏監(jiān)控


主要監(jiān)控 native 線程的幾個(gè)生命周期方法:

  • pthread_create

  • pthread_detach

  • pthread_join

  • pthread_exit


hook 以上幾個(gè)方法,用于記錄線程的生命周期和堆棧,名稱等信息;當(dāng)發(fā)現(xiàn)一個(gè) joinable 的線程在沒有 detach 或者 join 的情況下,執(zhí)行了 pthread_exit,則記錄下泄露線程信息;在合適的時(shí)機(jī),上報(bào)線程泄露信息。


linux 線程中,pthread 有兩種狀態(tài) joinable 狀態(tài)和 unjoinable 狀態(tài)。


joinable 狀態(tài)下,當(dāng)線程函數(shù)自己返回退出時(shí)或 pthread_exit 時(shí)都不會(huì)釋放線程所占用堆棧和線程描述符。


只有當(dāng)你調(diào)用了 pthread_join 之后這些資源才會(huì)被釋放,需要 main 函數(shù)或者其他線程去調(diào)用 pthread_join 函數(shù)。


具體代碼可以參考:KOOM-thread_holder。
https://github.com/KwaiAppTeam/KOOM/blob/master/koom-thread-leak/src/main/cpp/src/thread/thread_holder.cpp


②線程上報(bào)


當(dāng)監(jiān)控到線程有異常的時(shí)候,我們可以收集線程信息,上報(bào)到后臺(tái)進(jìn)行分析。


收集線程信息代碼如下:
????private?fun?dumpThreadIfNeed()?{

????????val?threadNames?=?runCatching?{?File("/proc/self/task").listFiles()?}
????????????.getOrElse?{
????????????????return@getOrElse?emptyArray()
????????????}
?????????????.map?{
????????????????runCatching?{?File(it,?"comm").readText()?}.getOrElse?{?"failed?to?read?$it/comm"?}
????????????}
?????????????.map?{
????????????????if?(it.endsWith("\n"))?it.substring(0,?it.length?-?1)?else?it
????????????}
?????????????:?emptyList()

????????Log.d("TAG",?"dumpThread?=?"?+?threadNames.joinToString(separator?=?","))
????}


接下來介紹打開太多文件導(dǎo)致的 OOM 問題。


打開太多文件


| 錯(cuò)誤信息

E/art:?ashmem_create_region?failed?for?'indirect?ref?table':?Too?many?open?files
Java.lang.OutOfMemoryError:?Could?not?allocate?JNI?Env


這個(gè)問題跟系統(tǒng)、廠商關(guān)系比較大。


| 系統(tǒng)限制

Android 是基于 Linux 內(nèi)核,/proc/pid/limits 描述著 linux 系統(tǒng)對(duì)每個(gè)進(jìn)程的一些資源限制,如下圖是一臺(tái) Android 6.0 的設(shè)備,Max open files 的限制是 1024。
如果沒有 root 權(quán)限,可以通過 ulimit -n 命令查看 Max open files,結(jié)果是一樣的。
ulimit?-n


Linux 系統(tǒng)一切皆文件,進(jìn)程每打開一個(gè)文件就會(huì)產(chǎn)生一個(gè)文件描述符 fd(記錄在 /proc/pid/fd 下面)。

cd?/proc/10654/fd
ls


這些 fd 文件都是鏈接文件,通過 ls -l 可以查看其對(duì)應(yīng)的真實(shí)文件路徑。

當(dāng) fd 的數(shù)目達(dá)到 Max open files 規(guī)定的數(shù)目,就會(huì)觸發(fā) Too many open files 的奔潰,這種奔潰在低端機(jī)上比較容易復(fù)現(xiàn)。


知道了文件描述符這玩意后,看看怎么優(yōu)化。


| 文件描述符優(yōu)化

對(duì)于打開文件數(shù)太多的問題,盲目?jī)?yōu)化其實(shí)無從下手,總體的方案是監(jiān)控為主。


通過如下代碼可以查看當(dāng)前進(jìn)程的 fd 信息:
????private?fun?dumpFd()?{
????????val?fdNames?=?runCatching?{?File("/proc/self/fd").listFiles()?}
????????????.getOrElse?{
????????????????return@getOrElse?emptyArray()
????????????}
?????????????.map?{?file?->
????????????????runCatching?{?Os.readlink(file.path)?}.getOrElse?{?"failed?to?read?link?${file.path}"?}
????????????}
?????????????:?emptyList()

????????Log.d("TAG",?"dumpFd:?size=${fdNames.size},fdNames=$fdNames")

????}


| 文件描述符監(jiān)控

監(jiān)控策略:?當(dāng) fd 數(shù)大于 1000 個(gè),或者 fd 連續(xù)遞增超過 50 個(gè),就觸發(fā) fd 收集,將 fd 對(duì)應(yīng)的文件路徑上報(bào)到后臺(tái)。


這里模擬一個(gè) bug,打開一個(gè)文件多次不關(guān)閉,通過 dumpFd,可以看到很多重復(fù)的文件名,進(jìn)而大致定位到問題。

當(dāng)懷疑某個(gè)文件有問題之后,我們還需要知道這個(gè)文件在哪創(chuàng)建,是誰創(chuàng)建的,這個(gè)就涉及到 IO 監(jiān)控。


| IO 監(jiān)控

①監(jiān)控內(nèi)容


監(jiān)控完整的 IO 操作,包括:

  • open:獲取文件名、fd、文件大小、堆棧、線程。

  • read/write:獲取文件類型、讀寫次數(shù)、總大小,使用 buffer 大小、讀寫總耗時(shí)。

  • close:打開文件總耗時(shí)、最大連續(xù)讀寫時(shí)間。


②Java 監(jiān)控方案


以 Android 6.0 源碼為例,F(xiàn)ileInputStream 的調(diào)用鏈如下:
java?:?FileInputStream?->?IoBridge.open?->?Libcore.os.open?->??
?BlockGuardOs.open?->?Posix.open


Libcore.java 是一個(gè)不錯(cuò)的 hook 點(diǎn)。
http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/libcore/io/Libcore.java


package?libcore.io;
public?final?class?Libcore?{
????private?Libcore()?{?}

????public?static?Os?os?=?new?BlockGuardOs(new?Posix());
}


我們可以通過反射獲取到這個(gè) Os 變量,它是一個(gè)接口類型,里面定義了 open、read、write、close 方法,具體實(shí)現(xiàn)在 BlockGuardOs 里面。
http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/libcore/io/BlockGuardOs.java


//?反射獲得靜態(tài)變量
Class?clibcore?=?Class.forName("libcore.io.Libcore");
Field?fos?=?clibcore.getDeclaredField("os");


通過動(dòng)態(tài)代理的方式,在它所有 IO 方法前后加入插樁代碼來統(tǒng)計(jì) IO 信息。
//?動(dòng)態(tài)代理對(duì)象
Proxy.newProxyInstance(cPosix.getClassLoader(),?getAllInterfaces(cPosix),?this);

beforeInvoke(method,?args,?throwable);
result?=?method.invoke(mPosixOs,?args);
afterInvoke(method,?args,?result);


此方案缺點(diǎn)如下:

  • 性能差,IO 調(diào)用頻繁,使用動(dòng)態(tài)代理和 Java 的字符串操作,導(dǎo)致性能較差,無法達(dá)到線上使用標(biāo)準(zhǔn)。

  • 無法監(jiān)控 Native 代碼,這個(gè)也是比較重要的。

  • 兼容性差:需要根據(jù) Android 版本做適配,特別是 Android P 的非公開 API 限制。


③Native 監(jiān)控方案


Native Hook 方案的核心從 libc.so 中的這幾個(gè)函數(shù)中選定 Hook 的目標(biāo)函數(shù)。
int?open(const?char?*pathname,?int?flags,?mode_t?mode);
ssize_t?read(int?fd,?void?*buf,?size_t?size);
ssize_t?write(int?fd,?const?void?*buf,?size_t?size);?write_cuk
int?close(int?fd)
;


我們需要選擇一些有調(diào)用上面幾個(gè)方法的 library,例如選擇 libjavacore.so、libopenjdkjvm.so、libopenjdkjvm.so,可以覆蓋到所有的 Java 層的 I/O 調(diào)用。


不同版本的 Android 系統(tǒng)實(shí)現(xiàn)有所不同,在 Android 7.0 之后,我們還需要替換下面這三個(gè)方法。
open64
__read_chk
__write_chk


native hook 框架目前使用比較廣泛的是愛奇藝的 xhook,以及它的改進(jìn)版,字節(jié)跳動(dòng)的 bhook。
https://github.com/iqiyi/xHook/blob/master/README.zh-CN.md

https://github.com/bytedance/bhook/blob/main/doc/overview.zh-CN.md


具體的 native IO 監(jiān)控代碼,可以參考 Matrix-IOCanary,內(nèi)部使用的是 xhook 框架。
https://github.com/Tencent/matrix/blob/master/matrix/matrix-android/matrix-io-canary/src/main/cpp/io_canary_jni.cc


關(guān)于 IO 涉及到的知識(shí)非常多,后面有時(shí)間可以單獨(dú)整理一篇文章。接下來看看最后一種 OOM 類型。


內(nèi)存不足


| 堆棧信息

這種是最常見的 OOM,Java 堆內(nèi)存不足,512M 都不夠玩,發(fā)生此問題的大部分設(shè)備都是 Android 7.0,高版本也有,不過相對(duì)較少。


| 重溫 JVM 內(nèi)存結(jié)構(gòu)

JVM 在運(yùn)行時(shí),將內(nèi)存劃分為以下 5 個(gè)部分:

  • 方法區(qū):存放靜態(tài)變量、常量、即時(shí)編譯代碼

  • 程序計(jì)數(shù)器:線程私有,記錄當(dāng)前執(zhí)行的代碼行數(shù),方便在 cpu 切換到其它線程再回來的時(shí)候能夠不迷路

  • Java 虛擬機(jī)棧:線程私有,一個(gè) Java 方法開始和結(jié)束,對(duì)應(yīng)一個(gè)棧幀的入棧和出棧,棧幀里面有局部變量表、操作數(shù)棧、返回地址、符號(hào)引用等信息

  • 本地方法棧:線程私有,跟 Java 虛擬機(jī)棧的區(qū)別在于 這個(gè)是針對(duì) native 方法

  • 堆:絕大部分對(duì)象創(chuàng)建都在堆分配內(nèi)存


內(nèi)存不足導(dǎo)致的 OOM,一般都是由于 Java 堆內(nèi)存不足,絕大部分對(duì)象都是在堆中分配內(nèi)存,除此之外,大數(shù)組、以及 Android3.0-7.0 的 Bitmap 像素?cái)?shù)據(jù),都是存放在堆中。


Java 堆內(nèi)存不足導(dǎo)致的 OOM 問題,線上難以復(fù)現(xiàn),往往比較難定位到問題,絕大部分設(shè)備都是 8.0 以下的,主要也是由于 Android 3.0-7.0 Bitmap 像素內(nèi)存是存放在堆中導(dǎo)致的。


基于這個(gè)結(jié)論,關(guān)于 Java 堆內(nèi)存不足導(dǎo)致的 OOM 問題,優(yōu)化方案主要是圖片加載優(yōu)化、內(nèi)存泄漏監(jiān)控。


| 圖片加載優(yōu)化

①常規(guī)的圖片優(yōu)化方式


常規(guī)的圖片加載優(yōu)化,可以參考文章《面試官:簡(jiǎn)歷上最好不要寫 Glide,不是問源碼那么簡(jiǎn)單》
https://juejin.cn/post/6844903986412126216


文章核心內(nèi)容大概如下:

  • 分析了主流圖片庫 Glide 和 Fresco 的優(yōu)缺點(diǎn),以及使用場(chǎng)景

  • 分析了設(shè)計(jì)一個(gè)圖片加載框架需要考慮的問題

  • 防止圖片占用內(nèi)存過多導(dǎo)致 OOM 的三個(gè)方式:軟引用、onLowMemory、Bitmap ?像素存儲(chǔ)位置


這篇文章現(xiàn)在來看還是有點(diǎn)意義的,其中的原理部分還沒過時(shí),不過技術(shù)更新迭代,常規(guī)的優(yōu)化方式已經(jīng)不太夠了,長(zhǎng)遠(yuǎn)考慮,可以做圖片自動(dòng)壓縮、大圖自動(dòng)檢測(cè)和告警。


②無侵入性自動(dòng)壓縮圖片


針對(duì)圖片資源,設(shè)計(jì)師往往會(huì)追求高清效果,忽略圖片大小,一般的做法是拿到圖后手動(dòng)壓縮一下,這種手動(dòng)的操作完全看個(gè)人修養(yǎng)。


無侵入性自動(dòng)壓縮圖片,主流的方案是利用 Gradle 的 Task 原理,在編譯過程中,mergeResourcesTask 這個(gè)任務(wù)是將所有 aar、module 的資源進(jìn)行合并,我們可以在 mergeResourcesTask 之后可以拿到所有資源文件。


具體做法:

  • 在 mergeResourcesTask 這個(gè)任務(wù)后面,增加一個(gè)圖片處理的 Task,拿到所有資源文件

  • 拿到所有資源文件后,判斷如果是圖片文件,則通過壓縮工具進(jìn)行壓縮,壓縮后如果圖片有變小,就將壓縮過的圖片替換掉原圖


可以簡(jiǎn)單理解如下:?
具體代碼可以參考 McImage 這個(gè)庫。
https://github.com/smallSohoSolo/McImage


| 大圖監(jiān)控

上文的自動(dòng)壓縮圖片只是針對(duì)本地資源,而對(duì)于網(wǎng)絡(luò)圖片,如果加載的時(shí)候沒有壓縮,那么內(nèi)存占用會(huì)比較大,這種情況就需要監(jiān)控了。


①?gòu)膱D片框架側(cè)監(jiān)控


很多 App 內(nèi)部可能使用了多個(gè)圖片庫,例如 Glide、Picasso、Fresco、ImageLoader、Coil,如果想監(jiān)控某個(gè)圖片框架, 那么我們需要熟讀源碼,找到 hook 點(diǎn)。


對(duì)于 Glide,可以通過 hook SingleRequest,它里面有個(gè) requestListeners,我們可以注冊(cè)一個(gè)自己的監(jiān)聽,圖片加載完做一個(gè)大圖檢測(cè)。


其他圖片框架,同理也是先找到 hook 點(diǎn),然后進(jìn)行類似的 hook 操作就可以,代碼可以參考:dokit-BigImgClassTransformer。
https://github.com/didi/DoraemonKit/blob/master/Android/buildSrc/src/main/kotlin/com/didichuxing/doraemonkit/plugin/classtransformer/BigImgClassTransformer.kt


②從 ImageView 側(cè)監(jiān)控


上面是從圖片加載框架側(cè)監(jiān)控大圖,假如項(xiàng)目中使用到的圖片加載框架太多,有些第三方 SDK 內(nèi)部可能自己搞了圖片加載。


這種情況下我們可以從 ImageView 控件側(cè)做監(jiān)控,監(jiān)聽 setImageDrawable 等方法,計(jì)算圖片大小如果大于控件本身大小,debug 包可以彈窗提示需要修改。


方案如下:

  • 自定義 ImageView,重寫 setImageDrawable、setImageBitmap、setImageResource、setBackground、setBackgroundResource 這幾個(gè)方法,在這些方法里面,檢測(cè) Drawable 大小

  • 編譯期,修改字節(jié)碼,將所有 ImageView 的創(chuàng)建都替換成自定義的 ImageView

  • 為了不影響主線程,可以使用 IdleHandler,在主線程空閑的時(shí)候再檢測(cè)


最終是希望當(dāng)檢測(cè)到大圖的時(shí)候,debug 環(huán)境能夠彈窗提示開發(fā)進(jìn)行修改,release 環(huán)境可以上報(bào)后臺(tái)。


debug 如下效果:

當(dāng)然這種方案有個(gè)缺點(diǎn):不能獲取到圖片 url。圖片優(yōu)化告一段落,接下來看看內(nèi)存泄漏。


| 內(nèi)存泄漏監(jiān)控演進(jìn)

①LeakCanary


關(guān)于內(nèi)存泄漏,大家可能都知道 LeakCanary:
https://github.com/square/leakcanary/


只要添加一個(gè)依賴:
debugImplementation?'com.squareup.leakcanary:leakcanary-android:2.8.1'


就能實(shí)現(xiàn)自動(dòng)檢測(cè)和分析內(nèi)存泄漏,并發(fā)出一個(gè)通知顯示內(nèi)存泄漏詳情信息。


LeakCanary 只能在 debug 環(huán)境使用,因?yàn)樗窃诋?dāng)前進(jìn)程 dump 內(nèi)存快照,Debug.dumpHprofData(path); 會(huì)凍結(jié)當(dāng)前進(jìn)程一段時(shí)間,整個(gè) APP 會(huì)卡死約 5~15s,低端機(jī)上可能要幾十秒的時(shí)間。


②ResourceCanary


微信對(duì) LeakCanary 做了一些改造,將檢測(cè)和分析分離,客戶端只負(fù)責(zé)檢測(cè)和 dump 內(nèi)存鏡像文件,文件裁剪后上報(bào)到服務(wù)端進(jìn)行分析。


具體可以看這篇文章 Matrix ResourceCanary -- Activity 泄漏及 Bitmap 冗余檢測(cè)。
https://mp.weixin.qq.com/s/XL55txToSCJXM8ErwrUGMw


③KOOM


不管是 LeakCanary 還是 ResourceCanary,他們都只能在線下使用,而線上內(nèi)存泄漏監(jiān)控方案,目前 KOOM 的方案比較完善,下面我將基于 KOOM 分析線上內(nèi)存泄漏監(jiān)控方案的核心流程。
https://github.com/KwaiAppTeam/KOOM/blob/master/README.zh-CN.md


| 線上內(nèi)存泄漏監(jiān)控方案

基于 KOOM 源碼分析:


①檢測(cè)時(shí)機(jī)


間隔 5s 檢測(cè)一次;觸發(fā)內(nèi)存鏡像采集的條件。


當(dāng)內(nèi)存使用率達(dá)到 80% 以上:
??????//->OOMMonitorConfig

??????private?val?DEFAULT_HEAP_THRESHOLD?by?lazy?{
????????val?maxMem?=?SizeUnit.BYTE.toMB(Runtime.getRuntime().maxMemory())
????????when?{
??????????maxMem?>=?512?-?10?->?0.8f
??????????maxMem?>=?256?-?10?->?0.85f
??????????else?->?0.9f
????????}
??????}


兩次檢測(cè)時(shí)間內(nèi)(例如 5s 內(nèi)),內(nèi)存使用率增加 5%。


②內(nèi)存鏡像采集


我們知道 LeakCanary 檢測(cè)內(nèi)存泄漏,不能用于線上,是因?yàn)樗?dump 內(nèi)存鏡像是在當(dāng)前進(jìn)程進(jìn)行操作,會(huì)凍結(jié) App 一段時(shí)間。


所以,作為線上 OOM 監(jiān)控,dump 內(nèi)存鏡像需要單獨(dú)開一個(gè)進(jìn)程。


整體的策略是:虛擬機(jī) supend→fork 虛擬機(jī)進(jìn)程→虛擬機(jī) resume→dump 內(nèi)存鏡像的策略。


dump 內(nèi)存鏡像的源碼如下:
??//->ForkJvmHeapDumper

??public?boolean?dump(String?path)?{
????...

????boolean?dumpRes?=?false;
????try?{
??????//1、通過fork函數(shù)創(chuàng)建子進(jìn)程,會(huì)返回兩次,通過pid判斷是父進(jìn)程還是子進(jìn)程
??????int?pid?=?suspendAndFork();

??????MonitorLog.i(TAG,?"suspendAndFork,pid="+pid);
??????if?(pid?==?0)?{
????????//2、子進(jìn)程返回,dump內(nèi)存操作,dump內(nèi)存完成,退出子進(jìn)程
????????Debug.dumpHprofData(path);
????????exitProcess();
??????}?else?if?(pid?>?0)?{
????????//?3、父進(jìn)程返回,恢復(fù)虛擬機(jī),將子進(jìn)程的pid傳過去,阻塞等待子進(jìn)程結(jié)束
????????dumpRes?=?resumeAndWait(pid);
????????MonitorLog.i(TAG,?"notify?from?pid?"?+?pid);
??????}
????}
????return?dumpRes;
??}


注釋 1:父進(jìn)程調(diào)用 native 方法掛起虛擬機(jī),并且創(chuàng)建子進(jìn)程;


注釋 2:子進(jìn)程創(chuàng)建成功,執(zhí)行 Debug.dumpHprofData,執(zhí)行完后退出子進(jìn)程;


注釋 3:得知子進(jìn)程創(chuàng)建成功后,父進(jìn)程恢復(fù)虛擬機(jī),解除凍結(jié),并且當(dāng)前線程等待子進(jìn)程結(jié)束。


注釋 1 源碼如下:
//?->native_bridge.cpp

pid_t?HprofDump::SuspendAndFork()?{
??//1、暫停VM,不同Android版本兼容
??if?(android_api_?????suspend_vm_fnc_();
??}
??...

??//2,fork子進(jìn)程,通過返回值可以判斷是主進(jìn)程還是子進(jìn)程
??pid_t?pid?=?fork();
??if?(pid?==?0)?{
????//?Set?timeout?for?child?process
????alarm(60);
????prctl(PR_SET_NAME,?"forked-dump-process");
??}
??return?pid;
}


注釋 3 源碼如下:
//->hprof_dump.cpp

bool?HprofDump::ResumeAndWait(pid_t?pid)?{
??//1、恢復(fù)虛擬機(jī),兼容不同Android版本
??if?(android_api_?????resume_vm_fnc_();
??}
??...
??int?status;
??for?(;;)?{
????//2、waitpid,等待子進(jìn)程結(jié)束
????if?(waitpid(pid,?&status,?0)?!=?-1?||?errno?!=?EINTR)?{
??????//進(jìn)程異常退出
??????if?(!WIFEXITED(status))?{
????????ALOGE("Child?process?%d?exited?with?status?%d,?terminated?by?signal?%d",
??????????????pid,?WEXITSTATUS(status),?WTERMSIG(status));
????????return?false;
??????}
??????return?true;
????}
????return?false;
??}
}


這里主要是利用 Linux 的 waitpid 函數(shù),主進(jìn)程可以等待子進(jìn)程 dump 結(jié)束,然后再返回執(zhí)行內(nèi)存鏡像文件分析操作。


③內(nèi)存鏡像分析


前面一步已經(jīng)通過 Debug.dumpHprofData(path) 拿到內(nèi)存鏡像文件,接下來就開啟一個(gè)后臺(tái)服務(wù)來處理。
?//->HeapAnalysisService

??override?fun?onHandleIntent(intent:?Intent?)?{
????...
????kotlin.runCatching?{
??????//1、通過shark將hprof文件轉(zhuǎn)換成HeapGraph對(duì)象
??????buildIndex(hprofFile)
????}
????...
????//2、將設(shè)備信息封裝成json
????buildJson(intent)

????kotlin.runCatching?{
??????//3、過濾泄漏對(duì)象,有幾個(gè)規(guī)制
??????filterLeakingObjects()
????}
????...
????kotlin.runCatching?{
??????//?4、gcRoot是否可達(dá),判斷內(nèi)存泄漏
??????findPathsToGcRoot()
????}
????...

????//5、泄漏信息填充到j(luò)son中,然后結(jié)束了
????fillJsonFile(jsonFile)


????//通知主進(jìn)程內(nèi)存泄漏分析成功
????resultReceiver?.send(AnalysisReceiver.RESULT_CODE_OK,?null)

????//這個(gè)服務(wù)是在單獨(dú)進(jìn)程,分析完就退出
????System.exit(0);
??}


內(nèi)存鏡像分析的流程如下:

  • 通過 shark 這個(gè)開源庫將 hprof 文件轉(zhuǎn)換成 HeapGraph 對(duì)象。

  • 收集設(shè)備信息,封裝成 json,現(xiàn)場(chǎng)信息很重要。

  • filterLeakingObjects:過濾出泄漏的對(duì)象,有一些規(guī)制,例如已經(jīng) destroyed 和 finished 的 activity、fragment manager 為空的 fragment、已經(jīng) destroyed 的 window 等。

  • findPathsToGcRoot:內(nèi)存泄漏的對(duì)象,查找其到 GcRoot 的路徑,通過這一步就可以揪出內(nèi)存泄漏的原因。

  • fillJsonFile:格式化輸出內(nèi)存泄漏信息。


| 小結(jié)

線上 Java 內(nèi)存泄漏監(jiān)控方案分析,這里小結(jié)一下:

  • 掛起當(dāng)前進(jìn)程,然后通過 fork 創(chuàng)建子進(jìn)程

  • fork 會(huì)返回兩次,一次是子進(jìn)程,一次是父進(jìn)程,通過返回的 pid 可以判斷是子進(jìn)程還是父進(jìn)程

  • 如果是父進(jìn)程返回,則通過 resumeAndWait 恢復(fù)進(jìn)程,然后當(dāng)前線程阻塞等待子進(jìn)程結(jié)束

  • 如果子進(jìn)程返回,通過 Debug.dumpHprofData(path) 讀取內(nèi)存鏡像信息,這個(gè)會(huì)比較耗時(shí),執(zhí)行結(jié)束就退出子進(jìn)程

  • 子進(jìn)程退出,父進(jìn)程的 resumeAndWait 就會(huì)返回,這時(shí)候就可以開啟一個(gè)服務(wù),后臺(tái)分析內(nèi)存泄漏情況,這塊跟 LeakCanary 的分析內(nèi)存泄漏原理基本差不多


不畫圖了,結(jié)合源碼看應(yīng)該可以理解。


對(duì)于 Java 內(nèi)存泄漏監(jiān)控,線下我們可以使用 LeakCanary、線上可以使用 KOOM,而對(duì)于 native 內(nèi)存泄漏應(yīng)該如何監(jiān)控呢?


方案如下:

  • 首先要了解 native 層。

  • 申請(qǐng)內(nèi)存的函數(shù):malloc、realloc、calloc、memalign、posix_memalign。

  • 釋放內(nèi)存的函數(shù):free。

  • hook 申請(qǐng)內(nèi)存和釋放內(nèi)存的函數(shù)。
分配內(nèi)存的時(shí)候,收集堆棧、內(nèi)存大小、地址、線程等信息,存放到 map 中,在釋放內(nèi)存的時(shí)候從 map 中移除。

那怎么判斷 native 內(nèi)存泄漏呢?

  • 周期性的使用?mark-and-sweep?分析整個(gè)進(jìn)程 Native Heap,獲取不可達(dá)的內(nèi)存塊信息「地址、大小

  • 獲取到不可達(dá)的內(nèi)存塊的地址后,可以從我們的 Map 中獲取其堆棧、內(nèi)存大小、地址、線程等信息


具體實(shí)現(xiàn)可以參考:koom-native-leak。
https://github.com/KwaiAppTeam/KOOM/blob/master/koom-native-leak/README.zh-CN.md


總結(jié)


本文從線上 OOM 問題入手,介紹了 OOM 原理, 以及 OOM 優(yōu)化方案和監(jiān)控方案,基本上都是大廠開源出來的比較成熟的方案:

  • 對(duì)于 pthread_create OOM 問題,介紹了無侵入性的 new Thread 優(yōu)化、無侵入性的線程池優(yōu)化、以及線程泄漏監(jiān)控

  • 對(duì)于文件描述符過多問題,介紹了原理以及文件描述符監(jiān)控方案、IO 監(jiān)控方案

  • 對(duì)于 Java 內(nèi)存不足導(dǎo)致的 OOM、介紹了無侵入性圖片自動(dòng)壓縮方案、兩種無侵入性的大圖監(jiān)控方案、Java 內(nèi)存泄漏監(jiān)控的線下方案和線上方案、以及 native 內(nèi)存泄漏監(jiān)控方案。


大廠對(duì)外開源的技術(shù)非常多,但不一定最優(yōu),我們?cè)趯W(xué)習(xí)過程中可以多加思考, 例如線程優(yōu)化,booster 對(duì)于 new Thread 的優(yōu)化只是設(shè)置了線程名,有助于分析問題,而經(jīng)過我的猜想和驗(yàn)證,通過字節(jié)碼插樁,將 new Thread 無侵入性替換成線程池調(diào)用,才是真正意義上的線程優(yōu)化。


有問題可以在 github 上找到我:
https://github.com/lanshifu


(完)

碼農(nóng)突圍資料鏈接

1、臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開放下載!
2、計(jì)算機(jī)基礎(chǔ)知識(shí)總結(jié)與操作系統(tǒng) PDF 下載
3、艾瑪,終于來了!《LeetCode Java版題解》.PDF
4、Github 10K+,《LeetCode刷題C/C++版答案》出爐.PDF

歡迎添加魚哥個(gè)人微信:smartfish2020,進(jìn)粉絲群或圍觀朋友圈

瀏覽 48
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)
評(píng)論
圖片
表情
推薦
點(diǎn)贊
評(píng)論
收藏
分享

手機(jī)掃一掃分享

分享
舉報(bào)

感谢您访问我们的网站,您可能还对以下资源感兴趣:

国产秋霞理论久久久电影-婷婷色九月综合激情丁香-欧美在线观看乱妇视频-精品国avA久久久久久久-国产乱码精品一区二区三区亚洲人-欧美熟妇一区二区三区蜜桃视频 日本Sm/调教/捆绑/紧缚| a片小视频| 黃色一级一片免费播放| 大香蕉伊人综合在线| 久久R5| www.51av| 逼特逼视频在线观看| 影音先锋成人在线资源| 亚洲Av秘无码一区二区| 91亚洲在线观看| 东京热免费视频| 打炮影院| 日韩亚洲视频| 真人BBwBBWBBw另类视频| 色婷婷中文在线| 狠狠爱av| 综合一区二区| 亚洲天天| 四虎成人精品无码永久在线的客服 | 黄色片a| 你懂得在线观看| 无码a级| 黄色一级电影网| 成人视频免费网站| 神马午夜秋霞不卡| 麻豆精品传媒2021md| 国产成人69免费看| 国产g蝌蚪| 黄片视频免费看| 99久久精品国产一区色| 亚洲中文字幕高清| 日韩在线观看免费| 无码一区二区在线观看| 亚韩av| 97人人爽人人爽人人爽| 91视频青青草| 狠狠爱一区| 麻豆偷拍| 亚洲中文字幕免费| 婷婷色AV| 可以在线观看的AV| 老太老熟女城中层露脸60| 国产精品伦子伦免费视频| 三级无码av| 成人无码区免费A片久久鸭| 免费在线观看毛片| 韩国免费一级a一片在线播放| 成人尤物网站| 久久久XXX| 91福利在线观看| 成人操B视频| 蜜桃精品在线观看| 成人亚洲A片V一区二区三区蜜月| 可以在线观看的AV| 国产成人自拍视频在线观看| 国产精品99精品| 高清无码高潮| 五月天婷婷在线播放视频免费观看| 丁香婷婷色五月激情综合三级三级片欧美日韩国 | 国产51视频| 九鼎联盟骗子| 浙江妇搡BBBB搡BBBB| 女人久久| 日韩欧美爱爱| www.91在线| 97在线视频免费观看| 一区在线免费观看| 亚洲少妇免费| 91无码人妻精品1国产四虎| 热久精品| 一卡二卡在线视频| 影音先锋91视频| 亚洲俺去了| 中文字幕韩日| a片在线电影网| 成人av黄色三级片在线观看| 成人黄色在线观看| 日本一区二区三区在线观看| 久久青| 四虎精品影院| 无码一二三区| 婷婷五月丁香网| 中文字幕在线观看免费高清电影| 激情五月天色| 欧美三级欧美成人高清| 五月婷亚洲精品AV天堂| 你懂得在线| 9l农村站街老熟女| 五月天丁香网| 自拍偷拍15p| 青娱乐精品在线| 九九韩剧网最新电视剧免费观看 | 亚洲特级毛片| 日韩亚洲欧美在线观看| 久久久人妻熟妇精品无码蜜桃| 伦理被部长侵犯HD中字| 人人干97| 免费av片| 精品久久一区二区| 91九色91蝌蚪91成人| 强奸乱伦五月天| 蜜桃Av噜噜一区| 日韩欧美中文字幕在线视频 | 狠狠噜噜| 国产xxxx视频| 新超碰97| 久久波多野结衣一区二区| 久久久久黄片| 亚洲欧美成人视频| 特一级黄色视频| 久久婷婷婬片A片AAA| 久久人人操人人| 成人一区视频| 婷婷五月天丁香网| 7799精品视频天天看| 国产成人免费看| 毛片资源| 无码日韩人妻精品久久蜜桃| 成年人免费电影| 熟睡侵犯の奶水授乳在线| 无码人妻精品一区二区三区蜜桃91| 国产精品无码ThePorn| 久久久久亚洲AV无码麻豆| 国产2页| 成人黄色视频免费| av无码中文| 成人做爰100部免费网站| 91禁樱桃在线| 无码高清一区| 欧美大香蕉在线| 午夜精品无码| 激情乱伦视频| 亚洲乱伦| 成人禁区| 欧美精产国品一| 日韩色妇| 青草视屏| 在线国产激情| 国内自拍偷拍视频| 想要xx| 久操成人| 91香蕉麻豆| 在线观看禁无码精品| 人人澡人人看| 亚洲三级在线| 婷婷开心色四房播播在线| 国产乱码在线| 内射学生妹J亅| 黄色A片网址| 中文字幕成| 无码乱码在线观看| 国产美女一级真毛片酒店| 国产99久久九九精品无码免费 | 福利所导航| 免费操逼| 亚洲无码aa| 91在线无码视频| 欧美一区二区三区在线播放| wwwwww黄| 一区二区三区四区在线看| 在线内射视频| 91欧美日韩综合| 巜人妻初尝按摩师BD中字| 东京热一区二区| 久久精品亚洲| 欧美人人插| 色色射| 国产精品视频色| 亚洲无码高清在线视频| 露脸老熟女91集合| 福利导航在线| 国产高清视频在线观看| 伊人大香蕉视频| 人人摸人人操人人摸| 在线v片| 一边做一边说国语对白| 国产三级黄| 激情操逼网| 国产精品久久精品| 亚洲综合中文| 国产精品天天AVJ精麻传媒| 国产精品欧美综合亚洲| 精品秘一区性综合三区| 色呦呦视频在线观看| 99久久久精品| 古装一级无遮挡A片| 三级网站免费| 雾水情缘电影港片| 国产精品成人69| 强行征服邻居人妻HD高清日本| 亚洲有码中文字幕| 人妻av中文字幕| 日本A片视频| 无码中文字幕在线观看| 中文字幕黑人无码| www.一级片| 一级a看片在线观看| aa免费视频| 黄色片A片| 91啪啪| 五月婷婷性爱| 亚洲av小说| 伊人综合大香蕉| 亚洲av| 透逼视频| 高清的日逼| 波多野吉衣av| 亚洲小视频在线播放| 欧美三级电影在线观看| 豆花视频免费| 中文字幕国产AV| 极品人妻疯狂3p超刺激| 久久久久久国产| 国产三级AV在线观看| 丁香五月成人网| 免费在线观看无码视频| 一本色道久久综合无码人妻| 91在线精品秘一区二区黄瓜| 国产精品国产三级片| 免费涩涩无遮挡18国产| 精品欧美乱码久久久久久| 日韩人妻无码一区二区| 91大神免费观看| 大香蕉伊人手机在线| 最近最经典中文MV字幕| 午夜久久久久久久久久久久91| 成人AAA| 亚洲在线高清视频| 中文字幕东京热| 西西人体44www大胆无码| 99精品无码| 久久久成人网| 无码人妻免费视频| 国外成人性视频免费| 伊人88| 天堂一区| 91插插网| 影音先锋成人无码| 天天综合国产| 中文字幕乱码免费综合久久| 视频一二三区| 亚洲黄色成人| 911亚洲精品| 天天爽夜夜爽精品成人免费| 91插插插插| 亚洲精品久久久久久久久久久 | 西西WWW888大胆无码| 久久永久视频| 色接久久| 日本欧美成人片AAAA| 丁香激情五月少妇| 人人操在线观看| 在线免费观看黄片| 亚洲免费观看在线观看| www.日本黄色视频| 日本精品视频在线观看| 午夜美女视频| 国产美女一区| 欧美中出| 美妇肥臀一区二区三区-久久99精品国 | 中文A片| 中字无码制服| 久久久福利视频| 亚洲成人一区二区在线观看| 校园春色亚洲色图| 九九色九九| 男女日皮视频| 日韩欧美成人网| 蜜臀精品一区二区三区| 欧美中文字幕在线观看| 日逼老女人| 国产黄色视频在线观看免费| 水蜜桃视频网| 伊人在线综合| 38D蜜桃臀| 午夜激情毛片| 精品国产AV无码一区二区三区| 白峰美羽人妻AND-499| 欧美操逼大全| 欧美熟妇另类久久久久久不卡| 操逼无码精品| 精品久久一区二区三区四区| 日韩精品无码电影| NP玩烂了公用爽灌满视频播放| 久久久成人免费视频| 亚洲精选中文字幕| AV性爱在线| 91精品婷婷国产综合久久| 久久久少妇| 国产婷婷色一区二区| 国产成人精品亚洲男人的天堂| 2017天天干天天射| 人人操人人爽人人妻| 91丨熟女丨对白| 麻豆操逼| 国产性受XXXXXYX性爽| 中文字幕视频免费| 日本欧美在线观看| 人妻大屁股-91Porn| 中文字幕免费在线| 91小电影| 污视频网站免费观看| 精品视频免费观看| 欧美日韩午夜福利视频| 翔田千里中文字幕无码| AV网站在线播放| 夜夜干天天操| 成人亚洲在线| 五月天婷婷乱伦| 深夜av| 欧美日韩中文字幕| 色婷婷视屏| 无码专区中文字幕| 日韩91视频| 久久久国产AV| 人人操人人干人人爽| 人人操碰人人| 国产无码小视频| 蜜桃Av噜噜一区二区三区| 北条麻妃九九九在线视频| 最新中文字幕免费MV第一季歌词| 国产精品免费av在线| 亚洲精品18禁| 日韩成人无码一区二区| 91视频网站入口| 欧美成人精品激情在线视频| 国产45页| 爱爱爱爱视频| 国产操b| 亚洲欧洲日本在线| 天天操综合| 成人黄网站免费观看| 人人操人人透| 人人操比| 久久一做爱| 国内精品内射| 大香蕉福利视频| 国产精品囯产三级囯产AV野外| 成人中文字幕在线| 精品中文一区二区三区| 青青草原网址| 黄色影视不卡| 俄罗斯白嫩BBwBBwBBw91| 国精产品一区二区三区| 自拍视频在线| 91久久精品日日躁夜夜躁欧美| 五月天最新网址| 中文字幕国产综合| 大鸡巴久久久| 免费看黄色大全| 欧美成人第一页| 国产精品秘精东影业| 一级黄色小视频| 免费中文视频| 国产黄片免费视频| www久久| 欧美日韩在线视频一区| 天天日综合| 免费黄色成人| 91成人18| 欧美乱伦内射| 99精品免费视频| 国产69精品久久| 无码免费高清| 五月婷婷综合激情| 五月天中文字幕| 日韩中文字幕国产| 超碰成人在线观看| 一级片AV| 久久麻豆| 91久久婷婷亚洲精品成人| 日韩成人综合| 91精品国产一区三一| 久久草在线观看| 国产第五页| 动漫3d啪啪成人h动漫| ww成人| 二区视频| 中文在线A∨在线| 亚洲日韩视频在线观看| 影音先锋男人网| 日韩三级小说| 国产视频h| 国产a区| 91大神在线观看入口| 欧美一区二区三区婷婷五月| 日本边摸边吻奶边做爰| 国产精品一麻了麻了| 色综合久久久无码中文字幕999| 精品人妻一二三区| 欧美日韩免费视频| 久久撸视频| 日韩无码免费看| 一级黄色影片| 欧美中出| 激情五月天在线视频| 亚洲热在线观看| 香蕉在线观看| 77777色婷婷| 91羞羞| 香蕉日逼| 日日干日日干| 91丨露脸丨熟女精品| 一本大道DVD中文字幕| 国产噜噜噜噜噜久久久久久久久| 婷婷色小说| 操逼视频在线免费看| 亚洲成人AV在线| 免费成人高清视频| 爱爱帝国综合社区| 免费在线观看黄视频| 欧美综合视频在线观看| 一区在线观看视频| 国产婬片lA片www777| 久久久久久av| 无码人妻在线播放| 一本到在线观看午夜剧场| 永久m3u8在线观看| 99精品在线| 国产操逼大全| www.插逼| 五月激情综合网| 国产一级婬乱A片| 操b网站| 河南熟妇搡BBBB搡BBBB| 全部视频午夜寂寞| 91操B| 日逼网站视频| 亚洲天堂天天| 欧美毛片在线观看| 国产乱码在线| 丰满熟妇人妻中文字幕| 91天天综合在线| 婷婷午夜福利| 91人妻无码| 久久综合色色| 欧美一级黄色电影| 一级a一级a免费观看免免黄‘/ | 182av| 免费视频一区二区| 亚洲乱码精品久久久久..| 亚洲色图欧美| 国产人国产视频成人免费观看…| 天天色操| 日本无码电影| 蜜桃av无码一区三区| 欧美色图视频网站| 1插菊花综合| 欧亚一区二区| 成人在线视频观看| 久久精品毛片| 成人免费一级视频| 91乱子伦国产乱子伦| 人人干天天干| 丁香花在线高清完整版视频| 人人妻人人| 国产成人无码Av片在线公司| 91蜜桃传媒| 色999| 中文字幕AV一区| 黄色片大香蕉| 日韩超碰在线| 粉嫩av一区二区白浆| 亚洲国产精品自在自线| 国产黄色精品视频| 日韩超清无码| 秋霞久久日| 最近中文字幕| 欧美女人日逼视频| 久草网大香蕉| 久久99老妇伦国产熟女| 操逼视频电影| 最新久欠一区二区免费看| 九九九在线| 日韩AV无码成人精品| 麻豆福利在线| 臭小子啊轻点灬太粗太长了的视频 | 欧美色图另类图片| 国产精品久久久一区二区三区| 老鸭窝在线观看视频| 日韩AV中文字幕在线播放| 天天干强奸视频在线综合| 国产精品国产精品国产专区不卡| 欧美色图另类图片| 肏逼在线观看| 久久大鸡吧| 欧美成人免费A级在线观看| 婷婷电影网| 手机AV在线| 亚洲午夜久久久久久久久红桃| 九九久久精品视频| 欧美一二三区黄色免费视屏| av资源在线播放| 亚洲天堂无码AV| 亚洲日韩一区二区三区四区| 一级免费视频| 91精品内射| 国产18水真多18精品| 无码中文字幕高清| 欧美一级婬片A片免费软件| 一区二区三区精品无码| 男人的天堂视频在线观看| 欧美日韩a| 91精品丝袜久久久久久久久粉嫩 | 无码成人精品| 日本少妇BBw| 99热精品在线| 一区二区三区成人电影| 波多野结衣大战黑人| 波多野结衣99| 久久精品一区二区三区四区五区| 国产女人18毛片水真多1| 一区二区经典| 熟女人妻一区二区三区免费看| 欧美老妇另类老屁XXX| A片免费在线播放| 日韩AV电影网站| 在线操B视频| 一级操逼| 操逼视频观看| 强伦轩一区二区三区在线观看| 日本免费福利视频| 操老女人逼| 中文字幕精品无码一区二区| 日韩色情在线| 精品乱子伦一区二区在线播放| 甘肃WBBBB搡wBBBB| 中文日韩欧美| 亚洲欧美日韩高清| 日韩激情一区二区| 无码免费中文字幕| 日本一级婬片A片AAA毛多多| 91亚洲高清| 国产av三级| 欧美午夜精品久久久久免费视| 亚洲精品无码视频| 免费V片| 国产亚洲欧美精品综合在线| 欧美日韩国产成人综合| 国产美女久久久| 伊人毛片| 精品欧美一区二区精品久久| 免费无码又爽又黄又刺激网站| 欧美老熟妇BBBBB搡BBB| 一品国精和二品国精的文化意义 | 伊人久久AV诱惑悠悠| 天天干天天操| 五月天婷婷综合| WWW久久久| 老熟女搡BBBB搡BBBB视频| 久久丁香五月天| 高清无码三级片| 亚洲色图一区二区三区| 在线观看黄A片免费网站| 91啦丨熟女露脸| 国产美女网站| 精品人妻一区二区三区鲁大师| 日老女人逼| 操逼福利视频| 亚洲欧洲精品视频| 婷婷五月天免费视频| 色色激情视频| 日韩电影一区| 色婷婷AV一区二区三区软件| 在线久草| 日韩性爱av| 欧美日韩视频一区二区| 欧洲三级片网站| 黄色成人视频在线观看| 蜜芽无码| 911精品国产一区二区在线| 婷婷五月色综合| 伊人激情五月| 日韩一区二区免费看| 亚洲免费网站| 97人人妻| 国产成人午夜福利在线| 中文字幕高清无码在线观看| 婷婷五月中文| 97人妻一区| 欧一美一伦一A片| 91视频美女模特| 操逼导航| AV在线无码| 亚洲国产婷婷香蕉A片| 国产多人搡BBBB槡BBBB| 婷婷精品免费| 亚洲综合中文字幕在线播放| 99热中文字幕在线观看| 亚洲欧美大香蕉视频网| 亚洲第一在线| 99re热| 亚洲免费三级| 国产精品不卡在线观看| 亚洲少妇免费| 欧洲性爱视频在线观看| 色婷婷中文在线| 淫色综合网| 国产又粗又长又硬黄色一级片| 日韩高清AV| 久久国产劲爆∧v内射| 久久精品中文| 日韩午夜电影| 69精品在线| 高清视频一区| 久操网在线视频| 亚洲色人妻| 人妻无码蜜桃视频| 国产精品天天AVJ精麻传媒| 欧美激情一区二区| 这里都是精品| 国产又爽又黄A片| 成人在线免费网站| 日韩成人精品在线| 日韩三级久久| 青青草逼视频| 亚洲AV无码久久久| 豆花视频无码| 一级片麻豆| 小黄片高清无码| 一级操逼视频免费观看| 日韩性爱视频| 爱搞视频在线观看| 婷婷色av| 搡BBBB搡BBB搡我瞎了| 国产高清一区二区| 亚洲精品国产成人综合久久久久久久久| 亚州中文字幕| 91久久精品无码一区| 99re国产视频| 亚洲AV成人一区二区三区不卡| 国产AV电影网| 色欲色欲一区二区三区| 国产成人电影一区二区| 尤物av在线| 天天综合字幕一区二区| 色婷婷视频一区二区| 奶头和荫蒂添的好舒服囗交漫画| 国产主播在线观看| 国产小视频在线观看| 欧美A∨| 久久av电影| 日韩电影无码| 一级免费黄片| av无码网站| a在线免费观看| 国产黄A片免费网站免费| 日韩高清一级| 久久精品一区二区三区蜜芽的特点 | 影音AV| 麻豆A∨在线| 一级片免费视频| 国产精品久久久久久亚洲影视| 99热青青草| 人妻无码高清| 国产91精品探花一区二区| 特级A级毛片| 波多野结衣无码一区二区| 国产精品成人免费久久黄AV片| 丁香婷婷激情| 69福利视频| 久久新视频| www.热久久| 一区二区三区久久久久〖网:.〗 | 一区毛片| 91乱子伦国产乱子伦!| 日日操视频| 久久久九九九| 婷婷五月中文字幕| 77777色婷婷| 一级无码毛片| 国产成人视频| 国产精品电影大全| 91成人片| 最近中文字幕高清2019中文字幕 | 韩日综合在线| 俺也去网av| 91无码人妻东京热精品一区| 国产乱子伦一区二区三| 狠狠ri| 五月丁香欧美综合| 大鸡巴久久久久久久| 日韩精品丰满无码一级A片∴| 日韩欧美在线中文字幕| 校园春色亚洲无码| 亚洲精品国偷拍自产在线观看蜜桃| 9热精品| 日韩成人视频在线| 国产一级婬片A片免费无成人黑豆 国产真实露脸乱子伦对白高清视频 | 国产乱人| 亚洲人妻在线观看| 欧美成人三区性价比| 免费高清无码在线观看| 国产av天堂| 操逼在线视频| 国产欧美一区二区精品性色超碰 | 成人视频黄片| 狠狠操天天操| yjizz视频网| 高清无码免费观看视频| 中文无码在线观看中文字幕av中文| 亚卅无码| 国产男女AV| 日韩一级无码特黄AAA片| av无码电影| 亚洲精品中文字幕无码| 欧美一卡二卡三卡| 日韩不卡在线| 4虎亚洲人成人网www| 黄色免费a级片一级片| 中文字幕线观看| 国产91麻豆视频| 欧一美一婬一伦一区?| 豆花视频免费| 手机AV在线播放| 日本少妇做爱| 亚洲中文字幕免费视频| 男女无码视频| 日韩无码播放| 91中文字幕在线观看| www.97yy| 污污污污污www网站免费民国| 四川少妇bbbb| 青青无码视频| 亚欧一区二区| 久久99网站| 五月婷婷色综合| 91免费视频观看| 操B视频网站| 日韩理论片| 天天干天天操综合| 国产青草视频在线观看| 啪啪啪AV| 国产美女做爱视频| 亚洲视频欧美视频| 亚洲精品人妻在线| 翔田千里av| 最近中文字幕免费mv第一季歌词強上 | 日韩中文字幕成人| 亚洲午夜激情| 日韩一级免费在线观看| 黄片无码免费| 亚洲电影中文字幕| 在线观看国产黄色| 精品一二三区| 欧美h| 好逼天天操| 日韩少妇无码| 国产在线A片| 九九热国产视频| sesese999| 一区二区三区免费观看| 欧美专区一区| A片黄色毛片| 四川少妇搡bbw搡bbbb| 久久99国产精品| 欧美精品区| 老熟女乱伦| 大香蕉综合| 中文字幕永久在线视频v1.0| a免费视频在线观看| 最近最经典中文MV字幕| 口爆AV| 日本少妇高潮| 爱搞搞就要搞| 美女做爱在线观看| 亚洲福利网| 国产精品你懂得| 国产无套免费网站69| 国产精品成人午夜福利| 狠狠狠狠狠| 国产AV日韩AⅤ亚洲AV中文| 欧美中文字| 丝袜毛片| 国产看片网站| 天天做天天爱天天高潮| 99美女精品视频| 无码人妻丰满熟妇区17水蜜桃| 欧美一级一区| 热久久9| 中日韩一级片| 精品一区国产探花| 日逼无码视频| 国产青草| 偷拍欧美日韩| 日本无码电影| 午夜无码鲁丝片午夜精品| 亚洲成人第一网站| 88在线无码精品秘入口九色| 波多野结衣av中文字幕| 91无码电影| 香蕉操逼小视频| 国产嫩草影院| 亚洲免费a| 九九色| 欧美成人精品AAA| 一区二区三区视屏| 日日夜夜综合| 亚洲人成小说| 黑人操白人| 操逼的视频| 国产精品操逼网站| 日韩五码在线| 农村少妇久久久久久久| 操逼视频大全| 少妇搡BBBB搡BBB搡打电话| 色五月网站| AV天天看| 久久精品视频在线观看| 阿拉伯三级片| 免费的黄色视频在线观看| 五月丁香激情综合| 亚洲精品中文字幕无码| 欧美三级长视频| 无套内射在线播放| 日本中文字幕网站| eeuss一区| 成人网肏逼视频| 国产suv精品一区二区6精华液| 久久肉| 99久久久久久久| 木下凛凛子AV888AV在线观看 | 青草在线视频| 亚洲欧美成人在线视频| 三级片网站大全| 中文字幕++中文字幕明步| 人人操日本| 日韩香蕉视频| 啪啪网站免费| 大香蕉伊人在线手机网| 欧美日韩北条麻妃视频在线观看 | 日韩欧美成人电影| 欧美footjob| 中文字幕一本道| 3344在线观看免费下载视频| 操逼综合| 亚洲无码午夜| 日韩黄色一级| 一级a一级a爱片免费视频| 天美果冻麻豆国产一区| 欧美一区二区三区四| 狠狠色色| 懂色aV| 亚洲国产av一区| 日韩精品丰满无码一级A片∴| 日批免费网站| 丁香婷婷色五月激情综合三级三级片欧美日韩国| 99re视频在线播放| 国产作爱| 欧美成人精品欧美一级乱黄| 精品交换一区二区三区无码| 亚欧洲精品在线视频| 大荫蒂HD大荫蒂视频| 国产免费精彩视频| 无码视频中文字幕| 人妻熟女一区二区| 久久久性爱| 国产激情网址| 东京热在线视频观看| 欧美国产精品| 中国熟睡妇BBwBBw| 日韩高清无码三级片| 欧美一卡二卡三卡| 国产91免费视频| 欧美日韩免费在线| 一级A片| 俺也色俺也干| 久草视频播放| 日韩成人无码视频| 东京热网站在线观看| 中国免费XXXX18| 麻豆国产视频| 国产精品久久免费视频| 日韩黄| 亚洲无码免费观看视频| 国产精品尤物| 十八毛片| 日韩人妻中文字幕| 亚洲激情国产| 久久久久久久久久8888| 欧美日韩视频在线| 免费无码国产| 中文字幕无码视频在线观看| 色婷婷视频| 日韩在线中文字幕| 成人福利网| 91丨牛牛丨国产人妻| 色五月婷婷激情| 无码精品人妻一区二区三刘亦菲| www91久久| 制服无码| 麻豆中文字幕| 成人动漫免费观看| 97色色超碰| 日本操逼视频| 国产精品成人在线视频| 黄网站免费在线观看| 波多野结衣av在线观看窜天猴| 丁香激情五月少妇| 亚洲理论电影|