Android APP memory用量如何回收

本文详细介绍了Android应用内存的构成及管理方法,包括NativeHeap、DalvikHeap、mmap等内存区域的分析技巧,并提供了有效回收内存的具体策略。

1. Android APP memory用量都有哪些

部分App的同学不太清楚自己的用量都用在那里,如何快速回收等,这里主要是讲一下用量分布,和快速回收部分内存的方法

$ adb shell dumpsys -t 100 meminfo com.android.systemui
Applications Memory Usage (in Kilobytes):
Uptime: 7193430 Realtime: 70018589

** MEMINFO in pid 5312 [com.android.systemui] **
                   Pss  Private  Private  SwapPss      Rss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty    Total     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap    47639    47596        0    16153    49500    74520    68733     5786
  Dalvik Heap    12002    10392        0      268    16628    19698     9849     9849
 Dalvik Other     1894     1764        0      331     3284                           
        Stack     1472     1472        0      680     1488                           
       Ashmem       70       52        0        0      664                           
    Other dev       77        4       72        0      536                           
     .so mmap     6635      368      756      357    64032                           
    .jar mmap     2578        0        0        0    34024                           
    .apk mmap    17791        0    15032        0    26404                           
    .ttf mmap      258        0        0        0      820                           
    .dex mmap    19236       36    19172       12    19904                           
    .oat mmap      766        0        8        0    16124                           
    .art mmap    12938    12176       84      927    28088                           
   Other mmap      113        8       68        0      968                           
   EGL mtrack     3576     3576        0        0     3576                           
    GL mtrack    33972    33972        0        0    33972                           
      Unknown      982      976        0      182     1324                           
        TOTAL   180909   112392    35192    18910   301336    94218    78582    15635

可以看到主要有
1、Native Heap: 主要是libc_malloc分配的内存
2、Dalvik:这个是虚拟机用量的内存
3、mmap:这个是载入java进程的文件
4、EGL/GL mtrack:这个是显示相关的一些用量

2. Native Heap的用量分析

性能问题分析方法[1] --- RAM有讲到,这里讲一下之前没有提到的

1、libc.debug.malloc.program抓取
之前用的方法是发送kill -45和kill -47的信号量

	1. $adb root

	2. $adb shell setenforce 0
	//同时此处加上将/data/local/tmp的权限改成777
	adb shell
	cd /data/local/
	chmod 777 tmp

    //32bit使用app_process
	3. $setprop libc.debug.malloc.program app_process64

    //(如果这个设置不成功,可以adb shell, 进去设置)
	4. $setprop libc.debug.malloc.options "backtrace_enable_on_signal backtrace leak_track"

	5. $stop

	6. $start

    //此处指令是可以开始监控了native用量了
	7. $kill -45 $(pidof com.android.systemui)

	8. 此时你可以去复现问题

    //如果问题复现,可以输入这个指令,将在/data/local/tmp目录生成类似改进程堆栈信息的文件
	9. $kill -47 $(pidof com.android.systemui)

    //(native_heapdump_viewer是在development/scripts里面,这个操作需要复现问题手机的symbols)
	10. python development/scripts/native_heapdump_viewer.py --verbose --html backtrace_heap.2225.txt --symbols ***/out/target/product/***/symbols > backtrace_heap.html

现在我们可以使用更简单的方法am dumpheap -n,其实际是在ActivityThread中调用Debug.dumpNativeHeap

	1. $adb root

	2. $adb shell setenforce 0
	//同时此处加上将/data/local/tmp的权限改成777
	adb shell
	cd /data/local/
	chmod 777 tmp

    //32bit使用app_process
	3. $setprop libc.debug.malloc.program app_process64

    //(如果卡顿backtrace可以适当改小)
	4. $adb shell setprop libc.debug.malloc.options backtrace=64

	5. $stop

	6. $start

	7. 此时你可以去复现问题

    //如果问题复现,可以输入这个指令,将在/data/local/tmp目录生成类似改进程堆栈信息的文件
	8. $am dumpheap -n $(pidof com.android.systemui) /data/local/tmp/native_heap2.txt

    //(native_heapdump_viewer是在development/scripts里面,这个操作需要复现问题手机的symbols)
	11. python development/scripts/native_heapdump_viewer.py --verbose --html backtrace_heap.2225.txt --symbols ***/out/target/product/***/symbols > backtrace_heap.html

2、使用Android Studio profile的Native Size + reference也可以找到一部分native heap的内容(下图gms core的hprof文件用Android Studio打开,此处紧急说明Native Sizereference分别在哪个位置)
在这里插入图片描述

3. Dalvik Heap的用量分析

这个也可以参考性能问题分析方法[1] --- RAM
此处注意一下建议使用Android Studio的profile和MemoryAnalyzer 2个工具结合分析,分析出来的综合对比

4. mmap的用量分析

这个使用adb shell showmap +pid形式加上xlsx的排序,大概你就知道是什么载入app导致用量高了(如果你想要更细的,可以使用adb shell cat /proc/(+pid)/smaps查看)

5.EGL/GL mtrack的用量分析

ion可以看一部分,但不全

	// mtk的自己做的节点
	adb shell cat /sys/kernel/debug/ion/ion_mm_heap
    //android R/S用这个
	adb shell cat /proc/ion/heaps/ion_mm_heap

	// 高通的
	adb shell cat /sys/kernel/debug/ion/heaps/system
	adb shell cat /sys/kernel/debug/dma_buf/dmaprocs           // kernel 4.14以后的版本使用这个

主要指的是这部分用量

   EGL mtrack     3576     3576        0        0     3576                           
    GL mtrack    33972    33972        0        0    33972                           

6. Memory用量如何及时回收

我们除了关注索引关系,还需要关注如何尽快释放内存,内存都在自己控制范围内(特别是低RAM手机)
内存在自己管控范围内

=> 如场景:app内部从主界面跳转到别的界面,然后回到主界面过程中发现很多用量无法回到初始用量,内存就是增加了

除了上述在合适时候解除索引reference关系,还建议在适当的时候主动释放内存

最见效果的是1、2的方法,如果发现这部分大了,可以试一下,配合索引解除有很大可能回到用量初始状态

1、 Java heap: System.gc();System.runFinalization();System.gc();
2、 GL/EGL: ThreadedRenderer.trimMemory(20或者80,80回收多一点,20影响小一点,按需求);(如果不行(上面80不行可以用下面的80)可以使用WindowManagerGlobal.getInstance().trimMemory(80);如果还是不行就直接调用ActivityThread的scheduleTrimMemory吧)
3、 Database: SQLiteDatabase.releaseMemory();
4、 font/image caches和text layout的回收:Canvas.freeCaches();Canvas.freeTextLayoutCaches();

ps:

本地调试回收GL/EGL使用的方法是adb指令的trimMemory,
adb shell am send-trim-memory --user -2 com.android.systemui 80;

注意需要改一下平台限制,不然adb指令不会执行

frameworks/base$ git diff
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d8243d1..4b4c4d8 121644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3117,12 +3117,12 @@ public class ActivityManagerService extends IActivityManager.Stub
                 throw new IllegalArgumentException("Process has no app thread");
             }
             if (app.mProfile.getTrimMemoryLevel() >= level) {
-                throw new IllegalArgumentException(
+                Slog.w(TAG,
                         "Unable to set a higher trim level than current level");
             }
             if (!(level < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN ||
                     app.mState.getCurProcState() > PROCESS_STATE_IMPORTANT_FOREGROUND)) {
-                throw new IllegalArgumentException("Unable to set a background trim level "
+                Slog.w(TAG, "Unable to set a background trim level "
                     + "on a foreground process");
             }

执行后可以看到EGL/GL的用量显著减少(当然这个方法对于launcher的使用要小心一点,不要回收launcher主界面这部分用量(其它用量可以回收,不是一刀切),如果回收,每次回到桌面都会变慢(主要是在低配置手机申请内存比较慢,高配置手机速度快影响就很小了),需要根据场景使用,注意使用时机,避免正在使用中触发回收,80不行的话20也可以)

** MEMINFO in pid 5312 [com.android.systemui] **
                   Pss  Private  Private  SwapPss      Rss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty    Total     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap    49388    49348        0     9601    51252    69136    45573    23562
  Dalvik Heap    10733     9144        0      238    15376    19718     9859     9859
 Dalvik Other     1901     1752        0      334     3312                           
        Stack     1496     1496        0      684     1512                           
       Ashmem       75       52        0        0      676                           
    Other dev       12        4        8        0      476                           
     .so mmap     6465      352      648      358    64056                           
    .jar mmap     2343        0        0        0    34296                           
    .apk mmap    18126        0    15340        0    26864                           
    .ttf mmap      262        0        4        0      824                           
    .dex mmap    18504       36    18444       12    19176                           
    .oat mmap      698        0        0        0    16232                           
    .art mmap    13024    12272       84      866    28228                           
   Other mmap      105        8       68        0      968                           
   EGL mtrack     2743     2743        0        0     2743                           
    GL mtrack     1332     1332        0        0     1332                           
      Unknown      980      972        0      182     1324                           
        TOTAL   140462    79511    34596    12275   268647    88854    55432    33421
<think>好的,我现在需要帮助用户解决他们遇到的“DataCloneError: Data cannot be cloned, out of memory”问题。首先,我得回顾用户提供的上下文和他们之前的提问。用户之前已经询问过这个问题,并且我给出了一个综合方案,现在用户可能遇到了类似的问题,或者之前的解决方案没有完全解决问题,需要进一步的帮助。 用户这次还提供了三个站内引用,虽然他们指出这些引用仅供参考,并不是真正的需求,但需要看看是否有可以借鉴的地方。第一个引用是关于微信小程序云数据库参数类型错误,用户将参数从undefined改为对象解决了问题。这可能提示在DataCloneError中需要检查数据结构是否正确,是否包含无法序列化的内容。第二个引用是Git克隆大文件时的内存不足问题,解决方案可能涉及分块处理或调整内存限制。第三个引用是Android模拟器复制文件时的空间不足,可能需要清理内存或优化资源使用。 结合用户当前的错误信息,DataCloneError通常发生在使用postMessage、Web Workers或者IndexedDB等需要对象序列化的场景中。而“out of memory”则说明可能存在内存不足的问题,两者结合可能意味着传输的数据过大或数据结构复杂导致序列化失败并耗尽内存。 首先,我需要确认用户的数据是否可以被正确克隆。结构化克隆算法支持的类型有限,比如不能包含函数、DOM节点、循环引用等。用户可能需要检查数据中是否有这些不可克隆的类型。例如,如果数据中包含函数,就会触发DataCloneError。 其次,内存不足的问题可能由于传输的数据量过大。这时候需要分块传输数据,而不是一次性发送整个大对象。类似Git处理大文件的方法,分块处理可以有效减少内存压力。例如,将数组分成较小的块,逐个传输。 另外,用户可能需要调整环境的内存配置,比如在Node.js中增加堆内存大小,或者在浏览器中优化内存使用。例如,通过Chrome的启动参数增加内存限制,或者在Node.js中使用--max-old-space-size参数。 还需要考虑内存泄漏的可能性。如果存在未释放的内存引用,长时间运行可能导致内存耗尽。用户可以使用内存分析工具,如Chrome的Memory Profiler,来检测内存泄漏点,并及时释放不再使用的对象。 此外,用户可能在传输数据时使用了不合适的格式,例如直接发送复杂的对象而不是先将其转换为可序列化的形式(如JSON)。转换为JSON字符串再解析可以去除不可序列化的属性,但需要注意这会丢失函数等不可序列化的内容。 在代码层面,用户需要检查数据传输的部分,确保每次发送的数据都是简单且可克隆的。例如,在处理大型数组时,避免一次性发送整个数组,而是分批次发送。同时,及时释放不再需要的数据引用,帮助垃圾回收回收内存。 最后,结合站内引用中的案例,比如微信小程序的参数类型错误,用户需要确保传入的数据结构正确,避免undefined或不可序列化的值。例如,在调用云数据库添加数据时,确保data参数是一个有效的对象,而不是undefined或其他类型。 总结下来,解决方案需要涵盖以下几个方面: 1. 检查数据结构,确保不包含不可克隆的类型。 2. 分块处理大数据,减少单次传输的数据量。 3. 调整环境的内存配置,增加可用内存。 4. 优化代码,及时释放内存,避免内存泄漏。 5. 使用正确的数据格式,如JSON转换,确保数据可序列化。 接下来需要将这些步骤具体化,给出可操作的代码示例和配置建议,帮助用户一步步解决问题。同时,需要确保回答结构清晰,语言简洁,符合用户的要求。</think>### DataCloneError: Data cannot be cloned, out of memory 综合解决方案 --- #### 一、核心问题定位 该错误通常出现在以下场景: 1. **数据不可序列化**:尝试通过`postMessage`、`Web Workers`或`IndexedDB`传输包含**函数/DOM对象/循环引用**的数据时触发[^1] 2. **内存超限**:传输数据体积超过浏览器或运行环境的内存分配限制[^2][^4] --- #### 二、分步解决方案 ##### 1. **验证数据可序列化** ```javascript // 检测对象是否可克隆 function isSafeToClone(obj) { const unsafeTypes = [Function, HTMLElement, Map, Set]; return !unsafeTypes.some(type => obj instanceof type) && JSON.stringify(obj) !== undefined; } // 调用示例 console.log(isSafeToClone(yourData)); // false时需处理数据 ``` ##### 2. **内存优化技巧** | 场景 | 优化方法 | 代码示例 | |------|---------|----------| | 大数据传输 | 分块传输(类似Git大文件处理逻辑[^2]) | ```javascript const CHUNK_SIZE = 1e5; // 10万条/块 for (let i=0; i<bigData.length; i+=CHUNK_SIZE) { const chunk = bigData.slice(i, i+CHUNK_SIZE); worker.postMessage({ chunk, index: i }); }``` | | 内存释放 | 强制垃圾回收 | ```javascript if (typeof gc === 'function') gc(); // Node.js需--expose-gc largeData = null;``` | | 格式转换 | 深度清理不可序列化属性(参考微信小程序云数据库经验) | ```javascript const safeData = JSON.parse(JSON.stringify(unsafeData));``` | ##### 3. **环境配置调整** - **浏览器内存扩展**: ```bash # Chrome示例(Windows) chrome.exe --js-flags="--max-old-space-size=4096" ``` - **Node.js内存扩容**: ```bash node --max-old-space-size=4096 your_app.js ``` ##### 4. **高级排查工具** - **Chrome内存分析**: ```text DevTools → Performance → Memory → 录制堆快照 ``` - **Node.js内存检测**: ```javascript const v8 = require('v8'); console.log(v8.getHeapStatistics().used_heap_size / 1024 / 1024 + ' MB'); ``` --- #### 三、典型场景处理 **场景1:传输包含DOM元素的数据** ```javascript // 错误示例 const element = document.getElementById('myCanvas'); worker.postMessage({ element }); // 触发DataCloneError // 正确做法 const canvasData = element.toDataURL('image/png'); worker.postMessage({ canvasData }); ``` **场景2:处理大文件上传**(参考Android模拟器内存问题[^3]) ```javascript // 分片读取文件 const reader = new FileReader(); const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB let offset = 0; reader.onload = e => { webWorker.postMessage(e.target.result); offset += CHUNK_SIZE; if (offset < file.size) readNextChunk(); }; function readNextChunk() { const chunk = file.slice(offset, offset + CHUNK_SIZE); reader.readAsArrayBuffer(chunk); } ``` --- #### 四、预防性编程实践 1. **添加类型校验**(类似微信小程序参数校验[^1]) ```javascript function validatePayload(data) { if (typeof data !== 'object' || data === null) { throw new Error('parameter.data should be object'); } // 其他校验逻辑... } ``` 2. **内存监控模块** ```javascript setInterval(() => { const memUsage = process.memoryUsage?.() || performance.memory; console.log(`内存用量:${Math.round(memUsage.heapUsed / 1024 / 1024)} MB`); }, 5000); ``` --- #### 五、系统级优化建议 1. **调整交换空间**(Linux/Mac) ```bash sudo dd if=/dev/zero of=/swapfile bs=1G count=4 sudo mkswap /swapfile && sudo swapon /swapfile ``` 2. **禁用内存密集型浏览器扩展** ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值