1# GC垃圾回收
2
3GC(全称 Garbage Collection),即垃圾回收。在计算机领域,GC就是找到内存中的垃圾,释放和回收内存空间。当前主流编程语言实现的GC算法主要分为两大类:引用计数和对象追踪(即Tracing GC)。ArkTS运行时基于分代模型(年轻代/老年代),混合使用引用计数和对象追踪算法,并行并发化执行GC任务,从而实现不同场景下的高性能内存回收表现。
4
5在ArkTS中,数据类型分为两类,简单类型和引用类型。简单类型内容直接保存在栈(Stack)中,由操作系统自动分配和释放。引用类型保存在堆(heap)中,需要引擎进行手动释放。GC就是针对堆空间的内存自动回收的管理机制。
6
7## Heap结构及其配置参数
8
9### Heap结构
10
11![image](./figures/gc-heap-space.png)
12
13- SemiSpace:年轻代(Young Generation),存放新创建出来的对象,存活率低,主要使用copying算法进行内存回收。
14- OldSpace:老年代(Old Generation),存放年轻代多次回收仍存活的对象会被复制到该空间,根据场景混合多种算法进行内存回收。
15- HugeObjectSpace:大对象空间,使用单独的region存放一个大对象的空间。
16- ReadOnlySpace:只读空间,存放运行期间的只读数据。
17- NonMovableSpace:不可移动空间,存放不可移动的对象。
18- SnapshotSpace:快照空间,转储堆快照时使用的空间。
19- MachineCodeSpace:机器码空间,存放程序机器码。
20
21注:每个空间会有一个或多个region进行分区域管理,region是空间向内存分配器申请的单位。
22
23### 相关参数
24
25> **注意:**
26>
27> 以下参数未提示可配置的均为不可配置项,由系统自行设定。
28
29根据系统分配heap总大小64MB-128MB/128MB-256MB/大于256MB的三个范围,以下参数系统会设置不同的大小。如果表格内范围仅有一个值,则表示该参数值不随heap总大小变化。手机设备heap总大小默认为大于256MB。
30开发者可以查看[hidebug接口文档](../reference/apis-performance-analysis-kit/js-apis-hidebug.md),使用相关接口查询内存信息。
31
32#### 堆大小相关参数
33
34| 参数名称 | 范围 | 作用 |
35| --- | --- | :--- |
36| HeapSize | 448MB | 主线程默认堆空间总大小,小内存设备会依据实际内存池大小修正 |
37| SemiSpaceSize | 2MB-4MB/2MB-8MB/2MB-16MB | semispace空间大小 |
38| NonmovableSpaceSize | 2MB/6MB/64MB | nonmovableSpace空间大小 |
39| SnapshotSpaceSize | 512KB | 快照空间大小 |
40| MachineCodeSpaceSize | 2MB | 机器码空间大小 |
41
42#### worker线程堆上限
43
44| 参数名称 | 范围 | 作用 |
45| --- | --- | --- |
46| HeapSize  | 768 MB | work类型线程堆空间大小 |
47
48#### Semi Space
49
50heap中会生成两个Semi Space供copying使用。
51
52| 参数名称 | 范围 | 作用 |
53| --- | --- | :--- |
54| semiSpaceSize | 2MB-4MB/2MB-8MB/2MB-16MB | semispace空间大小,会根据堆总大小有不同的范围限制 |
55| semiSpaceTriggerConcurrentMark | 1M/1.5M/1.5M| 首次单独触发Semi Space的并发mark的界限值,超过该值则触发 |
56| semiSpaceStepOvershootSize| 2MB | 允许过冲最大大小 |
57
58#### Old Space 和 Huge Object Space
59
60初始化时均设定为Heap剩余未分配空间的大小,默认手机设备主线程OldSpaceSize上限接近350MB。
61
62| 参数名称 | 范围 | 作用 |
63| --- | --- | :--- |
64| oldSpaceOvershootSize | 4MB/8MB/8MB | oldSpace允许过冲最大大小 |
65
66#### 其他空间
67
68| 参数名称 | 范围 | 作用 |
69| --- | --- | :--- |
70| defaultReadOnlySpaceSize | 256 KB | ReadOnlySpace默认空间大小 |
71| defaultNonMovableSpaceSize | 2 MB/6 MB/64 MB | NonMovableSpace默认空间大小|
72| defaultSnapshotSpaceSize | 512 KB/512 KB/ 4 MB | SnapshotSpace默认空间大小|
73| defaultMachineCodeSpaceSize | 2 MB/2 MB/8 MB | MachineCodeSpace默认空间大小|
74
75#### 解释器栈大小
76
77| 参数名称 | 范围 | 作用 |
78| --- | --- | --- |
79| maxStackSize | 128KB | 控制解释器栈帧大小 |
80
81#### 并发参数
82
83| 参数名称 | 值 | 作用 |
84| --- | ---: | --- |
85| gcThreadNum | 7 | gc线程数量,默认为7,可通过`gc-thread-num`参数自行设定该参数值 |
86| MIN_TASKPOOL_THREAD_NUM | 3 | 线程池最小线程数 |
87| MAX_TASKPOOL_THREAD_NUM | 7 | 线程池最大线程数 |
88
89注:该线程池主要用于执行GC流程中的并发任务,实际线程池初始化综合参考gcThreadNum以及线程上下限,gcThreadNum为负值时初始化线程池线程数 = CPU核心数/2
90
91#### 其他参数
92
93| 参数名称 | 值 | 作用 |
94| --- | --- | --- |
95| minAllocLimitGrowingStep | 2M/4M/8M | heap整体重新计算空间大小限制时,控制oldSpace、heapObject和globalNative的最小增长步长 |
96| minGrowingStep | 4M/8M/16M | 调整oldSpace的最小增长步长 |
97| longPauseTime | 40ms | 判断是否为超长GC界限,超长GC会触发完整GC日志信息打印,方便开发者定位分析。可通过`gc-long-paused-time`进行配置 |
98
99### 其他:新增单VM内ArrayBuffer的native总内存上限为4GB
100
101## GC流程
102
103
104![image](./figures/gc-process.png)
105
106### HPP GC
107
108HPP GC(High Performance Partial Garbage Collection),即高性能部分垃圾回收,其中“High Performance”主要三方面,包含分代模型、混合算法和GC流程优化,以下主要是对HPP GC的流程中的一些具体策略的介绍。
109
110#### Young GC
111
112- **触发机制:** 年轻代GC触发阈值在2MB-16MB变化,根据分配速度和存活率等会变化。
113- **说明:** 主要回收semi space新分配的年轻代对象。
114- **场景:** 前台场景
115- **日志关键词:** [ HPP YoungGC ]
116
117#### Old GC
118
119- **触发机制:** 老年代GC触发阈值在20MB-300多MB变化,大部分情况,第一次Old GC的阈值在20M左右,之后会根据对象存活率,内存占用大小进行阈值调整。
120- **说明:** 对年轻代和部分老年代空间做整理压缩,其他空间做sweep清理。触发频率比年轻代GC低很多,由于会做全量mark,因此GC时间会比年轻代GC长,单次耗时约5ms~10ms。
121- **场景:** 前台场景
122- **日志关键词:**[ HPP OldGC ]
123
124#### Full GC
125
126- **触发机制:** 不会由内存阈值触发。应用切换后台之后,如果预测能回收的对象尺寸大于2M会触发一次Full GC。DumpHeapSnapshot 和 AllocationTracker 工具默认会触发Full GC。Native 接口和JS/TS 也有接口可以触发。
127- **说明:** 会对年轻代和老年代做全量压缩,主要用于性能不敏感场景,最大限度回收内存空间。
128- **场景:** 后台场景
129- **日志关键词:**[ CompressGC ]
130
131此后的Smart GC或者 IDLE GC 都是在上述三种GC中做选择。
132
133### 触发策略
134
135#### 空间阈值触发GC
136
137- 函数方法:`AllocateYoungOrHugeObject`,`AllocateHugeObject`等分配函数
138- 限制参数:对应的空间阈值
139- 说明:对象申请空间到达对应空间阈值时触发GC。
140- 典型日志:日志可区分GCReason::ALLOCATION_LIMIT
141
142#### native绑定大小达到阈值触发GC
143
144- 函数方法:`GlobalNativeSizeLargerThanLimit`
145- 限制参数:`globalSpaceNativeLimit`
146- 说明:影响是否进行全量mark,以及是否开始并发mark。
147
148#### 切换后台触发GC
149
150- 函数方法:`ChangeGCParams`
151- 说明:切换后台主动触发一次Full GC。
152- 典型日志:`app is inBackground`,`app is not inBackground`
153  GC 日志中可区分GCReason::SWITCH_BACKGROUND
154
155### 执行策略
156
157#### ConcurrentMark
158
159- 函数方法:`TryTriggerConcurrentMarking`
160- 说明:尝试触发并发mark,将遍历对象进行标记的任务交由线程池中并发运行,减少UI主线程挂起时间。
161- 典型日志:`fullMarkRequested`,`trigger full mark`,`Trigger the first full mark`,`Trigger full mark`,`Trigger the first semi mark`,`Trigger semi mark`
162
163#### new space GC前后的阈值调整
164
165- 函数方法:`AdjustCapacity`
166- 说明: 在GC后调整SemiSpace触发水线,优化空间结构。
167- 典型日志:无直接日志,可以通过GC统计日志看出,GC前 young space 的阈值有动态调整。
168
169#### 第一次OldGC后阈值的调整
170
171- 函数方法:`AdjustOldSpaceLimit`
172- 说明:根据最小增长步长以及平均存活率调整OldSpace阈值限制。
173- 典型日志:`"AdjustOldSpaceLimit oldSpaceAllocLimit_: " << oldSpaceAllocLimit << " globalSpaceAllocLimit_: " << globalSpaceAllocLimit_;`
174
175#### 第二次及以后的OldGC对old Space/global space阈值调整,以及增长因子的调整
176
177- 函数方法:`RecomputeLimits`
178- 说明:根据当前GC统计的数据变化重新计算调整`newOldSpaceLimit`,`newGlobalSpaceLimit`,`globalSpaceNativeLimit`和增长因子。
179- 典型日志:`"RecomputeLimits oldSpaceAllocLimit_: " << newOldSpaceLimit_ << " globalSpaceAllocLimit_: " << globalSpaceAllocLimit_ << " globalSpaceNativeLimit_:" << globalSpaceNativeLimit_;`
180
181#### PartialGC的Cset 选择策略
182
183- 函数方法:`OldSpace::SelectCSet()`
184- 说明:PartialGC执行时采用该策略选择存活对象数量少,回收代价小的Region优先进行GC。
185- 典型日志:`Select CSet failure: number is too few`,
186  `"Max evacuation size is 6_MB. The CSet region number: " << selectedRegionNumber;`,
187  `"Select CSet success: number is " << collectRegionSet_.size();`
188
189## SharedHeap
190
191### SharedHeap结构
192
193![image](./figures/gc-shared-heap.png)
194
195- SharedOldSpace:共享老年代空间(这里并不区分年轻代老年代),存放一般的共享对象。
196- SharedHugeObjectSpace:共享大对象空间,使用单独的region存放一个大对象的空间。
197- SharedReadOnlySpace:共享只读空间,存放运行期间的只读数据。
198- SharedNonMovableSpace:共享不可移动空间,存放不可移动的对象。
199
200注:SharedHeap主要用于线程间共享使用的对象,提高效率并节省内存的产物。共享堆并不单独属于某个线程,保存具有共享价值的对象,存活率会更高,去除了SemiSpace的类型。
201
202## 特性
203
204### Smart GC
205
206#### 特性介绍
207
208在应用性能敏感场景,通过将js线程(SmartGC对worker线程和taskpool线程不生效)GC触发水线临时调整到js堆最大值(js线程默认448MB),尽量避免触发GC导致应用掉帧。如果敏感场景持续时间过久,对象分配已经达到了堆最大值,则还是会触发GC,且这次GC由于积累的对象太多,GC时间会相对较久。
209
210#### 支持敏感场景
211
212- 应用冷启动(默认支持)
213- 应用滑动
214- 应用点击页面跳转
215- 超长帧
216
217当前该特性使能由系统侧进行管控,三方应用暂无接口直接调用。
218
219日志关键词: “SmartGC”
220
221#### 交互流程
222
223![image](./figures/gc-smart-feature.png)
224
225标记性能敏感场景,在进入和退出性能敏感场景时,在堆上标记,避免不必要的GC,维持高性能表现。
226
227## 日志解释
228
229### 开启全量日志
230
231默认情况下详细的GC日志仅在GC耗时超过40ms的情况下才会打印,如果需要开启所有GC执行的日志需要使用命令在设备中开启。
232
233**使用样例:**
234
235```shell
236# 设置开启GC全量日志参数,开启参数为0x905d,关闭GC全量日志,设置为默认值为0x105c
237hdc shell param set persist.ark.properties 0x905d
238# 重启生效
239hdc shell reboot
240```
241
242### 典型日志
243
244以下日志为一次GC完整执行后的统计信息,具体到GC的类型不同会有一些差异。开发者可以在导出的日志文件中搜索关键词`[gc]`查看GC相关的日志,也可以查看关键词`ArkCompiler`查看更为全面虚拟机相关的日志。
245
246```
247// GC前对象实际占用大小(region实际占用大小)->GC后对象实际占用大小(region实际占用大小),总耗时(+concurrentMark耗时),GC触发原因。
248C03F00/ArkCompiler: [gc]  [ CompressGC ] 26.1164 (35) -> 7.10049 (10.5) MB, 160.626(+0)ms, Switch to background
249// GC运行时的各种状态以及应用名称
250C03F00/ArkCompiler: [gc] IsInBackground: 1; SensitiveStatus: 0; OnStartupEvent: 0; BundleName: com.huawei.hmos.filemanager;
251// GC运行时的各阶段耗时统计
252C03F00/ArkCompiler: [gc] /***************** GC Duration statistic: ****************/
253C03F00/ArkCompiler: [gc] TotalGC:                 160.626 ms
254C03F00/ArkCompiler: Initialize:              0.179   ms
255C03F00/ArkCompiler: Mark:                    159.204 ms
256C03F00/ArkCompiler: MarkRoots:               6.925   ms
257C03F00/ArkCompiler: ProcessMarkStack:        158.99  ms
258C03F00/ArkCompiler: Sweep:                   0.957   ms
259C03F00/ArkCompiler: Finish:                  0.277   ms
260// GC后各个部分占用的内存大小
261C03F00/ArkCompiler: [gc] /****************** GC Memory statistic: *****************/
262C03F00/ArkCompiler: [gc] AllSpaces        used:  7270.9KB     committed:   10752KB
263C03F00/ArkCompiler: ActiveSemiSpace  used:       0KB     committed:     256KB
264C03F00/ArkCompiler: OldSpace         used:  4966.9KB     committed:    5888KB
265C03F00/ArkCompiler: HugeObjectSpace  used:    2304KB     committed:    2304KB
266C03F00/ArkCompiler: NonMovableSpace  used:       0KB     committed:    2304KB
267C03F00/ArkCompiler: MachineCodeSpace used:       0KB     committed:       0KB
268C03F00/ArkCompiler: HugeMachineCodeSpace used:       0KB     committed:       0KB
269C03F00/ArkCompiler: SnapshotSpace    used:       0KB     committed:       0KB
270C03F00/ArkCompiler: AppSpawnSpace    used: 4736.34KB     committed:    4864KB
271C03F00/ArkCompiler: [gc] Anno memory usage size:  45      MB
272C03F00/ArkCompiler: Native memory usage size:2.99652 MB
273C03F00/ArkCompiler: NativeBindingSize:       0.577148KB
274C03F00/ArkCompiler: ArrayBufferNativeSize:   0.0117188KB
275C03F00/ArkCompiler: RegExpByteCodeNativeSize:0.280273KB
276C03F00/ArkCompiler: ChunkNativeSize:         19096   KB
277C03F00/ArkCompiler: [gc] Heap alive rate:         0.202871
278// 该虚拟机的此类型GC的整体统计
279C03F00/ArkCompiler: [gc] /***************** GC summary statistic: *****************/
280C03F00/ArkCompiler: [gc] CompressGC occurs count  6
281C03F00/ArkCompiler: CompressGC max pause:    2672.33 ms
282C03F00/ArkCompiler: CompressGC min pause:    160.626 ms
283C03F00/ArkCompiler: CompressGC average pause:1076.06 ms
284C03F00/ArkCompiler: Heap average alive rate: 0.635325
285```
286
287- gc类型:[HPP YoungGC]、[HPP OldGC]、[CompressGC]、[SharedGC]。
288- TotalGC: 总耗时。其下相应为各个阶段对应的耗时,基本的包括`Initialize`、`Mark`、`MarkRoots`、`ProcessMarkStack`、`Sweep`、`Finish`,实际根据不同的GC流程不同会有不同的阶段。
289- IsInBackground:是否在后台场景,1:为后台场景,0:非后台场景。
290- SensitiveStatus:是否为敏感场景,1:为敏感场景,0:非敏感场景。
291- OnStartupEvent:是否为冷启动场景,1:为冷启动场景,0:非冷启动场景。
292- used:当前已分配的对象实际占用的内存空间大小。
293- committed:当前实际分配给heap内存空间大小。因为各个空间是按region进行分配的,而region一般也不会被对象完全占满,因此committedSize大于等于usedSize,hugeSpace是会完全相等,因为其一个对象单独占一个region。
294- Anno memory usage size:当前进程所有堆申请的内存大小,包括heap与sharedHeap。
295- Native memory usage size:当前进程所申请的Native内存大小。
296- NativeBindingSize:当前进程堆内对象绑定的Native内存大小。
297- ArrayBufferNativeSize:当前进程申请的数组缓存Native内存大小。
298- RegExpByteCodeNativeSize:当前进程申请的正则表达式字节码Native内存大小。
299- ChunkNativeSize:当前进程申请的ChunkNative内存大小。
300- Heap alive rate:堆内对象的存活率。
301
302## GC开发者调试接口
303
304> **注意:**
305> 以下接口仅供调试使用,非正式对外SDK接口,不应在应用正式版本中使用。
306
307### ArkTools.hintGC()
308
309- 调用方式:`ArkTools.hintGC()`
310- 接口类型:js接口
311- 作用:调用后由VM主动触发判断当前是否适合进行一次full GC。后台场景、内存预期存活率低于设定值,则会触发,判断为敏感状态则不会触发。
312- 使用场景:开发者提示系统进行GC
313- 典型日志:无直接日志,仅可区分外部触发(`GCReason::TRIGGER_BY_JS`)
314
315
316使用参考
317
318```
319// 首先需要声明接口
320declare class ArkTools {
321     static hintGC(): void;
322}
323
324@Entry
325@Component
326struct Index {
327  @State message: string = 'Hello World';
328  build() {
329  Row() {
330    Column() {
331      Text(this.message)
332        .fontSize(50)
333        .fontWeight(FontWeight.Bold)
334      Button("触发HintGC").onClick((event: ClickEvent) => {
335          ArkTools.hintGC();  //方法内直接调用
336      })
337    }
338    .width('100%')
339  }
340  .height('100%')
341}
342}
343```
344
345
346