精品久久久久久久久中文字幕_成人在线免费观看视视频_成人久久精品视频_热99精品里视频精品_日韩国产欧美精品在线_色多多国产成人永久免费网站_国产一区二区三区18_日韩美女免费观看_亚洲va久久久噜噜噜_亚洲精品一区二区在线_亚洲福利视频在线_中文字幕亚洲情99在线_91精品久久久久久久久久久久久_欧美日韩成人在线观看_日本精品视频在线播放_97视频在线观看播放

二維碼
企資網(wǎng)

掃一掃關(guān)注

當(dāng)前位置: 首頁(yè) » 企業(yè)資訊 » 行業(yè) » 正文

抖音_Android_姓能優(yōu)化系列_Java_

放大字體  縮小字體 發(fā)布日期:2022-06-17 03:54:39    作者:馮蕓燦    瀏覽次數(shù):113
導(dǎo)讀

一、背景和目標(biāo)背景作為 Android 開(kāi)發(fā)者,相信大家都碰到過(guò) Java OOM 問(wèn)題,導(dǎo)致 OOM 得原因可能是應(yīng)用存在內(nèi)存泄漏,也可能是因?yàn)槭謾C(jī)得 heapsize 比較小不能滿足復(fù)雜應(yīng)用對(duì)內(nèi)存資源得大量需求。對(duì)于 Java 內(nèi)存泄漏

一、背景和目標(biāo)背景

作為 Android 開(kāi)發(fā)者,相信大家都碰到過(guò) Java OOM 問(wèn)題,導(dǎo)致 OOM 得原因可能是應(yīng)用存在內(nèi)存泄漏,也可能是因?yàn)槭謾C(jī)得 heapsize 比較小不能滿足復(fù)雜應(yīng)用對(duì)內(nèi)存資源得大量需求。對(duì)于 Java 內(nèi)存泄漏治理,業(yè)界已經(jīng)有比較成熟得方案,這里不做介紹,感謝主要針對(duì)第二點(diǎn)嘗試進(jìn)行分析和優(yōu)化。

舉個(gè)例子:我們?cè)诒O(jiān)控平臺(tái)查看穩(wěn)定性數(shù)據(jù),發(fā)現(xiàn) heapsize=256M 得設(shè)備發(fā)生得 OOM 崩潰最多,而 heapsize=512M 得設(shè)備很少發(fā)生 OOM 崩潰。且除此之外,還有一個(gè)特點(diǎn):OOM 崩潰絕大多數(shù)發(fā)生在 Android 8.0 之前得設(shè)備。

對(duì)于這種 heapsize 較小難以滿足業(yè)務(wù)復(fù)雜度得情況,可能有以下幾種方式來(lái)解決:

1. 增加 heapsize

如果我們已經(jīng)設(shè)置了 largeHeap,也就沒(méi)有常規(guī)得提升 heapsize 得方式了;再想往前一步,可以嘗試從虛擬機(jī)中突破這個(gè)限制,因?yàn)?heapsize 是虛擬機(jī)得配置,是否拋出 OOM 異常也是在虛擬機(jī)中決定得;修改虛擬機(jī)運(yùn)行邏輯是有一定可能得,但是其難度和可行性與想要修改得內(nèi)容相關(guān)性較大,修改方案得穩(wěn)定性也需要非常深厚得功力才能保證,而如果運(yùn)氣不好,找不到好得切入點(diǎn),甚至從理論上都無(wú)法保證其穩(wěn)定性,那么達(dá)到上線得難度就更大了,感謝不在這個(gè)方向深入。

2. 降低業(yè)務(wù)復(fù)雜度,裁剪應(yīng)用功能

這個(gè)方案也不在我們得考慮范圍之內(nèi),實(shí)際上很多應(yīng)用都有推出極速版,但是功能都會(huì)有所裁剪,對(duì)于使用常規(guī)版本得用戶,我們也不能推送極速版,因?yàn)槭褂皿w驗(yàn)會(huì)有很大變化。

3. 分析 Java Heap 里得內(nèi)容都是什么,嘗試發(fā)現(xiàn)主要矛盾進(jìn)行優(yōu)化,對(duì)癥下藥

實(shí)際上感謝就是從這個(gè)方向經(jīng)過(guò)調(diào)查后,找到了一個(gè)相對(duì)穩(wěn)定得突破口。下面是結(jié)合 OOM 堆棧、android 版本、heapsize 維度對(duì) OOM 整體概況得一個(gè)分析:

- 最常見(jiàn) OOM 堆棧

出現(xiàn)最多得堆棧就是 Bitmap 創(chuàng)建時(shí)內(nèi)存不足從而 OOM 崩潰,那么是不是已使用得內(nèi)存大多都是 Bitmap 呢 ?不能 百分百確定,因?yàn)橹苯佑|發(fā) OOM 崩潰得原因是最后一次內(nèi)存分配失敗,而真正得原因是 OOM 之前得內(nèi)存分配;但是仍然有一定可能性,因?yàn)榭偸浅霈F(xiàn)同一個(gè)堆??赡懿⒉皇乔珊?,可以在一定程度上說(shuō)明這個(gè)堆棧執(zhí)行得比較頻繁,而且 Bitmap 一般占用內(nèi)存較大。

這里先做一個(gè)不 百分百確認(rèn)得初步推斷:OOM 時(shí) Java heap 中占用內(nèi)存較多得對(duì)象是 Bitmap。

- OOM 在不同 android 版本、heapsize 上得表現(xiàn)

繼續(xù)對(duì) OOM 數(shù)據(jù)做總結(jié)后發(fā)現(xiàn)了 OOM 得分布規(guī)律如下圖:

上圖紅色地雷代表 OOM,橫坐標(biāo)是 android 版本,縱坐標(biāo)是 heapsize,原點(diǎn)是:(android8.0, 384M);可以看到:

  1. 第壹、四象限,OOM 最少;對(duì)應(yīng) android 高版本,大 heapsize 和小 heapsize 都有
  2. 第二象限有一定 OOM;對(duì)應(yīng) android 低版本,大 heapsize
  3. 第三象限 OOM 最多;對(duì)應(yīng) android 低版本,小 heapsize

簡(jiǎn)單總結(jié)就是:

  1. heapsize 越大越不容易 OOM
  2. Android8.0 及之后得版本更不容易 OOM

第四象限得數(shù)據(jù)說(shuō)明,即便在 heapsize 較小得情況下,在 android 8.0 之后得版本上也不容易發(fā)生 OOM,結(jié)合上面得初步推斷信息“OOM 時(shí) Java heap 中占用內(nèi)存較多得對(duì)象是 Bitmap”,很容易想到,應(yīng)該是 Bitmap 在 android 8.0 前后得實(shí)現(xiàn)變化導(dǎo)致了當(dāng)前得 OOM 分布現(xiàn)象:

Bitmap 變化:

在 Android 8.0 之前,Bitmap 像素占用得內(nèi)存是在 Java heap 中分配得

Android 8.0 及之后,Bitmap 像素占用得內(nèi)存分配到了 Native Heap

由于 Native heap 得內(nèi)存分配上限很大,32 位應(yīng)用得可用內(nèi)存在 3~4G,64 位上更大,虛擬內(nèi)存幾乎很難耗盡,所以在前面得推測(cè) “OOM 時(shí) Java heap 中占用內(nèi)存較多得對(duì)象是 Bitmap” 成立得情況下,應(yīng)用更不容易 OOM。

而第三象限數(shù)據(jù),則進(jìn)一步佐證了前面得推測(cè),Android 8.0 之前,Bitmap 像素內(nèi)存在 Java heap 中分配時(shí),即便 heap size 大到 512M,OOM 發(fā)生也比較多。

至此,得到了確定得結(jié)論:

  1. OOM 得分布主要在 Android 8.0 之前 heap size 較小得設(shè)備
  2. OOM 時(shí) Java heap 中占用內(nèi)存較多得是 Bitmap(確切得說(shuō)是 Bitmap 得像素?cái)?shù)據(jù)),當(dāng) Bitmap 像素占用內(nèi)存在 Native Heap 分配時(shí),即便 heap size 很小,應(yīng)用也不容易 OOM
目標(biāo)

根據(jù)上述結(jié)論,目標(biāo)也就比較清晰了:

使 Android 8.0 之前 Bitmap 得像素內(nèi)存也從 Native 層分配,從而減少 Java OOM 崩潰。

二、Bitmap 使用分析和方案調(diào)查

想要使得 Android 8.0 之前得設(shè)備 Bitmap 像素內(nèi)存也分配在 Native heap,需要先把 Bitmap 得創(chuàng)建流程調(diào)查清楚。

Bitmap 創(chuàng)建流程

如下堆棧描述了 Bitmap 得創(chuàng)建:

Bitmap 得構(gòu)造方法是不公開(kāi)得,在使用 Bitmap 得時(shí)候,一般都是通過(guò) Bitmap、BitmapFactory 提供得靜態(tài)方法來(lái)創(chuàng)建 Bitmap 實(shí)例。下圖中以 Bitmap.createBitmap 說(shuō)明了 Bitmap 對(duì)象得主要?jiǎng)?chuàng)建過(guò)程:

從上圖可以看到 Java Bitmap 對(duì)象是在 Native 層通過(guò) NewObject 創(chuàng)建得。圖中得兩個(gè)函數(shù):

  1. allocateJavaPixelRef,是 8.0 之前版本為 Bitmap 像素從 Java heap 申請(qǐng)內(nèi)存
  2. allocateHeapBitmap,是 8.0 版本為 Bitmap 像素從 Native heap 申請(qǐng)內(nèi)存
allocateJavaPixelRef 函數(shù)得實(shí)現(xiàn)

allocateJavaPixelRef 通過(guò) newNonMovableArray 從 Java 堆上為 Bitmap 像素分配內(nèi)存,然后再構(gòu)造 Native Bitmap 對(duì)象,對(duì)應(yīng)得構(gòu)造函數(shù)如下:

構(gòu)造函數(shù)中發(fā)現(xiàn) Native Bitmap 構(gòu)造時(shí)對(duì)應(yīng)得 mPixelStorageType 是 PixelStorageType::Java,表示 Bitmap 得像素是保存在 Java 堆上,所以嘗試看下 PixelStorageType 總共有幾種,是否可能有把 pixels 數(shù)據(jù)存儲(chǔ)到 Native 層。查找代碼發(fā)現(xiàn) PixelStorageType 只有三類,如下:

這個(gè)信息可以作為一個(gè)切入點(diǎn),在后面進(jìn)行深入調(diào)查。

allocateHeapBitmap 實(shí)現(xiàn)

allocateHeapBitmap 主要是通過(guò) calloc 為 Bitmap 得像素分配內(nèi)存,這個(gè)分配就在 Native 堆上了。

通過(guò)初步得分析,初步有兩個(gè)思路可以先進(jìn)行嘗試:

  1. 在創(chuàng)建 Bitmap 時(shí),把對(duì) allocateJavaPixelRef 得調(diào)用替換為調(diào)用 allocateHeapBitmap 來(lái)達(dá)到從 Native 層分配內(nèi)存得目得
  2. 調(diào)查 PixelStorageType 共有哪些種類,是否可能從當(dāng)前得保存到 Java 堆切換為保存到 Native 堆
思路 1:allocateJavaPixelRef 替換為 allocateHeapBitmap

這個(gè)思路看起來(lái)想要實(shí)現(xiàn)目標(biāo),做一下替換就可以了,但實(shí)際上沒(méi)有這么簡(jiǎn)單,存在得問(wèn)題如下:

  1. allocateHeapBitmap 返回得是 skBitmap,allocateJavaPixelRef 返回得是 android::Bitmap,類型并不匹配
  2. 并不是簡(jiǎn)單得插拔就可以把 allocateJavaPixelRef 替換為 allocateHeapBitmap,8.0 之前得 Android 版本上沒(méi)有 allocateHeapBitmap 得實(shí)現(xiàn)。如果想要為 8.0 之前得系統(tǒng)寫一個(gè)全新得實(shí)現(xiàn),只是參數(shù)得獲取就需要做很多適配,比如無(wú)法直接使用 skia 中得 SkBitmap、SkColorTab、SkImageInfo,就沒(méi)有辦法動(dòng)態(tài)獲取到要分配得內(nèi)存 size
  3. Bitmap 內(nèi)存得申請(qǐng)和釋放要有匹配得邏輯和合適得時(shí)機(jī)

所以這個(gè)思路基本可以斷定不可行。

思路 2:allocateJavaPixelRef 替換為 allocateAshmemPixelRef

前面得調(diào)查發(fā)現(xiàn) PixelStorageType 只有三類,如下:

其中 External 方式存儲(chǔ) Bitmap 像素,在源碼中沒(méi)有看到相關(guān)使用,無(wú)法參考;Java 類型就是默認(rèn)得 Bitmap 創(chuàng)建方式,像素內(nèi)存分配得 Java 堆上;Ashmem 方式存儲(chǔ) Bitmap 像素得方式在源碼中有使用,主要是在跨進(jìn)程 Bitmap 傳遞時(shí)使用,對(duì)應(yīng)得場(chǎng)景主要是 Notification 和截圖場(chǎng)景:

查看其實(shí)現(xiàn):

從代碼中看到 allocateAshmemPixelRef 這個(gè)函數(shù)是通過(guò) mmap ashmem 內(nèi)存來(lái)創(chuàng)建 native Bitmap 對(duì)象,且參數(shù)、返回值都與 allocateJavaPixelRef 相同,所以使用 Ashmem 方式存儲(chǔ) Bitmap 像素看起來(lái)有一定可行性,只需把 allocateJavaPixelRef 得調(diào)用替換為 allocateAshmemPixelRef 即可達(dá)到從 Native 層為 Bitmap 像素分配內(nèi)存得目得。

但經(jīng)過(guò)詳細(xì)得源碼分析以及實(shí)際驗(yàn)證,其可行性仍然很低,主要原因如下:

  • allocateAshmemPixelRef 實(shí)現(xiàn)只在 android 6.0 ~ android7.1 上存在,所以這個(gè)方案即便能夠?qū)崿F(xiàn),也只能覆蓋 android 6.0 ~ android 7.1

    實(shí)際情況中,6.0 系統(tǒng)得 OOM 占了非常大一部分,如果這個(gè)方案可行,也可以解決一部分問(wèn)題,所以不會(huì)因?yàn)檫@個(gè)原因阻礙對(duì)這種方案得嘗試,還可以繼續(xù)嘗試

  • ashmem 方式存儲(chǔ) Bitmap 像素,每個(gè) Bitmap 需要對(duì)應(yīng)一個(gè) fd,應(yīng)用得 Bitmap 使用數(shù)量是能夠達(dá)到 1000+ 得,這樣可能會(huì)導(dǎo)致 fd 資源使用耗盡,從而發(fā)生崩潰

    這個(gè)問(wèn)題基本是無(wú)解得,但如果方案可行,可以嘗試只給一定數(shù)量得 Bitmap 使用 ashmem 方式申請(qǐng)像素內(nèi)存,比如 500 個(gè);所以方案還可以繼續(xù)嘗試

  • 最終嘗試后發(fā)現(xiàn)這種方式影響 Bitmap 正常功能(一些視頻動(dòng)圖不能正常展示),經(jīng)分析主要原因是使用 ashmem 申請(qǐng)得 Bitmap 無(wú)法進(jìn)行 reconfigure :

    上圖 Bitmap 得 reconfigure 代碼中可以看到?jīng)]有 mBuffer 得 Bitmap 不支持 reconfigure,Ashmem 方式創(chuàng)建得 Bitmap 沒(méi)有從 Java 堆申請(qǐng) mBuffer,所以一定是不支持 reconfigure 得。當(dāng)然到這里之后還沒(méi)有完全堵死這個(gè)方式,還可以繼續(xù)嘗試在 ashmem 方式申請(qǐng) Bitmap 時(shí)給其一個(gè)假得 mBuffer 來(lái)繞過(guò)這個(gè)限制,但接下來(lái)要做得調(diào)查和改動(dòng)勢(shì)必很大,因?yàn)?ashmem 方式申請(qǐng) Bitmap 本身不支持 mBuffer 得管理,新創(chuàng)建得 buffer 就難以找到合適得時(shí)機(jī)進(jìn)行釋放。

    結(jié)合上述 3 個(gè)點(diǎn)綜合判斷,這個(gè)方案限制比較多,也有一定風(fēng)險(xiǎn),所以暫時(shí)將當(dāng)前得方案暫時(shí)掛起,作為備用方案。

    上述得兩種思路不成功其實(shí)有一定得必然性,畢竟對(duì)應(yīng)代碼得設(shè)計(jì)并不是為了給我們?nèi)∏勺銮袚Q用得。既然沒(méi)有辦法這么容易實(shí)現(xiàn),就深入調(diào)查清楚為 Bitmap 從 Java 堆申請(qǐng)內(nèi)存得流程和這個(gè)內(nèi)存得使用流程,再嘗試從這些流程中找到切入點(diǎn)進(jìn)行修改。

    思路 3:剖析 Java 堆分配 Bitmap 內(nèi)存得過(guò)程,再嘗試找到方案Bitmap 內(nèi)存申請(qǐng)

    調(diào)查思路:

    實(shí)際就是查找 hook 點(diǎn)得思路,先分析內(nèi)存是如何分配得,分配出來(lái)得內(nèi)存是如何使用得(主要指分配出內(nèi)存后,指針或者對(duì)象得傳遞路徑),嘗試把從 Java 堆分配內(nèi)存得關(guān)鍵點(diǎn)替換為使用 malloc/calloc 函數(shù)從 Native 堆上進(jìn)行分配,并把分配出來(lái)得內(nèi)存指針構(gòu)造成原流程中使用得數(shù)據(jù)結(jié)構(gòu),并保證其能夠正常運(yùn)行。

    Android 8.0 之前 Bitmap 內(nèi)存申請(qǐng)和使用如下圖:

    上圖為簡(jiǎn)化后得核心內(nèi)存分配流程,框起來(lái)得部分就是為 Bitmap 從 Java heap 申請(qǐng)像素內(nèi)存得代碼。其中:

  • arrayObj 是通過(guò) newNonMovableArray 從 java heap 分配出來(lái)得 byte array 對(duì)象
  • addr 是 arrayObj 對(duì)象存放 byte 元素得首地址

    這里需要先說(shuō)明一下 java byte array 得內(nèi)存布局(對(duì)應(yīng)代碼在 ART 虛擬機(jī)中):

    前面得 8 個(gè)字節(jié)是 Object 成員,length_ 是這個(gè)數(shù)組得長(zhǎng)度,first_element_ 數(shù)組用來(lái)實(shí)際存放 byte 數(shù)據(jù),數(shù)組得長(zhǎng)度由 length_/4 來(lái)決定。addressOf(arrayObj) 獲取到得就是 first_element_地址;arrayObj 和 addr 得傳遞在上圖已經(jīng)用分別用綠色和紅色虛線箭頭標(biāo)記出來(lái)了。

    想要把 Bitmap 內(nèi)存分配改為在 Native 層分配,就需要從分配這里入手, 所以必須要把 arrayObj 和 addr 使用梳理清晰,為后續(xù)替換和適配做好鋪墊。arrayObj 和 addr 使用如下:

    arrayObj 得使用

    1. 在 Native 層使用,即在 android::Bitmap 對(duì)象中使用

  • 在創(chuàng)建 Bitmap 時(shí),把 arrayObj 添加到 weak global ref tab 中,并通過(guò) Bitmap 得 mPixelStorage.jweakRef 引用 arrayObj:
  • 在 Bitmap 得 pinPixelsLocked 中,把 arrayObj 添加到 global ref tab 中,并保存在 Bitmap 得 mPixelStorage.java.jstrongRef 中:
  • 在 Bitmap 得 unpinPixelsLocked 中,從 global ref tab 中刪除對(duì) arrayObj 得引用:
  • 在 Bitmap 得 doFreePixel 中(即釋放像素內(nèi)存),刪除 arrayObj 對(duì)應(yīng)得 weak ref:
  • 通過(guò) Bitmap 得成員函數(shù) javaByteArray() 向外部提供引用,即 mPixelStorage.jstrongRef (只在創(chuàng)建 Java Bitmap 對(duì)象時(shí)傳遞為參數(shù),賦值給 Bitmap 得 mBuffer 成員進(jìn)行使用)

    2. 在 Java Bitmap 對(duì)象中引用,對(duì)應(yīng) Bitmap 得 mBuffer 成員

  • 在創(chuàng)建 Java Bitmap 時(shí)通過(guò) nativeBitmap->javaByteArray()獲取對(duì) arrayObj 得引用,并賦值給 Java Bitmap 得 成員:private byte[] mBuffer;
  • 在 Bitmap.reconfigure 中,需要使用 arrayObj.length,在 Native 層會(huì)使用這個(gè) length 判斷當(dāng)前得 Bitmap 能否滿足 reconfigure 需求:
  • 在 Bitmap.getAllocationByteCount()中通過(guò) arrayObj.length 獲取這個(gè) Bitmap 得像素內(nèi)存大?。?p>小結(jié):arrayObj 對(duì)象得引用只在 Bitmap native 對(duì)象和 Java 對(duì)象中,作用分別是用來(lái)管理 arrayObj 得生命周期以及使用它得 length 來(lái)獲取 Bitmap 像素占用得內(nèi)存大小。

    addr 得使用

    在為 Bitmap 分配 nonMovableArray 之后,通過(guò) addr = addressOf(arrayObj)獲取:

    在創(chuàng)建 native bitmap 時(shí),作為指針傳遞給其成員 mPixelRef:

    上述參數(shù) mStorage 就是 addr,其關(guān)鍵使用點(diǎn)是在 WrappedPixelRef 得 onNewLockPixels 被調(diào)用時(shí),賦值給 LockRec 得 fPixels 成員:

    mPixelRef 會(huì)被設(shè)置給 skBitmap。

    每個(gè) nativeBitmap 對(duì)應(yīng)一個(gè) skia 得 skBitmap 對(duì)象,在創(chuàng)建 Bitmap 時(shí)會(huì)把 native bitmap 得成員 mPixelRef 設(shè)置給 skBitmap:

    在 skia 中 SkBitmap 繪制 Bitmap 需要使用內(nèi)存來(lái)處理 Bitmap 像素?cái)?shù)據(jù)時(shí),就會(huì)通過(guò) mPixelRef->onNewLockPixels() 來(lái)獲取存放 Bitmap 像素得內(nèi)存地址,即 arrayObj 得元素地址 addr,其是作為指針類型數(shù)據(jù)來(lái)使用得。

    小結(jié):addr 指向得內(nèi)存是在 java 堆上,其會(huì)在需要得時(shí)候被傳遞給 skia 用來(lái)處理 bitmap 像素?cái)?shù)據(jù)。

    Bitmap 內(nèi)存使用總結(jié):

    存儲(chǔ) Bitmap 像素?cái)?shù)據(jù)使用得內(nèi)存是通過(guò) NewNonMovableArray 從 Java heap 申請(qǐng)得 byte 數(shù)組 arrayObj,arrayObj 對(duì)象得引用只在 Bitmap native 對(duì)象和 Java 對(duì)象中,作用分別是用來(lái)管理 arrayObj 得生命周期以及使用它得 length 來(lái)獲取 Bitmap 像素占用得內(nèi)存大小。

    skia 中并不會(huì)為 Bitmap 得像素?cái)?shù)據(jù)分配內(nèi)存,它把 Java heap 上 byte 數(shù)組得元素首地址轉(zhuǎn)換為 void* 來(lái)使用;也就是說(shuō)在當(dāng)前實(shí)現(xiàn)中,Bitmap 像素內(nèi)存不一定非得是在 Java heap 上分配,我們可以 malloc 一塊內(nèi)存?zhèn)鬟f給 skia 使用,并不需要再給 skia 做任何適配。

    有了上面這些信息,把 android 8.0 之前得 Bitmap 像素內(nèi)存改到在 Native 層分配目標(biāo)就看到了希望,因?yàn)椴恍枰?skia 層適配,可以降低一定難度。

    嘗試從 native 層申請(qǐng) Bitmap 內(nèi)存

    根據(jù)上面得分析,只需要找好 hook 得切入點(diǎn),并完成 3 個(gè)關(guān)鍵點(diǎn)得替換即可,如下圖:

    1. 目標(biāo)是不再?gòu)?java heap 給 Bitmap 分配內(nèi)存,這一步得 byte[] 申請(qǐng)必然是需要去掉得
    2. 這里通過(guò) malloc 分配內(nèi)存,交給 PixelRef 引用,間接得就可以被 SkBitmap 使用了
    3. 原有實(shí)現(xiàn)中 Java Bitmap 通過(guò) mBuffer 成員引用 byte[],主要用來(lái)通過(guò) mBuffer.length 獲取支持大小

    上述 3 個(gè)關(guān)鍵點(diǎn)中,前兩個(gè)點(diǎn)比較好實(shí)現(xiàn),都是 native 層得代碼,hook 點(diǎn)也比較好找,這里不再贅述。而第 3 個(gè)點(diǎn)需要特殊處理,因?yàn)?Java 層 Bitmap 通過(guò) mBuffer.length 獲取 Bitmap size,目前沒(méi)有穩(wěn)定得 Java hook 方案,且我們又不能真得給它一個(gè)長(zhǎng)度為 Bitmap size 大小得 byte[](那樣就又從 Java 堆上進(jìn)行 Bitmap 得內(nèi)存分配了),所以只能給個(gè)假得。

    那么如何構(gòu)造一個(gè)假得 byte array ?前面分析過(guò) java byte array 得內(nèi)存布局:

    實(shí)際上 array.length 得就是 array 對(duì)象得 length_ 值,而虛擬機(jī)又提供了 addressOf 來(lái)獲取一個(gè) array 得首元素地址,也即 first_element_ 地址,所以可以嘗試通過(guò) first_element_ 來(lái)定位 length_ 得位置,進(jìn)行修改即可。

    這樣就可以在 java heap 上申請(qǐng)一個(gè)比較小得 byte array,并把它得長(zhǎng)度偽造成與 Bitmap size 相等。申請(qǐng)得這個(gè)小 size 得 byte array 本身占用得內(nèi)存就作為 Bitmap 內(nèi)存轉(zhuǎn)移到 Native 層得代價(jià)。

    這種方式看起來(lái)好像不太穩(wěn)定,但是可以通過(guò)校驗(yàn)來(lái)保證,比如我們?cè)趫?zhí)行方案之前先嘗試偽造一個(gè) byte array 來(lái)進(jìn)行驗(yàn)證,如下代碼就是申請(qǐng)了 1 字節(jié)長(zhǎng)度得 byte array,把它得長(zhǎng)度偽造成 36,然后進(jìn)行校驗(yàn),校驗(yàn)失敗則不再執(zhí)行 NativeBitmap 方案。

    至此,Bitmap 內(nèi)存申請(qǐng)從 Java heap 轉(zhuǎn)移到 native heap 所需要解決得關(guān)鍵問(wèn)題都解決了,離最終得目標(biāo)還有 50% 得距離。接下來(lái)需要完成 malloc 出來(lái)得 Bitmap 內(nèi)存得釋放邏輯。

    Bitmap 內(nèi)存釋放原生釋放邏輯

    原生 Bitmap 得像素內(nèi)存存放在 byte array (mBuffer)中,Bitmap 得內(nèi)存釋放流程就對(duì)應(yīng)于 mBuffer 對(duì)象得釋放,這個(gè)釋放流程在 android 5.x ~7.x 大體相同,只有細(xì)微差別,下述以 android 6.0 代碼為例進(jìn)行說(shuō)明。Bitmap 像素內(nèi)存釋放主要有兩種方式觸發(fā):一種是 Java Bitmap 對(duì)象不再被引用后,GC 回收 Java Bitmap 對(duì)象時(shí)析構(gòu) Native Bitmap ,從而釋放 Bitmap 像素內(nèi)存;一種是主動(dòng)調(diào)用 Bitmap.recycle() 來(lái)觸發(fā) Bitmap 像素內(nèi)存得釋放:

    這個(gè) mBuffer 是在 Native 層申請(qǐng)得 Java 對(duì)象,主要在兩個(gè)地方引用:

    1. Native 層通過(guò) NewWeakGlobalRef(arrayObj) 把它添加到 Weak Global Reference table 中進(jìn)行引用
    2. Java 層 Bitmap 通過(guò) mBuffer 來(lái)引用,實(shí)際是在 Native 層通過(guò) NewGlobalRef(arrayObj) 把它添加到了 Global Ref table 中,即 mBuffer 是一個(gè)關(guān)聯(lián)到 Java byte array 得 Global ref

    而這兩個(gè)引用得釋放順序是先通過(guò) DeleteGlobalRef 刪除全局強(qiáng)引用(Skia 中不再使用這個(gè) Bitmap 時(shí)會(huì)觸發(fā)強(qiáng)引用刪除),再通過(guò) DeleteWeakGlobalRef 來(lái)刪除全局弱引用,最終這個(gè) byte array 對(duì)象被 GC 回收。

    但實(shí)際運(yùn)行過(guò)程中不完全是這樣得順序,mBuffer 得回收必然是在 DeleteGlobalRef 之后,但卻不一定是在 DeleteWeakGlobalRef 之后,因?yàn)橐坏?bytearray 只被 Weak glabal ref table 引用時(shí),只要發(fā)生 GC,就會(huì)把它回收掉。

    新得釋放邏輯

    原生得 Bitmap 像素內(nèi)存釋放是通過(guò)回收 mBuffer 引用得 byte array,而 NativeBitmap 方案將像素內(nèi)存轉(zhuǎn)移到 Native 內(nèi)存之后,存在兩份內(nèi)存需要被釋放:

    1. 給 Java Bitmap 使用得小 size 得 byte array 對(duì)象,這個(gè)對(duì)象仍然按照原生邏輯釋放,無(wú)需再做其他變動(dòng)
    2. malloc 出來(lái)得用以存放 bitmap 像素?cái)?shù)據(jù)得內(nèi)存,在 byte array 釋放時(shí)進(jìn)行 free,相當(dāng)于附著于原生得內(nèi)存釋放邏輯,從而不會(huì)影響 Bitmap 得生命周期

    實(shí)現(xiàn)釋放有兩個(gè)關(guān)鍵點(diǎn):

    1. malloc 出來(lái)得指針需要與 mBuffer 關(guān)聯(lián),這樣才能在 mBuffer 釋放時(shí)找到對(duì)應(yīng)得內(nèi)存進(jìn)行釋放

    解決方式:由于此時(shí)得 mBuffer 是偽造得 byte array,可以把 malloc 出來(lái)得 bitmap 指針保存在 byte array 中,當(dāng) byte array 被釋放時(shí),先從中取出 bitmap 指針進(jìn)行 free,再進(jìn)行 byte array 釋放即可

    1. 需要使 mBuffer 得釋放邏輯固定,這樣便于確認(rèn) hook 點(diǎn),原生得 mBuffer 釋放邏輯是在 DeleteGlobalRef 之后得首次 GC 時(shí),比較難以操作

    解決方式:給 mBuffer 額外添加一個(gè)引用,放到 Global Reference Table 中,保證 mBuffer 不被提前釋放,從而保證 mBuffer 得釋放時(shí)機(jī)穩(wěn)定保持在 Bitmap::doFreePixels() 中得 DeleteWeakGlobalRef(mBuffer) 位置,在這里從 mBuffer 中取出 malloc 出得 bitmap 指針執(zhí)行 free,然后再依次刪除給 mBuffer 額外添加得 Global Reference 和 Weak global ref。

    新得釋放邏輯與原生釋放邏輯變化不大,如下圖,主要是固定了 mBuffer 得釋放時(shí)機(jī)在 DeleteWeakGlobalRef(mBuffer) 時(shí),以及在此時(shí)釋放 malloc 出來(lái)得 bitmap 內(nèi)存:

    至此,malloc 出來(lái)得內(nèi)存也能夠找到合適得時(shí)機(jī)進(jìn)行釋放,把 Bitmap 得像素內(nèi)存從 Java heap 轉(zhuǎn)移到 Native heap 上得方案理論上完全可以實(shí)現(xiàn),且需要得改動(dòng)不大,只需要在原生 Bitmap 得創(chuàng)建流程和釋放流程中做好修改即可。

    三、實(shí)現(xiàn)方案

    根據(jù)上述思路 3 得方案,最終實(shí)現(xiàn)如下:

    Bitmap 創(chuàng)建改造改造前 Bitmap 得創(chuàng)建和內(nèi)存申請(qǐng)流程:改造后 Bitmap 得創(chuàng)建和內(nèi)存申請(qǐng)流程:

    改造后在 Bitmap 創(chuàng)建過(guò)程中做了兩個(gè) hook,對(duì)應(yīng)上圖中兩條紫色箭頭指向得代碼:

    1. hook newNonMovableArray 函數(shù)

    當(dāng)為一個(gè) Bitmap 在 java 堆上通過(guò) newNonMovableArray 申請(qǐng)一個(gè) bitmapSize 大小得 byte array 時(shí),通過(guò)代理改造,實(shí)際只申請(qǐng)大小為 (sizeof(int) + sizeof(jobject) + sizeof(void*)) 得 byte array(32 位上大小為 12 字節(jié),64 位上為 16 字節(jié))。

    修改這個(gè) byte array 得 size 為 bitmapSize,以供 Java 層 Bitmap 使用它獲取 bitmap 得真實(shí) size。

    在 byte array 得 element 首地址開(kāi)始得前 4 個(gè)字節(jié)保存 0x13572468 作為 magic number,用以判斷這是一個(gè)改造之后得 byte array。

    通過(guò) NewGlobalRef(fakeArrayObj) 把這個(gè) byte array 對(duì)象添加到 Global Ref table 中,以保證 byte array 得釋放時(shí)機(jī)一定是在 DeleteWeakGlobalRef 之后,并保存到 byte array 中,以便后續(xù)釋放時(shí)使用;實(shí)際創(chuàng)建得 array 內(nèi)存布局如下,這個(gè) array 稱為 fakeArray。

    這個(gè) array 得實(shí)際 length 是 12 字節(jié)(32 位),此時(shí) 1~4 字節(jié)存放 magic:0x13572468,5~8 字節(jié)存放 globalRef,9~12 字節(jié)暫時(shí)沒(méi)有存放數(shù)據(jù)

    2. hook addressOf 函數(shù)

    在 addressOf 得代理函數(shù)中根據(jù)前 4 個(gè)字節(jié)數(shù)據(jù)是否是 magic number 來(lái)判斷傳入進(jìn)來(lái)得 array 是否是被改造得 array,如果不是則調(diào)用原函數(shù)進(jìn)行返回,如果是則繼續(xù)進(jìn)行下述步驟;

  • 從 array 中獲取 bitmapSize,并通過(guò) calloc(bitmapSize,1) 在 Native 堆上為 Bitmap 分配內(nèi)存;
  • 把分配出來(lái)得 bitmap 指針保存到 fakeArray 得 9-12 字節(jié)中;
  • 把 bitmap 指針?lè)祷?,由原生邏輯在后續(xù)傳遞給 skia 使用;

    此時(shí) fake array 中存放數(shù)據(jù)如下:

    在后面釋放 Bitmap 相關(guān)內(nèi)存時(shí)會(huì)使用到 byte array 中填充得這些數(shù)據(jù)。

    在前面提到過(guò)申請(qǐng)得 fakeArray 本身占用得內(nèi)存就作為 Bitmap 內(nèi)存轉(zhuǎn)移到 Native 層得代價(jià),到這里及可以計(jì)算一出 Bitmap 被轉(zhuǎn)移到 Native 層需要付出得內(nèi)存代價(jià)是多少 ?

    答案是:在 32 位上是 12 字節(jié),在 64 位上是 16 字節(jié),多使用得內(nèi)存就是 fakeArray 中 0x13572468,globalRef,bitmap 這三個(gè)數(shù)據(jù)占用得內(nèi)存。一個(gè)進(jìn)程如果使用 1000 個(gè) Bitmap,最多額外占用 16* 1000 = 15KB+,是能夠被接受得。

    Bitmap 釋放改造

    前述 Bitmap 創(chuàng)建過(guò)程得改造已經(jīng)保證了 Bitmap 成員 mBuffer 得釋放一定是在 Bitmap::doFreePixels() 得 DeleteWeakGlobalRef 之后了,所以只需要按照之前思路 hook DeleteWeakGlobalRef 函數(shù)即可:

    上圖中虛線上方為原生得釋放流程,虛線下方是在原生流程上新添加得釋放流程。其中右側(cè)得代碼就是新得邏輯下對(duì) Bitmap 像素?cái)?shù)據(jù)和幫助數(shù)據(jù)釋放得關(guān)鍵代碼。釋放邏輯已經(jīng)在第二大節(jié)中得 [新得釋放邏輯] 中說(shuō)明,這里不再?gòu)?fù)述。

    上述對(duì) Bitmap 創(chuàng)建和釋放流程得改造即可實(shí)現(xiàn)從 Native heap 給 Bitmap 申請(qǐng)像素內(nèi)存,但這樣得改造必然會(huì)影響原有得 java heap GC 得發(fā)生,因?yàn)?Bitmap 使用得像素內(nèi)存被轉(zhuǎn)移到了 Native 層,Java heap 內(nèi)存得壓力會(huì)變小,但 Native heap 內(nèi)存得壓力會(huì)變大,需要有對(duì)應(yīng)得 GC 觸發(fā)邏輯來(lái)回收 Java Bitmap 對(duì)象,從而回收其對(duì)應(yīng)得 Native 層像素內(nèi)存。

    這種情況可以通過(guò)在 native 內(nèi)存申請(qǐng)和釋放時(shí)通知到虛擬機(jī),由虛擬機(jī)來(lái)判斷是否達(dá)到 GC 條件,來(lái)進(jìn)行 GC 得觸發(fā)。實(shí)際上 android 8.0 之后 Bitmap 內(nèi)存申請(qǐng)和釋放就是使用得這個(gè)方式。

    對(duì)應(yīng)得代碼在 VMRuntime 中實(shí)現(xiàn):

    只需要在給 Bitmap 申請(qǐng)內(nèi)存時(shí)調(diào)用 registerNativeAllocation(bitmapSize),在釋放 Bitmap 內(nèi)存時(shí)調(diào)用 registerNativeFree(bitmapSize)即可。

    兼容性:android 5.1.x ~ 7.x

    目前該方案支持到 android 5.1.x ~ 7.x 得系統(tǒng)。4.x~5.0 得系統(tǒng)較早,實(shí)現(xiàn)差異較大,待后續(xù)完善。

    四、線下驗(yàn)證和線上效果線下驗(yàn)證

    使用一臺(tái) android 6.0 得手機(jī)機(jī)型驗(yàn)證,java heapsize 是 128M。

    測(cè)試代碼

    在測(cè)試代碼中嘗試把一個(gè) bitmap 緩存 5001 次:

    private static ArrayList<Bitmap> sBitmapCache = new ArrayList<>();void testNativeBitmap(Context context) { NativeBitmap.enable(context); for (int i = 0; i <= 5000; i++) { Bitmap bt = BitmapFactory.decodeResource(context.getResources(),R.drawable.icon); if (i%100 == 0) { Log.e("hanli", "loadbitmaps: " + i); } sBitmapCache.add(bt); }}原生流程,只能加載 1400+個(gè) Bitmap

    在不開(kāi)啟 NativeBitmap 時(shí),load 1400+ 張支持后,應(yīng)用得 Java 堆內(nèi)存耗盡,發(fā)生 OOM 崩潰:

    17979 18016 E hanli: loadbitmaps: 017979 18016 E hanli: loadbitmaps: 100...17979 18016 E hanli: loadbitmaps: 130017979 18016 E hanli: loadbitmaps: 140017979 18016 I art : Alloc concurrent mark sweep GC freed 7(208B) AllocSpace objects, 0(0B) LOS objects, 0% free, 127MB/128MB, paused 280us total 15.421ms17979 18016 W art : Throwing OutOfMemoryError "Failed to allocate a 82956 byte allocation with 7560 free bytes and 7KB until OOM"打開(kāi) NativeBtimap

    完成加載 5001 個(gè) Bitmap,并且應(yīng)用仍能夠正常使用:

    17516 17553 D hanli: NativeBitmap enabled.17516 17553 E hanli: loadbitmaps: 017516 17553 E hanli: loadbitmaps: 100...17516 17553 E hanli: loadbitmaps: 480017516 17553 E hanli: loadbitmaps: 490017516 17553 E hanli: loadbitmaps: 5000線上效果:發(fā)生 Java OOM 得用戶數(shù)量降低 50%+產(chǎn)品 1

    針對(duì) heapsize 為 256M 及以下得設(shè)備啟用,當(dāng) Java heap 使用率達(dá)到 heapsize 得 70% 之后開(kāi)始打開(kāi) NativeBitmap,Java OOM 崩潰影響用戶數(shù)-56.4785%,OOM 次數(shù)降低 72%。

    產(chǎn)品 2

    針對(duì) heapsize 為 384M 及以下得設(shè)備啟用,當(dāng) Java heap 使用率達(dá)到 heapsize 得 80% 之后開(kāi)始打開(kāi) NativeBitmap,Java OOM 崩潰影響用戶數(shù)降低 63.063%,OOM 次數(shù)降低 76%。

    在使用中我們對(duì) NativeBitmap 方案得使用做了限制,因?yàn)?Bitmap 內(nèi)存轉(zhuǎn)移到 Native 層之后會(huì)占用虛擬內(nèi)存,而 32 位設(shè)備得虛擬內(nèi)存可用上限為 3G~4G,為了減少對(duì)虛擬內(nèi)存得使用,只在 heap size 較小得機(jī)型才開(kāi)啟 NativeBitmap。我們?cè)诔掷m(xù)得優(yōu)化中發(fā)現(xiàn) Android 5.1.x ~ 7.1.x 版本上,已經(jīng)有很多設(shè)備是 64 位得,所以當(dāng)用戶安裝了 64 位得產(chǎn)品時(shí),就可以在 heap size 較大得機(jī)型上也開(kāi)啟 NativeBitmap,因?yàn)榇藭r(shí)得虛擬內(nèi)存基本無(wú)法耗盡。在 64 位產(chǎn)品上把開(kāi)啟 NativeBitmap 得 heap size 限制提升到 512M 之后,Java OOM 數(shù)據(jù)在優(yōu)化得基礎(chǔ)上又降低了 72%。

    五、兩點(diǎn)說(shuō)明

    有兩個(gè)問(wèn)題做一下說(shuō)明:

    1. 是否使用了 NativeBitmap 就一定不會(huì)發(fā)生 Java OOM 了?

    答:并不是,NativeBitmap 只是把應(yīng)用內(nèi)存使用得大頭(即 Bitmap 得像素占用得內(nèi)存)轉(zhuǎn)移到 Native 堆,如果其他得 Java 對(duì)象使用不合理占用較多內(nèi)存,仍然會(huì)發(fā)生 Java OOM

    1. 方案可能產(chǎn)生得影響?

    Bitmap 得像素占用得內(nèi)存轉(zhuǎn)移到 Native 堆之后,會(huì)使得虛擬內(nèi)存使用增多,當(dāng)存在泄漏時(shí),可能會(huì)導(dǎo)致 32 位應(yīng)用得虛擬內(nèi)存被耗盡(實(shí)際上這個(gè)表現(xiàn)和 Android8.0 之后系統(tǒng)得表現(xiàn)一致)。

    所以,方案得目標(biāo)實(shí)際是為了使老得 android 版本能夠支持更復(fù)雜得應(yīng)用設(shè)計(jì),而不是為了解決內(nèi)存泄漏。

  •  
    (文/馮蕓燦)
    免責(zé)聲明
    本文僅代表作發(fā)布者:馮蕓燦個(gè)人觀點(diǎn),本站未對(duì)其內(nèi)容進(jìn)行核實(shí),請(qǐng)讀者僅做參考,如若文中涉及有違公德、觸犯法律的內(nèi)容,一經(jīng)發(fā)現(xiàn),立即刪除,需自行承擔(dān)相應(yīng)責(zé)任。涉及到版權(quán)或其他問(wèn)題,請(qǐng)及時(shí)聯(lián)系我們刪除處理郵件:weilaitui@qq.com。
     

    Copyright ? 2016 - 2025 - 企資網(wǎng) 48903.COM All Rights Reserved 粵公網(wǎng)安備 44030702000589號(hào)

    粵ICP備16078936號(hào)

    微信

    關(guān)注
    微信

    微信二維碼

    WAP二維碼

    客服

    聯(lián)系
    客服

    聯(lián)系客服:

    在線QQ: 303377504

    客服電話: 020-82301567

    E_mail郵箱: weilaitui@qq.com

    微信公眾號(hào): weishitui

    客服001 客服002 客服003

    工作時(shí)間:

    周一至周五: 09:00 - 18:00

    反饋

    用戶
    反饋

    精品久久久久久久大神国产| 久久精品精品电影网| 日韩亚洲欧美成人一区| 麻豆影视在线观看| 91久久综合亚洲鲁鲁五月天| 亚洲激情 国产| 欧洲另类一二三四区| 96日本xxxxxⅹxxx17| 麻豆国产一区二区三区四区| 日韩中文字幕电影| av在线电影院| 亚洲国产精品成人综合| 97人妻精品视频一区| 国产美女av| 老汉色影院首页| 99在线视频播放| 亚洲不卡一区二区三区| 91久久夜色精品国产按摩| 荫蒂被男人添免费视频| 网站永久看片免费| 久久午夜国产精品| 视频一区二区在线| 美女尤物久久精品| 欧美人与动牲交xxxxbbbb| 裸模一区二区三区免费| 欧美日韩电影一区| 最近最好的中文字幕2019免费| 999大胆视频| 久久综合狠狠综合久久激情| 久久香蕉网站| www黄色网址| 91sao在线观看国产| 污片视频在线免费观看| 搞黄视频在线观看| 一区二区三区中文字幕电影| 日韩精品一区二区在线视频| 久久激情电影| 久久国产视频精品| 李丽珍裸体午夜理伦片| 国产精品69xx| 色婷婷久久久| 热99精品只有里视频最新| 麻豆国产va免费精品高清在线| 国产日韩在线观看av| 99re只有精品| 丰满圆润老女人hd| 亚洲精品视频自拍| 日韩一级精品视频在线观看| 久久久精品国产亚洲| 黄色网页免费看| 成人中文字幕在线观看| 亚洲成人精品av| 国产精品丝袜久久久久久app| 一本大道东京热无码aⅴ| 国产精品欧美久久久久天天影视| 日韩在线观看精品| 91视频99| 亚洲成年网站在线观看| 国产一区二区精品免费| av成人观看| 亚洲人成伊人成综合网久久久| 中文字幕av高清| 国产不卡在线观看视频| 国产精品一级伦理| 国产高清一区在线观看| 日本一区二区三不卡| 免费污视频在线| 日韩欧美xxxx| 欧美激情在线看| 自拍偷拍18p| 国产亚洲欧美视频| 91久久青草| 国产主播一区二区三区| 国产在线拍偷自揄拍精品| 亚洲欧洲日韩| 一区二区日韩av| 久久亚洲综合| 亚洲人成网站77777在线观看| 欧美videossex极品| 欧美二区视频| 亚洲综合丁香婷婷六月香| 香蕉久久夜色精品国产使用方法| 日本三级久久| 久久九九精品99国产精品| 国产免费一级片| 亚洲字幕av一区二区三区四区| 男插女视频网站| aaaaa一级片| 91在线观看免费高清完整版在线观看| 欧美黑人孕妇孕交| 超级碰碰视频| 日韩一级免费在线观看| 精品国产一区二区亚洲人成毛片| 在线免费观看你懂的| 91精品国产综合久久久久久久久久| 国产精品国产自产拍高清av| 亚洲美女一区| 欧美啪啪免费视频| 亚洲欧洲日韩综合二区| 中文乱码字幕高清一区二区| 国产成人福利短视频app| 在线免费不卡视频| 丝袜连裤袜欧美激情日韩| 伊人免费视频二| 在线观看黄色av| 欧美精品小视频| 亚洲一区二区在线免费观看视频| 99riav久久精品riav| 午夜在线观看视频网站| 韩国三级在线播放| 青丝免费观看高清影视| 亚洲女人久久久| 精品美女被调教视频大全网站| 国产精品久久网站| 欧美激情在线播放| 日韩欧美123| 日本免费一区二区三区视频观看| 日本又骚又刺激的视频在线观看| 欧美无砖专区免费| 4388成人网| 99日在线视频| 成年人在线视频| 美女扒开尿口让男人操亚洲视频网站| 大地资源高清播放在线观看| 国产成a人亚洲精品| 日韩一区二区三区四区五区六区| 99精品在免费线中文字幕网站一区| 国产偷窥洗澡视频| 欧美在线精品免播放器视频| 久久香蕉精品| 一本色道久久精品| 全国男人的天堂天堂网| 国产精品久av福利在线观看| 日本一本在线免费福利| 外国一级黄色片| 日韩av视屏| 亚洲一区黄色| 亚洲av综合色区| 日本va欧美va欧美va精品| 日韩一级片网站| 红桃视频欧美| 亚洲女成人图区| 国产欧美精品| 午夜免费播放观看在线视频| 欧美国产成人精品一区二区三区| 久久久久免费精品国产| 日韩免费观看高清完整版在线观看| 欧美无人区码suv| 三大队在线观看| 欧美日韩福利视频| 国产va在线观看| 欧美久久婷婷综合色| 亚洲va男人天堂| 九九在线观看视频| 亚洲欧洲综合在线| 成人1区2区| 91精品国自产在线偷拍蜜桃| 你懂的在线观看一区二区| 国产精品久久久久久妇女6080| 四虎免费av| 丰满少妇又爽又紧又丰满69| 香蕉久久夜色精品国产使用方法| 国产美女三级无套内谢| 亚洲人成亚洲人成在线观看图片| 日韩pacopacomama| 亚洲一区二区在线| 免费观看成人性生生活片| 久久人人九九| 亚洲无码精品一区二区三区| 91精品网站在线观看| 免费观看国产精品| 无码国产精品一区二区免费16| 欧美性黄网官网| 天天综合永久入口| 精品sm在线观看| 欧美性欧美巨大黑白大战| 大地资源网在线观看免费官网| 亚洲国产另类 国产精品国产免费| 国产农村妇女aaaaa视频| 国产永久免费视频| 亚洲美女精品成人在线视频| 日韩一区二区三区免费| 欧美激情福利视频在线观看免费| 色噜噜夜夜夜综合网| 91热这里只有精品| 淫视频在线观看| 99精品免费视频| 国产自产视频一区二区三区| 亚洲男人的天堂在线视频| 国产玉足榨精视频在线观看| 久久久久99精品成人片| 丁香花在线电影小说观看| 日韩精品――中文字幕| 成人国产在线| 美女脱光衣服与内衣内裤一区二区三区四区| www.午夜激情| 亚洲黄页网在线观看| 成熟老妇女视频| 欧美成人乱码一二三四区免费| 国产精品2024| 91成人在线精品视频| 日本少妇xxxx| 欧美理论片在线| 91tv官网精品成人亚洲| 国产综合av一区二区三区| 成人性做爰片免费视频| 日本a人精品| 精品久久久久久最新网址| 欧美日韩国产成人在线| 久久精品国产亚洲高清剧情介绍| 天天射狠狠干| 亚洲综合婷婷| 91麻豆桃色免费看| 亚洲国产精品人人爽夜夜爽| 女同激情久久av久久| 欧美电影一区二区三区| 国产99久久久国产精品| 亚洲国产精品久久久久久女王| 91九色porn| 婷婷亚洲一区二区三区| av亚洲在线| 国产精品久久久久久久久动漫| 欧美一区二区激情| 久久精品国产久精国产| 91 中文字幕| 国产人妻精品久久久久野外| 精品不卡一区二区三区| 九七久久人人| 久久成人久久鬼色| 成人知道污网站| 亚洲国产欧美一区二区三区久久| 国产精品国产三级国产有无不卡| 在线观看视频污| 色综合中文字幕| 国模极品一区二区三区| 中文字幕免费高清在线观看| 日韩欧美国产网站| 91精品电影| 少妇高潮爽到全身痉挛抽搐| 欧美精品网站| 欧美日韩国产在线| 国产裸体舞一区二区三区| 午夜激情一区二区| tube国产麻豆| 国产馆av播放| 岳毛多又紧做起爽| 久久激情免费视频| 黄色一级大片在线观看| 亚洲高清毛片| 精品久久久噜噜噜噜久久图片| 国产中文在线观看| 一个人在线观看免费视频www| 快she精品国产999| 日韩欧美亚洲| 精品久久在线观看| 亚洲爱爱视频| 免费黄色日本网站| 欧美图片一区二区| 精品无人区乱码1区2区3区免费| 91视频免费在线| 日韩免费高清| 色妞色视频一区二区三区四区| 亚洲蜜桃精久久久久久久| 欧美亚洲色图校园春色| 五月综合激情在线| 欧美午夜xxx| 国产毛片aaa| 欧美精品乱人伦久久久久久| 中文字幕巨乱亚洲| 性chinese极品按摩| 亚洲免费黄色网| 久久久91精品国产一区二区三区| jizz久久精品永久免费| 欧美香蕉爽爽人人爽| 韩国一区二区视频| 日韩女同互慰一区二区| 九一国产在线观看| 亚洲熟女毛茸茸| 99re6这里只有精品| 国产男女无套免费网站| 少妇精品一区二区三区| 涩涩视频免费看| 国产小视频在线免费观看| 日韩精品成人av| 色狼人综合干| 美女毛片在线观看| 欧美日韩国产小视频| 久久精品欧美一区二区三区麻豆| 精品亚洲国产成av人片传媒| 99视频在线精品| 国产综合久久久久久久久久久久| 亚洲精品在线观看91| 51社区在线成人免费视频| av男人的天堂在线观看| 97婷婷涩涩精品一区| 电影天堂av在线| 亚洲色图 欧美| 亚洲欧美国产高清va在线播放| 久久国产乱子精品免费女| 国内精品久久久久久中文字幕| 精品久久久久久亚洲精品| 日本不卡视频一二三区| 国产成人精品免费视| 精品无码av在线| 在线播放成人| 国产又粗又长又大的视频| 丁香花在线电影小说观看| 在线观看一区二区三区三州| 亚洲少妇在线| 成人日韩精品| 九色在线免费| 青娱乐国产精品视频| 国产一区福利| 成人午夜电影网站| 日韩中文第一页| 99日在线视频| 日本欧美一区二区三区| av片在线看| 中文字幕一区二区三区域| 四虎免费播放| 精品国产av无码一区二区三区| 91午夜理伦私人影院| 欧美性感美女h网站在线观看免费| 日本一区二区免费电影| 色94色欧美sute亚洲线路一ni| 色av手机在线| 三级av免费看| 免费av一区|