지난 포스팅에서는 통합 모니터링 시스템인 APM(Application Performance Management)의 4대 핵심 지표를 해석하는 방법을 알아보았습니다. APM은 애플리케이션 내부의 흐름을 시각적으로 보여주는 훌륭한 도구이지만, 가끔은 APM 에이전트 자체가 멈추거나 네트워크 문제로 데이터를 전송하지 못하는 블랙아웃 상황이 발생하기도 합니다.
이럴 때 서버 관리자와 시니어 백엔드 개발자의 진가가 발휘되는 곳이 바로 리눅스(Linux) 터미널 화면입니다. 운영체제(OS) 레벨에서 시스템 리소스가 어떻게 소비되고 있는지 직접 확인하는 것은 모든 트러블슈팅의 가장 확실한 기본기입니다. 이번 글에서는 리눅스 명령어들을 활용하여 Java 프로세스의 CPU, 메모리, I/O 병목을 추적하는 실전 기법을 알아보겠습니다.
1. CPU 병목의 주범 스레드 색출하기 (top과 NID 매핑)
서버의 CPU 사용률이 100%를 치솟고 있을 때 가장 먼저 입력해야 할 명령어는 리눅스의 작업 관리자인 top입니다. 하지만 top만 켜서는 Java 프로세스 하나가 CPU를 몽땅 쓰고 있다는 사실밖에 알 수 없습니다. 더 깊이 들어가야 합니다.
- 스레드별 자원 모니터링:
top화면을 띄운 상태에서Shift + H를 누르면, 프로세스 단위가 아니라 스레드(Thread) 단위로 뷰가 전환됩니다. 여기서 어떤 특정 스레드(PID)가 CPU를 과점하고 있는지 찾아냅니다. (예: PID 12345번 스레드가 CPU 99% 점유) - 16진수 변환: 리눅스 OS가 관리하는 스레드 ID(12345)를 16진수로 변환합니다. 터미널에
printf "%x\n" 12345를 입력하면3039라는 16진수 값이 나옵니다. - 스레드 덤프와 매칭: 이전 포스팅에서 배운
jstack명령어로 Java 스레드 덤프를 추출한 뒤, 덤프 텍스트 파일 안에서nid=0x3039를 검색합니다. - 원인 규명: 검색된 해당 스레드의 콜 스택(Call Stack)을 확인하면, 정확히 Java 코드의 몇 번째 라인에 있는 무한 루프나 정규식 처리 로직이 CPU를 갉아먹고 있는지 100% 확신을 가지고 찾아낼 수 있습니다.
2. 메모리 사용량의 진실: RSS vs VSZ (free, ps)
Java의 힙(Heap) 메모리는 설정한 -Xmx 값 안에서 관리되지만, 실제 Java 프로세스가 리눅스 OS에서 점유하는 물리 메모리는 힙 사이즈보다 항상 훨씬 큽니다. (힙 메모리 + 메타스페이스 + 스레드 스택 + JVM 자체 네이티브 메모리가 합쳐지기 때문입니다.)
- 전체 메모리 현황 (
free -h): 현재 시스템의 남은 메모리를 직관적으로 보여줍니다. 여기서 주의 깊게 볼 항목은Swap영역입니다. 물리 메모리가 부족하여 하드디스크를 메모리처럼 사용하는 Swap 영역의 사용량이 늘고 있다면, 서버는 극심한 성능 저하에 시달리고 있는 것입니다. - 프로세스별 실제 점유율 (
ps -aux | grep java): 프로세스 목록을 확인하면 메모리와 관련된 두 가지 지표가 나옵니다.- VSZ (Virtual Memory Size): 프로세스가 OS로부터 할당받은 가상 메모리의 총량입니다. (실제 사용하지 않는 공간도 포함되므로 과대평가되어 있습니다.)
- RSS (Resident Set Size – 중요): 프로세스가 실제로 물리 메모리(RAM)에 올려서 사용하고 있는 진짜 크기입니다. OOM(OutOfMemory) 킬러에 의해 Java 프로세스가 강제 종료(Killed)되는 원인을 분석할 때는 반드시 이 RSS 수치의 증가 추이를 모니터링해야 합니다.
3. 숨겨진 성능 저하의 주범: I/O 대기 현상 (iostat)
CPU도 넉넉하고 메모리도 충분한데 서버가 느리게 동작한다면 디스크(Disk) 병목을 의심해야 합니다. 특히 데이터베이스 서버이거나 파일 로깅(Logging)이 매우 많은 애플리케이션 서버에서 자주 발생합니다.
- 터미널에
iostat -x 1 10(1초 간격으로 10번 출력) 명령어를 입력합니다. - 출력된 지표 중
%iowait(I/O 대기 시간 비율) 항목을 확인합니다. - 만약 이 수치가 지속적으로 10~20%를 상회한다면, 애플리케이션 스레드들이 하드 디스크에 데이터를 쓰고 읽는 속도를 기다리느라(Blocked) 서버 전체의 응답이 느려지고 있다는 뜻입니다. 이 경우 로깅 수준을 비동기(Async) 로깅으로 변경하거나 디스크를 SSD로 교체하는 등의 인프라적 조치가 필요합니다.
결론: 인프라와 애플리케이션의 경계를 허물다
JVM이라는 가상 머신 위에서 동작하는 Java 애플리케이션이라 할지라도, 결국 그 토대는 리눅스 운영체제입니다. 화려한 APM 그래프 이면에 숨겨진 OS 레벨의 메트릭(CPU, RSS 메모리, %iowait)을 커맨드 라인으로 자유자재로 다룰 수 있다면, 어떤 극한의 장애 상황에서도 당황하지 않는 진정한 전문가로 거듭날 수 있습니다.
모니터링 및 분석 파트는 여기서 마무리합니다. 다음 포스팅부터는 실제 백엔드 개발 실무에서 가장 빈번하게 발생하는 ‘코드 및 인프라 레벨 최적화’ 섹션으로 진입하여, 성능 튜닝의 꽃이라 불리는 ‘데이터베이스 커넥션 풀(HikariCP) 설정 최적화와 성능 튜닝 사례’를 본격적으로 파헤쳐 보겠습니다.