Flutter 튜토리얼 74편: 성능 프로파일링 도구 - DevTools로 앱 성능 분석하기
요약
핵심 요지
- 문제 정의: Flutter 앱에서 UI 버벅임, 높은 CPU 사용량, 메모리 누수 등의 성능 문제가 발생할 수 있다.
- 핵심 주장: DevTools의 성능 분석 도구를 활용하면 문제의 원인을 정확히 파악하고 최적화할 수 있다.
- 주요 근거: Flutter DevTools는 프레임 렌더링, CPU 사용량, 메모리 할당을 시각적으로 분석하는 기능을 제공한다.
- 한계: 프로파일링은 앱 성능에 영향을 주므로 프로덕션 환경과 결과가 다를 수 있다.
문서가 설명하는 범위
- Performance 뷰로 프레임 렌더링 분석
- CPU Profiler로 코드 실행 시간 분석
- Memory 뷰로 메모리 사용량 및 누수 탐지
- 성능 문제 해결을 위한 실전 가이드
읽는 시간: 25분 | 난이도: 중급
참고 자료
- Performance view - Flutter 공식 Performance 뷰 가이드
- CPU Profiler view - Flutter 공식 CPU Profiler 가이드
- Memory view - Flutter 공식 Memory 뷰 가이드
문제 상황
Flutter 앱이 느리게 동작하거나 버벅거릴 때, 문제의 원인을 찾기 어렵습니다. 코드만 보고는 어느 부분이 성능 병목인지 알 수 없습니다.
기존 방식의 한계
// 성능 문제가 있는 코드 - 원인 파악이 어려움class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { // 빌드가 느린 이유는? return ListView.builder( itemCount: items.length, itemBuilder: (context, index) { // 각 아이템 빌드 시간은? return ExpensiveWidget(items[index]); }, ); }}문제는 다음과 같습니다.
- 어떤 위젯이 느린지 알 수 없다
- CPU를 많이 사용하는 코드를 찾기 어렵다
- 메모리 누수 여부를 확인하기 어렵다
- 프레임 드롭의 원인을 파악하기 어렵다
해결 방법
챕터 1: DevTools 성능 분석 개요
Why
NOTE
DevTools1는 Flutter 앱의 성능을 분석하는 공식 도구입니다. 시각적인 타임라인과 그래프로 성능 문제를 쉽게 파악할 수 있습니다.
What
NOTEDevTools의 성능 분석 도구는 세 가지 뷰로 구성됩니다.
flowchart TD subgraph DevTools["DevTools 성능 분석"] P[Performance View] C[CPU Profiler] M[Memory View] end subgraph Analysis["분석 대상"] P --> F[프레임 렌더링] P --> T[타임라인 이벤트] C --> E[코드 실행 시간] C --> CA[호출 스택] M --> H[힙 메모리] M --> L[메모리 누수] end
뷰 분석 대상 사용 시점 Performance 프레임 렌더링, UI/Raster 스레드 화면이 버벅거릴 때 CPU Profiler 메서드 실행 시간, 호출 빈도 앱이 느릴 때 Memory 힙 크기, 객체 할당 메모리 사용량이 높을 때
How
TIPDevTools를 실행하는 방법입니다.
Terminal window # 1. 프로파일 모드로 앱 실행 (성능 분석에 최적)flutter run --profile# 2. DevTools 열기 (터미널에서)flutter pub global activate devtoolsflutter pub global run devtools# 또는 VS Code에서# Dart: Open DevTools 명령 실행// 프로파일 모드에서만 실행되는 코드import 'package:flutter/foundation.dart';if (kProfileMode) {// 프로파일링용 코드debugPrint('Profile mode: measuring performance...');}
Watch out
WARNING성능 분석은 반드시
profile모드에서 해야 합니다.debug모드는 최적화가 없어 실제 성능과 크게 다릅니다.Terminal window # 잘못된 예: debug 모드 (기본값)flutter run # 성능 분석에 부적합# 올바른 예: profile 모드flutter run --profile # 성능 분석에 적합# release 모드는 DevTools 연결 불가flutter run --release # 프로파일링 불가
결론: DevTools의 Performance, CPU Profiler, Memory 뷰를 활용하여 다양한 성능 문제를 분석할 수 있다.
챕터 2: Performance 뷰 - 프레임 분석
Why
NOTEFlutter는 60fps(또는 고주사율 디스플레이에서 120fps)로 화면을 렌더링합니다. 한 프레임을 16ms 내에 완료하지 못하면 화면이 버벅거립니다(
jank2).
What
NOTEPerformance 뷰는 프레임 렌더링 과정을 타임라인으로 보여줍니다.
flowchart LR subgraph Frame["한 프레임 (16ms)"] UI[UI Thread] R[Raster Thread] end subgraph UIWork["UI Thread 작업"] B[build] L[layout] P[paint] end subgraph RasterWork["Raster Thread 작업"] S[rasterize] C[composite] end UI --> UIWork R --> RasterWork UIWork -->|"완료"| RasterWork
스레드 역할 과부하 시 증상 UI Thread Widget 빌드, 레이아웃 계산 사용자 입력 지연 Raster Thread 픽셀 렌더링, GPU 작업 화면 버벅임
How
TIPPerformance 뷰에서 프레임을 분석하는 방법입니다.
// DevTools Performance 뷰 사용 순서// 1. DevTools > Performance 탭 선택// 2. Record 버튼 클릭// 3. 앱에서 버벅거리는 동작 수행// 4. Stop 버튼 클릭// 5. 타임라인에서 빨간색 프레임 확인프레임 차트 해석 방법입니다.
Frame Chart 색상:초록색: 정상 프레임 (16ms 이하)노란색: 약간 느림 (16-33ms)빨간색: jank 발생 (33ms 이상)문제 프레임 분석: 1. 빨간색 프레임 선택2. Frame Analysis 탭에서 상세 정보 확인3. UI vs Raster 시간 비교4. 병목 스레드 파악프레임 분석 예시입니다.
// 느린 빌드를 유발하는 코드 예시class SlowWidget extends StatelessWidget {@overrideWidget build(BuildContext context) {// 문제: build 메서드에서 무거운 연산final processedData = expensiveComputation(); // build에서 호출하면 안 됨return ListView.builder(itemCount: 1000,itemBuilder: (context, index) {return ListTile(title: Text(processedData[index]),);},);}}// 개선된 코드class OptimizedWidget extends StatefulWidget {@overrideState<OptimizedWidget> createState() => _OptimizedWidgetState();}class _OptimizedWidgetState extends State<OptimizedWidget> {late List<String> processedData;@overridevoid initState() {super.initState();// initState에서 한 번만 계산processedData = expensiveComputation();}@overrideWidget build(BuildContext context) {return ListView.builder(itemCount: processedData.length,itemBuilder: (context, index) {return ListTile(title: Text(processedData[index]),);},);}}
Watch out
WARNING
Shader compilation jank3는 첫 실행 시 발생하는 특수한 버벅임입니다. 새로운 셰이더가 컴파일될 때 일시적으로 프레임이 지연됩니다.// Shader 워밍업으로 해결 가능// 앱 시작 시 필요한 셰이더를 미리 컴파일// flutter build 시 셰이더 번들 생성// flutter build apk --bundle-sksl-path flutter_01.sksl.json
결론: Performance 뷰의 프레임 차트로 jank가 발생하는 시점과 원인을 파악할 수 있다.
챕터 3: Performance 뷰 - 타임라인 이벤트
Why
NOTE프레임 차트만으로는 구체적으로 어떤 작업이 느린지 알기 어렵습니다.
Timeline Events4 탭에서 각 프레임의 세부 작업을 분석할 수 있습니다.
What
NOTETimeline Events는 프레임 내의 모든 작업을 계층적으로 보여줍니다.
flowchart TD subgraph Frame["Frame #42"] B[Build] L[Layout] P[Paint] end subgraph BuildDetail["Build 상세"] B1[MyApp.build] B2[HomePage.build] B3[ListView.build] end B --> BuildDetail B1 --> B2 --> B3
이벤트 유형 설명 최적화 대상 Build Widget 트리 구성 불필요한 rebuild Layout 크기/위치 계산 복잡한 레이아웃 Paint 그리기 명령 생성 과도한 클리핑 Raster GPU 렌더링 큰 이미지, 복잡한 효과
How
TIPTimeline Events 분석 방법입니다.
Timeline Events 탭 사용법: 1. Performance 뷰에서 프레임 선택2. Timeline Events 탭 클릭3. 계층 구조에서 가장 긴 이벤트 확인4. 이벤트 클릭하여 소요 시간 확인5. 소스 코드 위치 파악Flame Chart로 시각화된 타임라인입니다.
// Flame Chart 해석// 가로축: 시간// 세로축: 호출 스택 깊이// 막대 너비: 해당 작업의 소요 시간// 넓은 막대 = 오래 걸린 작업 = 최적화 대상// 예: Build 이벤트가 10ms 이상이면// - 어떤 Widget.build가 오래 걸리는지 확인// - 해당 Widget 최적화 필요타임라인 분석을 통한 최적화 예시입니다.
// 문제: Paint가 오래 걸리는 경우class ProblematicWidget extends StatelessWidget {@overrideWidget build(BuildContext context) {return ClipPath(// ClipPath는 성능 비용이 높음clipper: ComplexClipper(),child: Container(decoration: BoxDecoration(// 복잡한 그라데이션도 비용 높음gradient: RadialGradient(colors: [Colors.red, Colors.blue, Colors.green],),boxShadow: [// 그림자도 Paint 비용 증가BoxShadow(blurRadius: 20, spreadRadius: 5),],),child: child,),);}}// 개선: 복잡한 효과 최소화class OptimizedWidget extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container(// 단순한 테두리로 대체decoration: BoxDecoration(color: Colors.blue,borderRadius: BorderRadius.circular(8),),child: child,);}}
Watch out
WARNING
RepaintBoundary5를 무분별하게 사용하지 마세요. 필요한 곳에만 사용해야 효과적입니다.// RepaintBoundary 적절한 사용// 자주 다시 그려지는 위젯을 분리할 때// 좋은 예: 애니메이션이 있는 부분만 분리Stack(children: [StaticBackground(), // 정적인 배경RepaintBoundary(child: AnimatedWidget(), // 자주 변경되는 부분만 분리),],)// 나쁜 예: 모든 위젯에 RepaintBoundary// 오히려 메모리 사용량만 증가
결론: Timeline Events 탭에서 프레임 내 세부 작업을 분석하여 정확한 병목 지점을 찾을 수 있다.
챕터 4: CPU Profiler - 코드 실행 분석
Why
NOTE앱이 전반적으로 느리거나 특정 작업이 오래 걸릴 때, 어떤 코드가 CPU 시간을 많이 사용하는지 파악해야 합니다.
CPU Profiler6는 메서드별 실행 시간을 분석합니다.
What
NOTECPU Profiler는 샘플링 방식으로 코드 실행을 기록합니다.
flowchart TD subgraph Sampling["CPU 샘플링"] S[250μs 간격] C[콜스택 캡처] end subgraph Views["분석 뷰"] BU[Bottom Up] CT[Call Tree] MT[Method Table] FC[Flame Chart] end S --> C --> Views
뷰 설명 사용 시점 Bottom Up 가장 많이 호출된 메서드부터 핫스팟 찾기 Call Tree 호출 계층 구조 호출 경로 추적 Method Table 메서드별 통계 전체 현황 파악 Flame Chart 시간순 시각화 실행 흐름 이해
How
TIPCPU Profiler 사용 방법입니다.
Terminal window # CPU 프로파일링 단계# 1. DevTools > CPU Profiler 탭 선택# 2. Record 버튼 클릭# 3. 앱에서 느린 작업 수행# 4. Stop 버튼 클릭# 5. 결과 분석Bottom Up 뷰 분석입니다.
Bottom Up 뷰 해석:Self Time: 해당 메서드 자체 실행 시간Total Time: 하위 호출 포함 전체 시간정렬 기준:- Self Time 높은 순 → 직접적인 병목- Total Time 높은 순 → 전체적인 영향예시:jsonDecode (Self: 15%, Total: 15%)_parseList (Self: 10%, Total: 10%)→ JSON 파싱이 CPU 시간의 25% 차지Call Tree 분석으로 호출 경로를 추적합니다.
// Call Tree 예시 해석// main() → 100%// └── MyApp.build() → 80%// └── HomePage.build() → 70%// └── _loadData() → 60%// └── jsonDecode() → 50%// → jsonDecode가 병목이지만// 실제 문제는 _loadData가 build에서 호출되는 것// 개선 전class HomePage extends StatelessWidget {@overrideWidget build(BuildContext context) {final data = jsonDecode(rawJson); // build에서 파싱하면 안 됨return DataView(data: data);}}// 개선 후class HomePage extends StatefulWidget {@overrideState<HomePage> createState() => _HomePageState();}class _HomePageState extends State<HomePage> {late Future<Data> _dataFuture;@overridevoid initState() {super.initState();_dataFuture = _loadAndParseData();}Future<Data> _loadAndParseData() async {// 별도 작업으로 분리return compute(jsonDecode, rawJson);}@overrideWidget build(BuildContext context) {return FutureBuilder(future: _dataFuture,builder: (context, snapshot) {if (snapshot.hasData) {return DataView(data: snapshot.data!);}return CircularProgressIndicator();},);}}
Watch out
WARNINGCPU 샘플링 레이트를 조절할 수 있습니다. 높은 레이트는 정확도가 높지만 오버헤드도 증가합니다.
샘플링 레이트 설정:Low (250μs): 기본값, 일반적인 분석에 적합Medium (125μs): 더 정밀한 분석 필요 시High (62.5μs): 매우 짧은 작업 분석 시주의: 높은 레이트는 앱 성능에 영향
결론: CPU Profiler로 가장 많은 CPU 시간을 사용하는 코드를 찾아 최적화할 수 있다.
챕터 5: Memory 뷰 - 메모리 분석 기초
Why
NOTEFlutter 앱의 메모리 사용량이 계속 증가하면 앱이 느려지거나 강제 종료될 수 있습니다.
Memory 뷰7로 메모리 사용 패턴과 누수를 분석합니다.
What
NOTEMemory 뷰는 Dart 힙 메모리를 실시간으로 모니터링합니다.
flowchart LR subgraph Memory["Dart 메모리"] H[Heap] O[Objects] GC[GC] end subgraph Metrics["측정 항목"] U[Used] C[Capacity] E[External] end H --> O O --> GC Memory --> Metrics
메트릭 설명 Used 현재 사용 중인 힙 메모리 Capacity 할당된 힙 크기 External 외부 메모리 (이미지 등) GC 가비지 컬렉션 발생
How
TIPMemory 뷰 기본 사용법입니다.
Memory Chart 읽기:파란색 영역: 사용 중인 힙점선: 힙 용량스파이크: 일시적인 메모리 증가정상 패턴:- 사용량이 증가 → GC 발생 → 감소 반복- 기준선이 일정하게 유지문제 패턴:- 사용량이 계속 증가 (메모리 누수)- GC 후에도 기준선이 높아짐메모리 차트 분석 예시입니다.
// 메모리 누수 패턴 확인// 1. Memory 뷰 열기// 2. 특정 화면 진입/이탈 반복// 3. 메모리 그래프 관찰// 정상: 화면 이탈 후 메모리 감소// 누수: 화면 이탈 후에도 메모리 유지/증가// 누수 의심 코드 예시class LeakyWidget extends StatefulWidget {@overrideState<LeakyWidget> createState() => _LeakyWidgetState();}class _LeakyWidgetState extends State<LeakyWidget> {late StreamSubscription subscription;@overridevoid initState() {super.initState();// 구독 시작subscription = someStream.listen((data) {setState(() {// 데이터 처리});});}// 문제: dispose에서 구독 해제 안 함// Widget이 제거되어도 구독이 유지되어 메모리 누수@overridevoid dispose() {subscription.cancel(); // 이 줄이 없으면 누수!super.dispose();}@overrideWidget build(BuildContext context) {return Container();}}
Watch out
WARNING
BuildContext를 비동기 콜백에서 사용할 때 주의하세요. Widget이 dispose된 후에도 콜백이 실행되면 문제가 됩니다.// 문제: async 콜백에서 context 사용class ProblematicWidget extends StatefulWidget {@overrideState<ProblematicWidget> createState() => _ProblematicWidgetState();}class _ProblematicWidgetState extends State<ProblematicWidget> {Future<void> loadData() async {final data = await fetchData();// Widget이 이미 dispose 되었을 수 있음!Navigator.of(context).push(...);}@overrideWidget build(BuildContext context) {return ElevatedButton(onPressed: loadData,child: Text('Load'),);}}// 해결: mounted 체크class SafeWidget extends StatefulWidget {@overrideState<SafeWidget> createState() => _SafeWidgetState();}class _SafeWidgetState extends State<SafeWidget> {Future<void> loadData() async {final data = await fetchData();// mounted 체크if (!mounted) return;Navigator.of(context).push(...);}@overrideWidget build(BuildContext context) {return ElevatedButton(onPressed: loadData,child: Text('Load'),);}}
결론: Memory 뷰의 실시간 차트로 메모리 사용 패턴을 모니터링하고 누수를 탐지할 수 있다.
챕터 6: Memory 뷰 - 스냅샷 비교
Why
NOTE메모리 차트에서 누수가 의심되면, 구체적으로 어떤 객체가 메모리에 남아있는지 확인해야 합니다.
힙 스냅샷8을 비교하면 누수된 객체를 찾을 수 있습니다.
What
NOTEDiff Snapshots 기능으로 두 시점의 메모리 상태를 비교합니다.
flowchart LR subgraph Before["스냅샷 1"] A1[화면 진입 전] end subgraph After["스냅샷 2"] A2[화면 이탈 후] end subgraph Diff["차이 분석"] D[새로 생성된 객체] R[유지된 객체] end Before --> Diff After --> Diff Diff --> D Diff --> R
용어 설명 Shallow Size 객체 자체의 크기 Retained Size 객체 + 참조하는 객체들의 크기 Instances 해당 클래스의 인스턴스 수
How
TIPDiff Snapshots 사용 방법입니다.
스냅샷 비교 절차:1. 앱을 초기 상태로 만듦2. Memory 뷰에서 "Take Heap Snapshot" 클릭 (스냅샷 1)3. 의심되는 작업 수행 (예: 화면 진입/이탈)4. "Take Heap Snapshot" 다시 클릭 (스냅샷 2)5. "Diff Snapshots" 탭에서 비교6. 인스턴스 수가 증가한 클래스 확인누수 객체 찾기 예시입니다.
// Diff Snapshots 결과 해석//// Class Name | Instances | Shallow Size// ---------------------------------------------------// _MyWidgetState | +5 | +2,048 bytes// StreamSubscription | +5 | +1,280 bytes// Timer | +5 | +960 bytes//// → 화면을 5번 진입/이탈했는데 State가 5개 남아있음// → StreamSubscription과 Timer도 함께 누수// 원인: dispose에서 정리 안 함class _MyWidgetState extends State<MyWidget> {late Timer _timer;late StreamSubscription _subscription;@overridevoid initState() {super.initState();_timer = Timer.periodic(Duration(seconds: 1), (_) {// 주기적 작업});_subscription = stream.listen((data) {// 데이터 처리});}@overridevoid dispose() {_timer.cancel(); // 타이머 취소_subscription.cancel(); // 구독 취소super.dispose();}@overrideWidget build(BuildContext context) => Container();}Trace Instances 기능으로 객체 참조 경로를 추적합니다.
Trace Instances 사용: 1. 누수된 클래스 선택2. "Trace Instances" 체크3. 새 스냅샷 캡처4. 해당 인스턴스의 참조 경로 확인5. 어디서 참조를 유지하는지 파악
Watch out
WARNING힙 스냅샷 캡처는 앱을 잠시 멈추게 합니다. 대규모 앱에서는 몇 초가 걸릴 수 있습니다.
// 스냅샷 캡처 시 주의사항// - 앱이 일시 정지됨// - 메모리가 많을수록 오래 걸림// - 분석 중에는 앱을 조작하지 말 것
결론: Diff Snapshots으로 두 시점의 메모리를 비교하여 누수된 객체를 정확히 찾을 수 있다.
챕터 7: 실전 성능 최적화 팁
Why
NOTE프로파일링 도구로 문제를 찾았다면 실제로 최적화해야 합니다. Flutter 앱에서 자주 발생하는 성능 문제와 해결 방법을 알아봅니다.
What
NOTEFlutter 성능 최적화의 주요 영역입니다.
flowchart TD subgraph Optimization["최적화 영역"] B[Build 최적화] R[Render 최적화] M[Memory 최적화] A[Async 최적화] end subgraph Techniques["기법"] B --> B1[const 생성자] B --> B2[Widget 분리] R --> R1[RepaintBoundary] R --> R2[이미지 캐싱] M --> M1[dispose 정리] M --> M2[약한 참조] A --> A1[compute] A --> A2[Isolate] end
How
TIP주요 최적화 기법들입니다.
1. Build 최적화
// const 생성자 사용class MyWidget extends StatelessWidget {const MyWidget({super.key}); // const 생성자@overrideWidget build(BuildContext context) {return Column(children: const [Icon(Icons.star), // constText('Hello'), // constSizedBox(height: 10), // const],);}}// Widget 분리로 rebuild 범위 최소화class ParentWidget extends StatefulWidget {@overrideState<ParentWidget> createState() => _ParentWidgetState();}class _ParentWidgetState extends State<ParentWidget> {int counter = 0;@overrideWidget build(BuildContext context) {return Column(children: [// ExpensiveWidget은 rebuild 안 됨const ExpensiveWidget(),// CounterWidget만 rebuildCounterWidget(count: counter),ElevatedButton(onPressed: () => setState(() => counter++),child: const Text('Increment'),),],);}}2. 리스트 최적화
// ListView.builder 사용 (필수!)// 보이는 항목만 빌드ListView.builder(itemCount: 10000,itemBuilder: (context, index) {return ListTile(title: Text('Item $index'));},)// itemExtent 지정으로 성능 향상ListView.builder(itemCount: 10000,itemExtent: 56.0, // 고정 높이itemBuilder: (context, index) {return ListTile(title: Text('Item $index'));},)3. 무거운 연산 분리
// compute 함수로 Isolate에서 실행Future<List<Item>> loadAndParseData() async {final rawData = await fetchRawData();// 메인 스레드가 아닌 별도 Isolate에서 파싱final items = await compute(parseData, rawData);return items;}List<Item> parseData(String rawData) {// 무거운 JSON 파싱final json = jsonDecode(rawData);return (json as List).map((e) => Item.fromJson(e)).toList();}4. 이미지 최적화
// 적절한 크기로 이미지 로드Image.network(imageUrl,cacheWidth: 200, // 캐시 크기 제한cacheHeight: 200,)// 이미지 캐싱CachedNetworkImage(imageUrl: imageUrl,placeholder: (context, url) => CircularProgressIndicator(),errorWidget: (context, url, error) => Icon(Icons.error),)
Watch out
WARNING과도한 최적화는 오히려 해로울 수 있습니다. 항상 프로파일링으로 문제를 확인한 후 최적화하세요.
// 시기상조 최적화 피하기// 문제가 확인되기 전까지는 단순하게 유지// 나쁜 예: 모든 곳에 const, RepaintBoundary 추가// - 코드 복잡성 증가// - 실제 효과 미미할 수 있음// 좋은 예: 프로파일링 → 병목 확인 → 해당 부분만 최적화
결론: 프로파일링으로 찾은 병목에 적절한 최적화 기법을 적용하여 성능을 개선한다.
한계
- 프로파일 모드 차이: 프로파일 모드와 릴리스 모드의 성능이 다를 수 있다
- 오버헤드: 프로파일링 자체가 앱 성능에 영향을 준다
- 플랫폼 차이: iOS와 Android에서 성능 특성이 다르므로 각각 테스트해야 한다
- 실제 기기 필요: 에뮬레이터/시뮬레이터의 성능은 실제 기기와 다르다
Footnotes
-
DevTools(데브툴즈): Flutter와 Dart 앱을 분석하고 디버깅하는 공식 도구 모음이다. ↩
-
Jank(쟁크): 프레임 렌더링이 지연되어 화면이 버벅거리는 현상이다. ↩
-
Shader Compilation(셰이더 컴파일): GPU가 그래픽을 렌더링하기 위해 셰이더 프로그램을 컴파일하는 과정이다. ↩
-
Timeline Events(타임라인 이벤트): 프레임 렌더링 과정에서 발생하는 개별 작업들이다. ↩
-
RepaintBoundary(리페인트 바운더리): 하위 위젯의 repaint를 상위로 전파하지 않도록 분리하는 위젯이다. ↩
-
CPU Profiler(CPU 프로파일러): 코드 실행 시간과 호출 빈도를 분석하는 도구이다. ↩
-
Memory View(메모리 뷰): 앱의 메모리 사용량과 할당을 분석하는 도구이다. ↩
-
Heap Snapshot(힙 스냅샷): 특정 시점의 힙 메모리 상태를 캡처한 것이다. ↩
공유
이 글이 도움이 되었다면 다른 사람과 공유해주세요!