크롬 힙 스냅샷, 작은 객체의 Retained Size가 큰 이유
화면에서 지운 요소가 메모리에 남아 있는데, 작은 객체 옆 Retained Size에 유난히 큰 숫자가 붙어 있다. 이 객체는 무엇을 붙잡고 있길래 이렇게 클까?

- 작은 객체도 다른 객체의 회수를 막으면 큰 Retained Size를 가질 수 있다.
- 생성자 그룹 행에서 Shallow Size는 합계, Retained Size는 최댓값이다.
- 누수는 동작 전후의 스냅샷을 비교한 뒤 불필요한 객체를 붙잡는 참조까지 따라가며 찾는다.
객체 크기와 그 객체 때문에 남는 메모리는 다르다
크롬 힙 스냅샷에서 Shallow Size가 작은데 Retained Size가 크다면, 그 객체가 다른 메모리의 회수를 막고 있을 수 있다. Shallow Size는 객체 자체가 차지하는 크기, Retained Size는 그 객체와 함께 회수할 수 있는 메모리의 크기다. Chrome의 메모리 용어 문서는 작은 객체도 참조를 통해 큰 메모리를 유지할 수 있다고 설명한다.
Retained Size를 그 객체에서 연결되는 모든 객체의 크기를 더한 값으로 읽으면 안 된다. 다른 살아 있는 경로에서도 참조하는 객체는 해당 객체 하나를 없애도 회수되지 않는다. 핵심은 함께 도달 불가능해지는 메모리다.
큰 값 자체가 누수 판정은 아니다. 앱이 계속 쓸 데이터를 유지하는 것과, 이미 끝난 작업의 데이터를 놓지 않는 것을 구분해야 한다.
| 보는 행 | Shallow Size | Retained Size |
|---|---|---|
| 개별 객체 | 객체 자체의 크기다. | 그 객체와 함께 회수할 수 있는 크기다. |
| Summary의 생성자 그룹 | 해당 객체들의 합계다. | 해당 객체들 중 최댓값이다. |
그룹 행의 두 숫자는 집계 방식도 다르다. Summary 설명에 따라 그룹을 펼쳐 개별 인스턴스를 봐야 한다.
같은 작업을 하고 되돌린 전후를 비교한다
누수 원인을 좁히려면 Memory → Heap snapshot에서 작업 전 스냅샷을 찍는다. 의심하는 동작과 역동작을 몇 차례 수행한 뒤 두 번째 스냅샷을 찍고, Comparison에서 첫 스냅샷과 비교한다. 공식 가이드가 드는 예는 문서를 열었다가 닫는 동작이다.
웹앱이라면 상세 화면을 열고 닫는 과정을 같은 조건으로 반복하는 데 적용할 수 있다. 예상과 달리 남은 객체를 골라 참조를 조사한다. 스냅샷 수집은 가비지 컬렉션으로 시작하므로, 단순히 한때 할당됐던 객체와 수집 뒤에도 남은 객체를 혼동하지 않아야 한다.
화면에서 지웠어도 JavaScript가 붙잡을 수 있다
선택한 객체를 가리키는 다른 객체는 Memory 하단의 Retainers에서 찾는다. 특히 detached DOM 사례처럼 화면 트리에서 제거된 노드를 JavaScript가 계속 참조할 수 있다. Class filter에 Detached를 입력해 관련 노드를 좁힌 다음, 더 이상 필요 없는 노드를 어느 코드가 유지하는지 살핀다.
수정할 대상은 무조건 가장 큰 객체가 아니라 불필요한 참조를 계속 보유하는 코드다. 참조를 정리한 뒤 같은 동작으로 다시 비교해야 수정 효과를 판단할 수 있다. Console이나 디버거도 객체를 유지할 수 있으므로, 메모리 용어 가이드는 Console을 비우고 활성 중단점 없이 스냅샷을 만들도록 권한다.
새 필터는 참조를 좁히는 데 쓴다
Chrome 153·154의 Memory 개선은 힙 스냅샷의 객체 간 참조를 최소 Retained Size로 필터링하고, null·undefined 같은 원시 값을 제외하는 기능을 포함한다. 참조를 크기나 이름으로 정렬하고, V8 실행 컨텍스트가 유지하는 메모리도 살필 수 있다.
참조가 많은 객체를 펼쳤을 때 조사할 대상을 줄이는 데 유용하다. 필터를 통과한 큰 참조가 곧 버그인 것은 아니다. 크기로 후보를 좁히고, 비교로 불필요하게 남은 객체를 찾고, 참조를 따라 원인을 고친다. 이 순서가 작은 객체 옆의 큰 숫자를 실제 수정으로 연결한다.