as

Settings
Sign out
Notifications
Alexa
亚马逊应用商店
AWS
文档
Support
Contact Us
My Cases
新手入门
设计和开发
应用发布
参考
支持

检测内存泄漏

检测内存泄漏

内存泄漏可能会降低应用的性能和用户体验。识别和解决这些泄漏对于创建高效且响应迅速的应用至关重要。

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的输出以了解泄漏的性质和来源。

先决条件

  1. 安装Node.js
  2. 安装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: 运行应用

  1. 运行Vega虚拟设备。

    已复制到剪贴板。

    kepler virtual-device start 
    
  2. 在Vega虚拟设备上运行您的应用:

    kepler run-kepler <VPKG路径> <应用ID> -d VirtualDevice
    

步骤4: 捕获和复制堆快照

  1. 在应用运行时,单击Take Snapshot(拍摄快照)按钮以捕获设备上的堆快照。
  2. 单击Create Detached DOM Leak(创建分离的DOM泄漏)按钮以生成10,000个内存泄漏元素。
  3. 重新拍摄快照。
  4. 单击Clear Leaks(清除泄漏)按钮以移除模拟内存泄漏的元素。
  5. 拍摄最后的快照。

    当您单击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
    
  6. 将快照文件复制到本地计算机。

    使用拍摄的堆快照,可以分析和识别潜在的内存泄漏。

步骤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分析快照

  1. 检查Alive objects allocated in target page部分以查看保留的对象。
  2. 查看MemLab found [X] leak(s)(MemLab发现 [X] 个泄漏)一行,看看发现了多少个不同的泄漏。对于每个泄漏:
  • 记下类似泄漏的数量和总保留大小。
  • 检查对象跟踪以了解对象的保留方式。
  • 注意意外保留,例如分离的DOM元素或大型数组。

解决泄漏

您可以通过执行以下操作来解决应用中的泄漏问题:

  1. 识别泄漏对象(例如,分离的DOM元素、未清除的事件侦听器)中的模式。
  2. 在您的应用中找到相应的代码。
  3. 实施修复,例如正确卸载组件、清除数组或删除事件侦听器。

验证修复

您可以通过执行以下操作来验证修复:

  1. 使用已实施的修复重建并重新运行您的应用。
  2. 重复快照和分析过程。
  3. 确认先前发现的泄漏不再存在于MemLab输出中。

Last updated: 2025年9月30日