본문 바로가기
개발 환경 & 생산성 도구

인텔리제이 디버깅 마스터: 기도 매매 끝내는 브레이크포인트 실무 활용 가이드

by CodeByJin 2026. 5. 21.
반응형

수천 개짜리 루프가 도는 배치 프로세스나 동시성 요청이 몰리는 멀티스레드 환경에서 버그가 터졌을 때, 아직도 코드 라인 왼쪽에 빨간 점 하나 찍고 무작정 F9(Resume)만 연타하고 계신가요? 이건 디버깅이 아니라 '기도 매매'와 다를 바 없습니다. 멈추지 말아야 할 곳에서 수백 번씩 멈추는 브레이크포인트는 개발자의 집중력을 파괴하고 흐름을 끊는 주범입니다. 인텔리제이가 제공하는 디버깅 툴셋을 제대로 손에 익히지 못하면, 시니어 레벨로 올라갈수록 복잡해지는 동시성 이슈나 대용량 데이터 정합성 문제를 해결할 때 며칠씩 밤을 새우는 고통을 겪게 됩니다.

의심스러운 녀석만 골라낸다: 조건부 브레이크포인트와 필터링

실무에서 가장 멍청한 짓 중 하나가 1만 건의 리스트를 처리하는 루프 중간에 일반 브레이크포인트를 걸어두는 겁니다. 내가 원하는 건 9999번째에서 터지는 NullPointerException인데, 1번째부터 9998번째까지 일일이 F9를 누르며 손가락 노동을 할 수는 없는 노릇이니까요.

이때 구원투수가 되는 것이 바로 조건부 브레이크포인트(Conditional Breakpoint)입니다. 방법은 간단합니다. 라인 마커에 빨간 점을 찍은 뒤, 마우스 우클릭을 하면 나타나는 Condition 입력창에 자바 문법 그대로 표현식을 집어넣으면 됩니다.

// Condition 창에 입력하는 예시
user.getId() == 1001 && "VIP".equals(user.getGrade())

이렇게 설정하면 인텔리제이는 해당 조건이 true가 될 때만 스레드를 멈춰 세웁니다. 하지만 여기서 간과하지 말아야 할 치명적인 트레이드오프가 있습니다. 이 조건식은 컴파일러가 아니라 인텔리제이가 런타임에 추상 구문 트리(AST)를 해석하거나 Expression 평가기를 돌리는 방식이라, 루프가 돌 때마다 엄청난 성능 오버헤드가 발생합니다. 수십만 건의 대용량 루프에 복잡한 조건부 브레이크포인트를 걸어두면 애플리케이션 자체가 멈춘 것처럼 느려지다가 OOM(Out Of Memory)을 뱉고 뻗어버릴 수도 있습니다. 조건식은 최대한 가볍게, 기본 타입 비교나 단순 Getter 호출 수준으로 압축하는 것이 실무 팁입니다.

스레드를 멈추지 않고 흔적만 남기는 '비정지 로그' 활용법

가끔은 프로세스를 멈추고 싶지 않을 때가 있습니다. 특히 타임아웃이 빡빡하게 걸린 외부 시스템 연동 구간이나, 내가 스레드를 멈추는 순간 데드락이 풀려버리는 기묘한 동시성 버그를 잡을 때가 그렇습니다. 그렇다고 디버깅용 System.out.println()이나 로그 코드를 새로 넣고 빌드·배포를 다시 하는 건 너무나도 레거시한 방식입니다. Git 스태시(Stash) 관리도 귀찮아집니다.

 

이 고통을 해결해 주는 기능이 바로 'Suspend 옵션 해제''Log evaluated expression'의 조합입니다.

  1. 브레이크포인트를 우클릭한 뒤, More (Ctrl+Shift+F8)를 누릅니다.
  2. 가장 상단에 있는 Suspend 체크박스를 과감하게 해제합니다. 이제 이 포인트는 통과해도 프로그램이 멈추지 않습니다.
  3. Evaluate and log 체크박스를 켜고, 콘솔에 찍고 싶은 변수나 식을 작성합니다. (예: "Current Index: " + i + ", Status: " + item.getStatus())

이렇게 해두면 애플리케이션은 중단 없이 최고 속도로 달리면서, 인텔리제이의 Debug 콘솔창에 실시간으로 지정한 값들을 뚝뚝 떨어뜨려 줍니다. 코드를 단 한 줄도 수정하지 않고도 동적으로 커스텀 로그 시스템을 구축하는 셈입니다. 운영 중인 로컬 톰캣이나 도커 컨테이너에 리모트 디버깅(Remote Debugging)으로 붙어서 상태를 모니터링할 때 이보다 강력한 무기는 없습니다.

인텔리제이 디버깅 브레이크포인트 설정 - 생성형 ai 이미지

Q. "Hit count" 기능은 정확히 어떤 상황에 써야 작업 시간이 줄어드나요?

이 질문은 주니어들이 참 많이 물어봅니다. 결론부터 말하면, 데이터의 상태가 아니라 '반복의 횟수' 자체가 트리거가 되는 규칙성 버그를 잡을 때 작업 시간을 수십 배 아껴줍니다.

 

가장 흔한 실무 예시로 '페이징 및 청크(Chunk) 처리 로직'이 있습니다. 대량 데이터를 100개씩 잘라서 배치 처리를 돌리는데, 꼭 5번째 청크(즉, 500번째 아이템)가 넘어가는 시점에 알 수 없는 포인터 예외나 수식 오류가 터진다고 가정해 봅시다. 이때 특정 데이터의 ID값을 모른다면 조건식을 걸기 애매합니다.

 

이때 브레이크포인트 속성에서 Hit count를 활성화하고 값을 500으로 설정하면, 앞선 499번의 루프 패스는 인텔리제이가 자바 가상머신(JVM) 레벨에서 무시해 버리고 딱 500번째 도달했을 때 정확하게 스레드를 홀딩합니다. 내부 카운터 장치를 쓰는 구조라 일반 조건부 브레이크포인트보다 연산 비용이 훨씬 저렴하다는 것도 큰 장점입니다.

멀티스레드 환경의 덫: All vs Thread 중단 옵션의 비밀

인텔리제이 기본 설정 상태로 디버깅을 하다가 서버 전체가 먹통이 되거나 타임아웃 예외가 연쇄적으로 터진 경험이 있다면, 100% 이 옵션을 몰라서 당한 겁니다. 브레이크포인트의 우클릭 메뉴를 보면 Suspend 옆에 라디오 버튼으로 AllThread가 있는 것을 볼 수 있습니다.

구분 동작 방식 실무 추천 상황 주의점
All (기본값) 브레이크포인트에 걸리는 순간, JVM 내부의 모든 스레드를 멈춤. 단일 스레드 테스트, 상태 변화가 없는 순수 로직 검증. 외부 헬스체크 핑, 커넥션 풀 킵얼라이브 스레드까지 다 멈춰서 커넥션이 끊길 수 있음.
Thread 해당 코드를 실행한 ‘그 스레드’만 멈추고, 나머지는 정상 기동함. 웹 서버(Spring MVC), 멀티스레드 배치, 비동기(CompletableFuture) 디버깅. 다른 스레드가 공유 자원을 먼저 수정해 버리는 레이스 컨디션이 발생할 수 있음.

이건 직접 운영해 보면 체감이 확 됩니다. 스프링 부트로 API 서버를 띄워놓고 디버깅을 할 때 All로 두면, 내가 코드 하나를 붙잡고 분석하는 동안 아키텍처 내부의 다른 워커 스레드나 톰캣의 내부 관리 스레드까지 전부 얼어붙습니다. 결국 프론트엔드나 앱 클라이언트 쪽에서는 "서버 연결 끊어짐" 에러를 뱉게 되죠. 가급적 인텔리제이 환경 설정(Settings -> Build, Execution, Deployment -> Debugger)에서 기본 Suspend 정책을 Thread로 변경해 두고 쓰는 것을 강력히 권장합니다.

단축키 손에 익히기: 흐름 제어 마스터리

브레이크포인트에 이쁘게 안착했다면, 다음 단계는 '현란한 무빙'입니다. 마우스로 디버그 탭의 화살표 아이콘을 꾹꾹 누르고 있다면 아직 갈 길이 멉니다. 아래 4가지 단축키는 뇌를 거치지 않고 손가락이 먼저 반응해야 합니다.

  • Step Over (F8): 현재 줄을 실행하고 바로 다음 줄로 내려갑니다. 메서드 호출이 있어도 내부로 들어가지 않고 결과만 챙깁니다.
  • Step Into (F7): 현재 줄에 있는 메서드 내부로 비집고 들어갑니다. 내가 짠 핵심 비즈니스 로직의 깊은 속내를 봐야 할 때 씁니다. 만약 JDK 기본 클래스(예: String.substring()) 내부까지 들어가서 길을 잃었다면 Smart Step Into (Shift+F7)를 써서 타겟 메서드만 골라 들어가세요.
  • Step Out (Shift+F8): 메서드 내부에 들어왔는데 "아, 여기선 볼일 끝났다" 싶을 때 씁니다. 현재 메서드의 남은 라인을 싹 실행하고 나를 호출했던 부모 메서드 위치로 튕겨 올려줍니다.
  • Run to Cursor (Alt+F9): 중간에 수많은 코드가 있지만 다 건너뛰고, 마우스 커서가 위치한 라인까지 한 방에 실행해 버립니다. 임시 브레이크포인트를 찍었다가 지우는 귀찮은 짓을 완벽히 대체합니다.

복잡한 객체 스캔: Watch와 Evaluate Expression의 한계 극복

디버거 하단의 Variables 탭은 현재 스코프의 모든 객체를 트리 구조로 보여주지만, 뎁스가 깊은 엔티티나 거대한 Map 구조를 들여다볼 때는 마우스 클릭을 수십 번 해야 하는 지옥이 펼쳐집니다. 이때는 Evaluate Expression (Alt + F8)을 적극적으로 찢어서 써야 합니다.

 

단순히 변수 이름만 쳐서 값을 보는 것에 그치지 말고, 아예 그 자리에서 스트림 API를 날리거나 가공 코드를 실행해 보세요. 예를 들어, 회원 리스트 중에서 활성화 상태인 멤버의 이메일만 추출해서 보고 싶다면 식을 다음과 같이 찌르는 겁니다.

users.stream().filter(User::isActive).map(User::getEmail).toList()

단, 여기서 주의할 점이 있습니다. Evaluate 창에서 실행하는 코드는 자바의 메모리와 영속성 컨텍스트 상태를 실제로 변경할 수 있습니다. 만약 식 내부에서 DB 데이터를 수정하는 메서드를 호출하거나, 리스트의 요소를 clear() 해버리면 남은 애플리케이션 흐름이 완전히 왜곡됩니다. 디버깅 창에서 값을 검증하려다 진짜 버그를 창조해 내는 불상사가 생길 수 있으니, 무조건 'Read-Only' 관점의 수식만 던지는 습관을 들여야 합니다.

결국 어떤 선택을 해야 하는가?

디버깅 생산성을 바꾸는 핵심 터닝 포인트를 정리하면 결국 다음과 같은 상황별 무기 선택으로 귀결됩니다.

  • 특정 식별자나 타겟 데이터의 오동작을 추적할 때는 오버헤드를 감수하더라도 조건부 브레이크포인트를 좁게 건다.
  • 동시성, 레이스 컨디션, 외부 시스템 연동 중단이 우려될 때는 Suspend를 끄고 Evaluate and log로 무중단 모니터링을 수행한다.
  • 웹 아키텍처 상에서 디버깅 때문에 다른 스레드가 차단되는 부작용을 막으려면 기본 Suspend 옵션을 반드시 All에서 Thread로 전환한다.
  • 작업이 끝난 후 코드를 커밋하기 전에는 반드시 Ctrl + Shift + F8 (View Breakpoints)을 열어 내가 여기저기 흩뿌려놓은 빨간 점들을 일괄 수거(Remove)한다.

코드를 잘 짜는 것만큼이나 이미 돌아가고 있는 코드의 런타임 상태를 완벽하게 통제하는 능력도 시니어 개발자의 핵심 덕목입니다. 인텔리제이가 제공하는 강력한 디버거 제어판을 단순 '일시정지' 버튼으로만 소모하지 마시고, 오늘 당장 실무 프로젝트에 이 테크닉들을 녹여보시길 바랍니다. 밤샘 디버깅 시간이 획기적으로 줄어들 것입니다.

 

 

초보 개발자를 위한 인텔리제이(IntelliJ) 필수 초기 설정 10단계: 야근을 줄이는 시니어의 IDE 최적

인텔리제이(IntelliJ IDEA)를 처음 설치하고 기본 설정 상태 그대로 코딩을 시작하는 개발자들을 볼 때마다 솔직히 마음이 답답해집니다. 순정 상태의 인텔리제이는 겉보기엔 화려하지만, 실무 트

byteandbit.tistory.com

 

반응형