RSS
프론트엔드

iOS 사파리 탭이 반복해서 죽을 때: 폰 시스템 로그로 WebContent 메모리 읽기

작성자
듀오랩스 대표·7분 읽기

아이폰에서 랜딩 페이지 하나를 가로로 눕혀 스크롤하면 탭이 죽었습니다. 사파리는 「문제가 반복적으로 발생했습니다」를 띄웠고, 크롬은 빈 화면을 남겼습니다. 세로로는 멀쩡했고, 가로에서도 가끔 죽었으며, 크게 확대하면 더 잘 죽었습니다. 서버는 200을 돌려주고 있었습니다.

이런 증상은 원인 후보가 둘로 갈립니다. WebKit이 특정 CSS나 기능에서 실제로 충돌하는 경우와, 페이지가 메모리 한도를 넘어 강제로 끝나는 경우입니다. 맥에서 아이폰 화면을 흉내 내 레이어 크기를 어림해 봤지만, 그 숫자로는 어느 쪽인지 가를 수 없었습니다. 그래서 폰이 남긴 기록을 직접 읽었습니다.

충돌 로그로 먼저 가르기

폰이 맥과 짝지어져 있으면 Xcode의 devicectl로 충돌 로그를 가져올 수 있습니다.

xcrun devicectl list devices
xcrun devicectl device copy from --device <UDID> \
  --domain-type systemCrashLogs --source / --destination ./crashlogs

처음에는 「The device is currently locked」로 실패했습니다. 폰 잠금을 풀어 두면 받아집니다.

볼 파일은 둘입니다. WebKit이 정말 충돌했다면 com.apple.WebKit.WebContent-….ips가, 시스템 전체 메모리가 모자랐다면 JetsamEvent-….ips가 남습니다. 받은 127개 중에 그날 밤 것은 둘 다 없었습니다.

없다는 것도 단서였습니다. 페이지를 그리는 WebContent 프로세스가 자기 메모리 한도를 넘어 끝나면 충돌 파일이 남지 않고 시스템 로그에만 기록됩니다.

폰의 시스템 로그를 맥으로 가져오는 방법

맥의 log collect가 연결된 폰의 로그를 아카이브로 떠 옵니다.

sudo /usr/bin/log collect --device-udid <UDID> --last 60m --output ./phone.logarchive

두 가지에 걸렸습니다. zsh에는 log라는 내장 명령이 있어서 /usr/bin/log로 전체 경로를 써야 합니다. 또 root 권한이 필요한데, 제 환경에서는 비밀번호를 입력할 터미널이 없어 sudo가 실패했습니다. 그래서 osascript -e 'do shell script "…" with administrator privileges'로 돌려 맥의 비밀번호 창을 띄웠습니다. 한 시간 분량이 432MB였습니다.

걸러 내는 조건

처음에는 jetsam, memory, terminat 같은 낱말로 넓게 걸렀습니다. 그랬더니 위젯과 시스템 서비스의 메모리 설정 줄이 수백 개 쏟아져 정작 필요한 줄이 묻혔습니다. 웹 페이지를 띄운 앱의 줄만 남기고, WebKit이 메모리에 대해 남기는 메시지 넷으로 좁히니 원하는 것이 나왔습니다.

/usr/bin/log show phone.logarchive --style compact --start "2026-10-27 23:30:00" \
  --predicate '(process == "MobileSafari" OR process == "Chrome") AND
    (eventMessage CONTAINS "phys_footprint_mb"
     OR eventMessage CONTAINS "measurePostLoadMemoryUsage"
     OR eventMessage CONTAINS "Received memory pressure event"
     OR eventMessage CONTAINS "processDidTerminate:")'

iOS의 크롬도 WebKit 위에서 돌기 때문에 같은 메시지가 남습니다.

로그가 보여 준 숫자

WebKit은 페이지 로드를 시작할 때, 로드가 끝난 뒤, 메모리 경고를 받을 때마다 WebContent 프로세스의 phys_footprint_mb를 남깁니다. 한 번 죽을 때를 시간순으로 놓으면 이렇습니다.

시각 phys_footprint
페이지 로드 시작 629MB
로드 10초 뒤 573MB
메모리 경고 1,864MB
2초 뒤 processDidTerminate: reason=Crash

30분 동안 같은 일이 열 번 넘게 반복됐습니다. 끝나는 지점은 매번 1,647MB에서 1,893MB 사이였습니다. 로그에는 reason=Crash라고 찍히지만 충돌 파일이 없으니, 이 근처가 이 기기에서 탭 하나가 쓸 수 있는 한도로 보입니다. 첫 질문의 답은 메모리 쪽이었습니다.

가장 많은 것을 알려 준 것은 다시 열린 직후의 기록이었습니다. 탭이 죽으면 브라우저가 같은 위치로 페이지를 다시 엽니다. 이때 631MB이던 프로세스가 1초 뒤 1,521MB가 됐습니다. 확대도 스크롤도 하지 않고 그 위치를 그리기만 했는데 한도 근처까지 찼습니다. 확대는 마지막 한 방이었을 뿐이고, 페이지가 원래 한도 바로 밑에서 돌고 있었습니다.

1.8GB 중 160MB만 보이는 이유

메모리 경고 때 WebKit은 VM 태그별 내역도 남깁니다.

phys_footprint_mb: 1864
javascript_gc_heap_size_mb: 15
bmalloc: 128 MB
malloc (small): 11 MB

합해도 160MB 정도이고, 나머지 1.7GB는 목록에 나오지 않습니다. 레이어를 그려 두는 그래픽 버퍼(IOSurface)는 프로세스 안의 일반 메모리가 아니라 태그 목록에서는 빠지지만, 그 프로세스의 footprint에는 들어갑니다. 스크립트가 아니라 그리는 쪽이 메모리를 쓴다는 뜻입니다. 이 구분이 다음에 볼 곳을 정합니다. JS 힙이나 DOM 노드를 줄여서는 풀리지 않는 문제입니다.

로그를 잘못 읽은 한 번

같은 30초 동안 resourceID가 6,360에서 12,802로 뛰어 있었습니다. 20초에 요청이 6천 건 나간 줄 알고 초 단위로 셌습니다. 실제로 기록된 요청은 52건이었습니다. 이 번호는 요청이 아닌 것에도 매겨지는 카운터였습니다. 로그에서 숫자가 크게 뛰면, 그 숫자가 무엇을 세는지부터 확인해야 합니다.

이 로그로 알 수 있는 것과 없는 것

이 방법으로 가른 것은 셋입니다. WebKit 충돌이 아니라 메모리 한도 초과라는 것, 한도가 이 기기에서 1.7~1.9GB 근처라는 것, 그 대부분이 그래픽 메모리라는 것입니다. 맥에서 흉내 내 어림한 숫자(약 150MB)가 실제보다 열 배쯤 작았다는 것도 이 로그로 알았습니다.

어느 레이어가 그 메모리를 쓰는지는 이 로그에 나오지 않습니다. 그건 맥 사파리의 웹 속성 창을 폰 사파리에 붙여, 레이어 탭에서 레이어별로 봐야 합니다. 시스템 로그는 무엇을 볼지 정해 주고, 레이어 탭은 그중 어느 것인지 짚어 줍니다.

우리 서비스에 적용할 계획이 있으신가요?

홈페이지·웹서비스 개발 살펴보기 →

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.