检测内存泄漏
内存泄漏可能会降低应用的性能和用户体验。识别和解决这些泄漏对于创建高效且响应迅速的应用至关重要。
Vega Studio是用于开发Vega应用的集成开发环境 (IDE),具有一项名为MemLab的功能。该功能通过分析JavaScript堆快照来检测适用于Vega的React Native应用中的内存泄漏。MemLab可以识别潜在的内存问题,包括:
- React Native虚拟DOM中类似于分离的文档对象模型 (DOM) 的对象。
- JavaScript对象中的循环引用。
- 保留了组件或事件侦听器以防止垃圾回收。
适用于Vega的React Native及其与React Native的关系
适用于Vega的React Native是亚马逊开发的专有脚本语言和框架,用于构建跨平台应用。它为各种亚马逊设备和平台创建高效且响应迅速的应用。适用于Vega的React Native以React Native为基础构建,继承了它的许多概念和方法。与React Native一样,适用于Vega的React Native使用基于组件的架构,让您编写一次即可在多个平台上部署。
适用于Vega的React Native中的内存管理
与React Native一样,在适用于Vega的React Native应用中,适当的内存管理至关重要。虽然该框架负责处理大部分内存分配和解除分配,但您需要在处理长期存在的对象、事件侦听器和复杂的状态管理时,注意潜在的内存泄漏。
内存泄漏检测的重要性
识别和修复内存泄漏对于维护适用于Vega的React Native应用的性能和稳定性至关重要,尤其是在资源有限的设备上。这就是诸如MemLab之类的工具在开发过程中大显身手之处。
本页提供有关以下方面的指导:
- 创建有意造成内存泄漏的适用于Vega的React Native应用示例。
- 在应用执行的不同阶段捕获堆快照。
- 使用MemLab分析这些快照并识别内存泄漏。
- 解读MemLab的输出以了解泄漏的性质和来源。
先决条件
- 安装Node.js。
-
安装Memlab:
npm install -g memlab
步骤1: 创建示例应用
出于测试目的,使用以下命令创建一个示例应用,其中存在故意造成的示例泄漏:
kepler project generate --template hello-world --name keplersampleapp --packageId com.amazon.keplersampleapp --outputDir keplersampleapp
示例app.js文件
以下文件示出了演示内存泄漏场景的示例应用的代码片段;并提供了创建、清除和分析内存泄漏的工具。
1 import React, { useRef, useState } from 'react';
2 import { Pressable, StyleSheet, Text, View } from 'react-native';
3
4 export default function App() {
5 const detachedDOMRef = useRef([]);
6 const [snapshotInfo, setSnapshotInfo] = useState(null);
7 const [focusedButton, setFocusedButton] = useState(null);
8
9 // 用于创建分离DOM元素的函数(严重内存泄漏)
10 const createDetachedDOMLeak = () => {
11 console.log('创建分离的DOM元素...');
12 const detachedElements = [];
13 for (let i = 0; i < 10000; i++) {
14 // 创建表示分离的DOM元素的对象
15 const element = { id: i, name: `分离的元素${i}` };
16 detachedElements.push(element);
17 }
18 detachedDOMRef.current = detachedElements; // 保留引用,而不是在实际的DOM中
19 console.log('使用10,000个元素创建了分离DOM泄漏');
20 };
21
22 // 用于清除内存泄漏的函数
23 const clearLeaks = () => {
24 console.log('清除所有泄漏...');
25 detachedDOMRef.current = []; // 清除引用
26 console.log('所有泄漏已清除');
27 };
28
29 // 模拟堆快照拍摄
30 const takeHeapSnapshot = async () => {
31 console.log('正在拍摄堆快照...');
32 const snapshot = {
33 detachedDOMSize: detachedDOMRef.current.length,
34 };
35 if (global.__CollectHeapSnapshot__) {
36 global.__CollectHeapSnapshot__();
37 }
38 setSnapshotInfo(snapshot);
39 console.log('堆快照已拍摄:', snapshot);
40 };
41
42 return (
43 <View style={styles.container}>
44 <Text style={styles.text}>Memory Leak Demo</Text>
45
46 <View style={styles.boundary}>
47 <View style={styles.buttonRow}>
48 <Pressable
49 onPress={createDetachedDOMLeak}
50 onFocus={() => setFocusedButton('createDetachedDOMLeak')}
51 onBlur={() => setFocusedButton(null)}
52 style={[
53 focusedButton === 'createDetachedDOMLeak' ? styles.focusedButton : styles.button,
54 ]}
55 >
56 <Text style={styles.buttonText}>Create Detached DOM Leak</Text>
57 </Pressable>
58
59 <Pressable
60 onPress={clearLeaks}
61 onFocus={() => setFocusedButton('clearLeaks')}
62 onBlur={() => setFocusedButton(null)}
63 style={[
64 focusedButton === 'clearLeaks' ? styles.focusedButton : styles.button,
65 ]}
66 >
67 <Text style={styles.buttonText}>清除泄漏</Text>
68 </Pressable>
69
70 <Pressable
71 onPress={takeHeapSnapshot}
72 onFocus={() => setFocusedButton('takeHeapSnapshot')}
73 onBlur={() => setFocusedButton(null)}
74 style={[
75 focusedButton === 'takeHeapSnapshot' ? styles.focusedButton : styles.button,
76 ]}
77 >
78 <Text style={styles.buttonText}>拍摄快照</Text>
79 </Pressable>
80 </View>
81 </View>
82
83 {snapshotInfo && (
84 <Text style={styles.snapshotText}>
85 快照已拍摄:{snapshotInfo.detachedDOMSize}分离的DOM元素
86 </Text>
87 )}
88 </View>
89 );
90 }
91
92 const styles = StyleSheet.create({
93 container: {
94 flex: 1,
95 justifyContent: 'center',
96 alignItems: 'center',
97 backgroundColor: '#e0e0e0',
98 },
99 text: {
100 fontSize: 24,
101 marginBottom: 20,
102 textAlign: 'center',
103 },
104 boundary: {
105 padding: 20,
106 borderWidth: 2,
107 borderColor: '#333',
108 backgroundColor: '#ffffff',
109 borderRadius: 10,
110 },
111 buttonRow: {
112 flexDirection: 'row',
113 justifyContent: 'center',
114 },
115 button: {
116 width: 150,
117 height: 50,
118 marginHorizontal: 5,
119 borderRadius: 5,
120 alignItems: 'center',
121 justifyContent: 'center',
122 backgroundColor: '#007bff',
123 },
124 focusedButton: {
125 backgroundColor: '#003f7f',
126 color: '#000', // 焦点上有黑色文字
127 },
128 buttonText: {
129 color: '#fff',
130 fontSize: 16,
131 },
132 snapshotText: {
133 marginTop: 20,
134 fontSize: 16,
135 color: '#333',
136 },
137 });
步骤2: 构建您的应用
运行以下命令:
npm install
npm run build:app
步骤3: 运行应用
-
运行Vega虚拟设备。
kepler virtual-device start -
在Vega虚拟设备上运行您的应用:
kepler run-kepler <VPKG路径> <应用ID> -d VirtualDevice
步骤4: 捕获和复制堆快照
- 在应用运行时,单击Take Snapshot(拍摄快照)按钮以捕获设备上的堆快照。
- 单击Create Detached DOM Leak(创建分离的DOM泄漏)按钮以生成10,000个内存泄漏元素。
- 重新拍摄快照。
- 单击Clear Leaks(清除泄漏)按钮以移除模拟内存泄漏的元素。
-
拍摄最后的快照。

当您单击Take Snapshot按钮时,日志会显示保存所捕获快照的文件路径。
ls /home/app_user/packages/<应用>/data/ -l total 39764 -rw-r—r-- 1 app_user 30097 26 2024-10-17 08:56 shared_preferences.json -rw-r—r-- 1 app_user 30097 6335533 2024-10-17 08:37 snapshot-17-10-2024-08-37-08.heapsnapshot -rw-r—r-- 1 app_user 30097 8081408 2024-10-17 08:37 snapshot-17-10-2024-08-37-25.heapsnapshot -rw-r—r-- 1 app_user 30097 6336519 2024-10-17 08:39 snapshot-17-10-2024-08-39-27.heapsnapshot -rw-r—r-- 1 app_user 30097 9492333 2024-10-17 08:39 snapshot-17-10-2024-08-39-36.heapsnapshot -rw-r—r-- 1 app_user 30097 10434707 2024-10-17 08:40 snapshot-17-10-2024-08-39-55.heapsnapshot -
将快照文件复制到本地计算机。
使用拍摄的堆快照,可以分析和识别潜在的内存泄漏。
步骤5: 运行MemLab
要在快照上运行MemLab,请使用以下命令格式。
memlab find-leaks --baseline <初始快照> --target <泄漏后快照> --final <清除后快照> --trace-all-objects
当您运行该命令时,MemLab会执行以下操作:
- 将所有快照加载到内存中。
- 比较基准和目标快照以识别新对象。
- 分析这些新对象在最终快照中的持久性。
- 跟踪对象引用以了解保留模式。
MemLab命令结构解释
memlab find-leaks命令是MemLab识别内存泄漏的核心功能。
以下是可选命令:
--baseline <初始快照>- 指定在引入任何潜在泄漏之前拍摄的初始堆快照。它可以作为参考点。--target <泄漏后快照>- 指定引入潜在泄漏后拍摄的快照。MemLab将其与基准进行比较,以确定可能存在泄漏的新对象。--final <清除后快照>- 指定在尝试清除泄漏后拍摄的快照。它可以帮助MemLab确定对象是实际在泄漏还是只是处于一种正常应用状态。--trace-all-objects- 指示MemLab跟踪所有对象,从而提供更全面的分析,但可能会增加处理时间。
MemLab输出示例
memlab find-leaks --baseline snapshot-17-10-2024-08-27-13.heapsnapshot --target snapshot-17-10-2024-08-27-29.heapsnapshot --final snapshot-17-10-2024-08-27-52.heapsnapshot --trace-all-objects
Alive objects allocated in target page:
┌─────────┬─────────────────────────────────────────────────────────┬───────────┬───────┬──────────────┐
│ (index) │ name │ type │ count │ retainedSize │
├─────────┼─────────────────────────────────────────────────────────┼───────────┼───────┼──────────────┤
│ 0 │ 'JSArray' │ 'object' │ 305 │ '1.1MB' │
│ 1 │ 'JSObject(id, name)' │ 'object' │ 10000 │ '1.1MB' │
│ 2 │ 'DictPropertyMap' │ 'object' │ 79 │ '122.1KB' │
│ 3 │ 'HiddenClass' │ 'object' │ 71 │ '92.6KB' │
│ 4 │ 'BoxedDouble' │ 'object' │ 1511 │ '36.2KB' │
│ 5 │ 'JSObject(memoizedState, baseState, baseQueue, queu...' │ 'object' │ 498 │ '23.9KB' │
│ 6 │ 'Environment' │ 'object' │ 287 │ '14.7KB' │
│ 7 │ 'JSFunction' │ 'closure' │ 185 │ '8.8KB' │
│ 8 │ 'JSObject(validated)' │ 'object' │ 155 │ '7.4KB' │
│ 9 │ 'JSObject($$typeof, type, key, ref, props, ...)' │ 'object' │ 155 │ '7.4KB' │
│ 10 │ 'JSObject(tag, create, destroy, deps, next)' │ 'object' │ 132 │ '6.3KB' │
│ 11 │ 'Detached FiberNode' │ 'object' │ 12 │ '5.5KB' │
│ 12 │ 'JSObject(value, children)' │ 'object' │ 66 │ '3.1KB' │
│ 13 │ 'HashMapEntry' │ 'object' │ 61 │ '2.9KB' │
│ 14 │ 'JSObject(style, children)' │ 'object' │ 58 │ '2.7KB' │
│ 15 │ 'warnAboutAccessingRef' │ 'object' │ 54 │ '2.5KB' │
│ 16 │ 'warnAboutAccessingRef' │ 'closure' │ 54 │ '2.5KB' │
│ 17 │ 'JSObject(lastEffect, stores)' │ 'object' │ 48 │ '2.3KB' │
│ 18 │ 'HiddenClass(Dictionary)' │ 'object' │ 45 │ '2.1KB' │
│ 19 │ 'HostObject' │ 'object' │ 43 │ '2KB' │
└─────────┴─────────────────────────────────────────────────────────┴───────────┴───────┴──────────────┘
Sampling trace due to a large number of traces:
Number of Traces: 10100
Sampling Ratio: 49.5%
MemLab found 5 leak(s)
Number of clusters loaded: 0
--Similar leaks in this run: 4956--
--Retained size of leaked objects: 1.1MB--
[(GC roots)] (synthetic) @3 [2.8MB]
--1 (element)---> [(Registers)] (synthetic) @5 [2.2KB]
--57 (element)---> [Environment] (object) @226263 [48 bytes]
--0 (internal)---> [JSObject(current)] (object) @47915 [1.1MB]
--directProp0 (internal)---> [JSArray] (object) @222185 [1.1MB]
--9999 (element)---> [JSObject(id, name)] (object) @191663 [112 bytes]
--class (internal)---> [HiddenClass] (object) @117709 [300 bytes]
--Similar leaks in this run: 22--
--Retained size of leaked objects: 2.9KB--
[(GC roots)] (synthetic) @3 [2.8MB]
--1 (element)---> [(Registers)] (synthetic) @5 [2.2KB]
--2 (element)---> [Detached FiberNode RCTView] (object) @69 [384 bytes]
--pendingProps (property)---> [JSObject(accessibilityViewIsModal, isTVSelectable, hitSlop, alexaEntityType, alexaExternalIds, ...)] (object) @228861 [680 bytes]
--onBlur (property)---> [onBlur] (closure) @45137 [128 bytes]
--prototype (property)---> [onBlur] (object) @224283 [48 bytes]
--Similar leaks in this run: 11--
--Retained size of leaked objects: 560 bytes--
[(GC roots)] (synthetic) @3 [2.8MB]
--1 (element)---> [(Registers)] (synthetic) @5 [2.2KB]
--34 (element)---> [JSObject(Dictionary)] (object) @133 [26.2KB]
--console (property)---> [JSObject(error, info, log, warn, trace, ...)] (object) @78609 [4.8KB]
--directProp1 (internal)---> [JSFunction] (closure) @87937 [256 bytes]
--environment (internal)---> [Environment] (object) @87913 [208 bytes]
--parentEnvironment (internal)---> [Environment] (object) @87801 [32 bytes]
--0 (internal)---> [JSObject(enable, disable, registerBundle, log, setup)] (object) @85777 [3.2KB]
--directProp4 (internal)---> [setup] (closure) @88235 [48 bytes]
--environment (internal)---> [Environment] (object) @88203 [2.6KB]
--11 (internal)---> [JSArray] (object) @88225 [2.1KB]
--4 (element)---> [JSArray] (object) @222491 [200 bytes]
--Similar leaks in this run: 1--
--Retained size of leaked objects: 64 bytes--
[(GC roots)] (synthetic) @3 [2.8MB]
--1 (element)---> [(Registers)] (synthetic) @5 [2.2KB]
--57 (element)---> [Environment] (object) @226263 [48 bytes]
--parentEnvironment (internal)---> [Environment] (object) @47913 [2KB]
--7 (internal)---> [JSObject(container, text, boundary, buttonRow, button, ...)] (object) @77759 [712 bytes]
--snapshotText (property)---> [JSObject(marginTop, fontSize, color)] (object) @77737 [708 bytes]
--class (internal)---> [HiddenClass] (object) @27707 [660 bytes]
--forInCache (internal)---> [SegmentedArray] (array) @224539 [64 bytes]
--Similar leaks in this run: 10--
--Retained size of leaked objects: 40 bytes--
[(GC roots)] (synthetic) @3 [2.8MB]
--3 (element)---> [(RuntimeModules)] (synthetic) @9 [15.4KB]
--378 (element)---> [, ] (symbol) @117483 [4 bytes]
了解MemLab的输出
MemLab的输出包含以下关键元素:
Alive objects allocated in target page(在目标页面中分配的活动对象)
此部分显示应用操作或更改后仍存在于内存中的对象。此列表指出了潜在的内存保留问题,并包含每个对象的以下详细信息:
- name - 对象名称或其结构类型。
- type - 该对象是“对象”(如JSObject、JSArray等)还是“闭包”(保留内存的函数)。
- count - 每种对象类型的实例数。
- retainedSize - 这些对象保留的总内存。
示例
JSArray (Object Type)- 305个实例,保留1.1MB的内存。JSObject(id, name)- 10,000个实例,也保留1.1MB的内存。
Sampling trace due to a large number of traces(由于大量轨迹而导致的采样轨迹)
此部分指出,由于存在大量轨迹 (10,100),MemLab应用了采样率为49.5%的采样来有效地处理和显示轨迹。
MemLab found X leak(s)(MemLab发现了X个泄漏)
在示例中,MemLab发现了5个泄漏。对于每个内存泄漏,输出会列出:
- Number of similar leaks - 发现类似泄漏模式的次数。
- Retained size of leaked objects - 泄漏对象保留的总内存。
- Leak trace - 显示对象如何相互关联,以及为什么它们没有遭遇垃圾回收。这些轨迹表明了导致对象保留的路径。
GC roots(GC根目录)
这些根目录是保留在内存中的对象的入口点,并可防止其他对象遭遇垃圾回收 (GC)。该轨迹指出了由于来自GC根目录的引用,对象如何保持活动状态,其中路径可以追溯到JSObject、JSArray和闭包引用。
示例1
- Leak pattern: 4,956 similar leaks, retaining 1.1MB of memory.
JSObject(current) → JSArray → JSObject(id, name).
这表明JSArray保留了对10,000个JSObject(id, name) 对象的引用,以防止它们遭遇垃圾回收。
示例2
Leak pattern: 22 similar leaks, retaining 2.9KB of memory.
轨迹涉及Detached FiberNode RCTView对象,该对象是React Native视图系统的一部分。泄漏可能与诸如onBlur之类的事件处理程序有关,即使在将其移除之后,也会导致不必要的对象保留。
使用MemLab分析快照
- 检查Alive objects allocated in target page部分以查看保留的对象。
- 查看MemLab found [X] leak(s)(MemLab发现 [X] 个泄漏)一行,看看发现了多少个不同的泄漏。对于每个泄漏:
- 记下类似泄漏的数量和总保留大小。
- 检查对象跟踪以了解对象的保留方式。
- 注意意外保留,例如分离的DOM元素或大型数组。
解决泄漏
您可以通过执行以下操作来解决应用中的泄漏问题:
- 识别泄漏对象(例如,分离的DOM元素、未清除的事件侦听器)中的模式。
- 在您的应用中找到相应的代码。
- 实施修复,例如正确卸载组件、清除数组或删除事件侦听器。
验证修复
您可以通过执行以下操作来验证修复:
- 使用已实施的修复重建并重新运行您的应用。
- 重复快照和分析过程。
- 确认先前发现的泄漏不再存在于MemLab输出中。
相关主题
Last updated: 2025年9月30日

