# Chapter 5. Optimization Practice

## 사례 분석

## 1. 대용량 메모리 기기 대상 배포 전략

하루 15만건의 페이지 뷰 웹사이트

### 개요

* JDK5

기존:

* 32비트 운영체제
* 1.5GB 힙 메모리

변경:

* 64비트 운영체제
* 16GB 힙메모리
* -Xms를 통한 힙크기 12GB 고정

### 문제

하루 페이지 뷰가 15만 정도되는 웹사이트에서 서버 실행 효율이 기대 이하이고, 웹 사이트가 장시간 응답하지 않는 상황이 자주 발생.

### 원인

가비지 컬렉션에서 원인을 찾음.

* 페러랠 컬렉터
  * Young: 패러렐 스캐빈지
  * Old: 패러렐 올드

<details>

<summary>패러렐 스캐빈지</summary>

#### ✅ 장점

* **다중 스레드를 활용한 빠른 Young GC 처리**\
  → Young Generation에서 객체가 자주 GC 되므로, 빠른 처리가 전체 성능에 영향 큼.
* **Throughput(처리량) 최적화에 초점**\
  → `-XX:+UseAdaptiveSizePolicy`를 통한 **자동 튜닝** 지원\
  → GC 시간과 애플리케이션 실행 시간의 비율을 조절 가능
* **배치 처리나 대용량 연산에 적합**\
  → 응답시간보다 전체 처리량이 중요한 시스템에서 유리함

#### ❌ 단점

* **STW(Stop-the-World)** 발생\
  → 짧더라도 GC 도중 전체 애플리케이션 멈춤
* **응답시간 제어가 어려움**\
  → 자동 튜닝은 정확히 예측되기 어렵고, 예측 가능한 GC가 필요한 실시간 서비스엔 부적합
* **Old 영역과의 연계에 따라 성능 저하 가능성 존재**\
  → Young GC가 자주 발생하면서 Old로 Promotion이 잦아질 경우, Old GC 트리거 가능

</details>

<details>

<summary>패러렐 올드</summary>

#### ✅ 장점

* **Full GC에서도 다중 스레드를 사용**\
  → Serial Old에 비해 Full GC 시간이 현저히 짧아짐
* **큰 힙 사이즈에서도 일정 수준 이상의 성능 보장**\
  → 대용량 처리 시스템에서 효율적
* **단순한 알고리즘 + 낮은 오버헤드**\
  → CMS나 G1보다 CPU 오버헤드가 낮은 편

#### ❌ 단점

* **STW 발생**\
  → Old 영역의 Full GC는 중단 시간이 크므로, 실시간성이 중요한 시스템에는 부적합
* **병렬화로 인한 CPU 점유율 급증**\
  → GC 동안 CPU 리소스를 많이 잡아먹음 → 다른 작업에 영향
* **Pause Time 조절 불가**\
  → CMS나 G1처럼 Pause Time을 예측하거나 조절하기 어려움

</details>

* 최장 14초의 STW\
  12GC에 달하는 힙 메모리를 전체 GC하기 위해 최대 14초까지 STW 발생.
* 구세대에 쌓이는 거대 객체\
  사용자가 웹 페이지를 요청하면 해당 파일을 디스크에서 메모리로 읽어 들이고, 이때 웹 페이지를 직렬화하는 과정에서 메모리에는 수많은 거대 객체가 쌓임.

<details>

<summary>직렬화 과정에서 거대 객체가 생기는 이유</summary>

직렬화(Serialization)는 객체를 바이트 스트림으로 변환해 저장하거나 네트워크로 전송할 수 있게 만드는 과정입니다. 여기서 웹 페이지를 직렬화한다는 것은 웹 페이지에 필요한 데이터를 메모리에 객체로 올려서 **직렬화 가능한 형태로 변환**한 후, 디스크에 쓰거나 다른 시스템에 전송하려는 목적이 있었을 수 있어요.

그런데…

**✅ 웹 페이지 데이터를 직렬화할 때 문제가 되는 시나리오:**

1. **전체 HTML + 이미지 + 스크립트 + 스타일 등 복잡한 구조를 객체로 구성**\
   → 예: DOM 트리 구조, 사용자 세션 정보, 캐시, 대형 Map/List 등
2. **동적으로 생성된 데이터가 많고, 이를 전부 메모리 객체로 저장**\
   → 사용자 요청 시 실시간으로 DB에서 불러온 콘텐츠까지 포함
3. **압축이나 필터링 없이 모든 정보를 객체 형태로 유지하고 직렬화**\
   → 예를 들어, `byte[]`, `String`, `BufferedImage`, `File`, `List<Object>` 등 수 MB\~수백 MB 크기의 필드 포함 객체가 다수 생성
4. **GC가 일어나기 전까지 계속해서 누적**\
   → Young 영역에 담기지 못하고 바로 Old 영역으로 올라가거나, Promotion 압박 발생

***

#### 🔸 왜 거대 객체가 GC를 방해했는가?

* Java의 GC는 **크기가 큰 객체**에 대해 **특수한 처리를 필요로 함**\
  → 일반적으로 **거대 객체는 Old 영역에 바로 할당됨** (`TLAB`을 못 씀)\
  → `-XX:PretenureSizeThreshold`를 넘는 객체는 바로 Old 영역으로 간다
* Old 영역은 GC 비용이 큼 (특히 Parallel Old는 Full GC가 STW임)
* 따라서 **많은 사용자가 동시에 웹 페이지 요청 → 거대 객체 생성 → 메모리에 쌓임 → Old 영역 포화 → Full GC 연속 발생 → 서버 응답 지연**

</details>



### 상황 분석

현재까지단일 자바 애플리케이션을 배포하는 주된 방식

* 가상 머신 인스턴스 하나가 거대한 자바 힙 메모리 관리
* 가상 머신 여러 개를 동시에 띄워 논리적인 클러스터 구성



현재 사례에서는,&#x20;

* 사용자 상호 작용이 많고
* 일시 정지 시간에 민감하고
* 대용량 메모리를 갖춤.



### 해결책 1. 단일 인스턴스

가상 머신 인스턴스 하나가 거대한 자바 힙 메모리 관리.

1. 셰넌도어 / ZGC 처럼 지연 시간 통제를 목표로 하는 가비지 컬렉터를 이용하면 문제 해결.



2. 패러렐 컬렉터로 거대 힙을 성공적으로 관리하는 사례

* 전체 GC 빈도를 가능한 한 낮게
* 사용자가 이용하지 않는 떄에 발생

힙 관리를 하루동안 잘하고, 새벽에 GC를 돌리는 방식. 이때 GC 빈도를 제어하려면 구세대가 안정되어야함. 오래 생존하는 객체가 적어야하고, 이를 통해 구세대 공간을 여유 있게 관리할 수 있음.

**단, 고려해야 할 것들이 존재**

* 힙 메모리의 거대 블록들을 수거하느라 일어나는 STW는 G1 컬렉터의 등장과 점진적 회복(incremental recovery)이 활발히 활용되면서 많이 줄어들었다.
* 64비트 가상 머신은 동일 버전 32비트 가상 머신보다 조금씩 느리다. (64비트 가상 머신에서는 대용량 메모리를 사용할 수 있다. 하지만 압축 포인터나 프로세서 캐시 라인 용량 같은 요인 때문)
* 애플리케이션이 충분히 안정적이어야 한다.
* 같은 프로그램이더라도 32비트 가상 머신보다 64비트 가상 머신에서 메모리를 많이 사용. (포인터 확장, 데이터 타입 정렬, 패딩 등의 요인 때문. 압축 포인터를 사용하여 메모리 추가 소비를 줄일 수 있음.)

<details>

<summary>incremental recovery</summary>

"최근 백업 이후 **변경된 데이터만 복구**하는 방식"\
→ 전체 복구(full recovery)에 비해 빠르고, 자원 소모가 적다.



### 🔸 예시

#### 📁 파일 백업 예시:

1. 월요일: 전체 백업 (`Full Backup`)
2. 화\~수: 변경된 파일만 추적 (`Incremental Backup`)
3. 목요일: 서버 장애 발생 → 복구 시

> ✅ `Full Backup (월요일)` + `화~수 Incremental Backup` → **Incremental Recovery**

</details>



### 해결책 2. 다중 인스턴스

가상 머신 여러 개를 동시에 띄워 논리적인 클러스터 구성

가상 머신 여러 개로 논리 클러스터를 구축해 하드웨어 자원을 십분 활용하는 것.&#x20;

예를 들어,&#x20;

1. 같은 물리 머신에서 애플리케이션 서버 프로세스를 여러 개 띄우고 각각에 서로 다른 포트를 할당.&#x20;
2. 앞단에 로드밸런서를 두어 리버스 프록시 방식으로 요청을 분배.

이 방식의 목적은 하드웨어 자원을 최대한 끌어 쓰기 위함. 따라서 애플리케이션을 역할이나 분야별로 분할하거나 Statefull 또는 핫 전송 같은 고가용성 요구 사항은 고려하지 않아도 상관 없음

<details>

<summary>핫 전송</summary>

사용자가 사용하던 상태(세션, 작업 정보 등)를 **중단 없이 다른 서버로 실시간 전송해서 이어받는 것**

#### 예시:

* A 서버에서 처리 중이던 사용자의 세션을 B 서버로 **끊김 없이 넘기는 것**
* 실시간 게임이나 스트리밍에서 서버 장애가 나도, 유저는 끊김 없이 그대로 이어가는 경우

#### 어려운 점:

* 세션, 파일 핸들, DB 커넥션 같은 자원을 서버 간에 실시간으로 넘기는 건 **복잡하고 비용이 큼**
* 그래서 일반적으로는 핫 전송을 잘 안 하고, 대신 상태를 서버가 아닌 \*\*외부 저장소(Redis, DB 등)에 저장하는 방식(= 무상태화 stateless)\*\*을 택함

</details>

**단, 고려해야 할 것들이 존재**

* 노드들이 전역 자원을 놓고 경합한다. \
  (동시 쓰기 연산 문제)
* 연결 풀과 같은 자원 풀을 효율적으로 활용하기 어렵다.\
  (노드 별로 따로 만들어 관리되기 떄문)
* 각 프로세스는 약 2GB\~4GB까지의 메모리만 할당됨.\
  (클러스터 노드로 32비스가상 머신을 이용하면, 노드별 메모리는 여전히 32비트로 제한.)
* 노드당 로컬 캐시가 따로 존재. \
  (메모리 낭비)\
  -> 중앙화된 캐시를 활용



이 사례에서는 두번째 해결방법을 선택.



## 2. 클러스터 간 동기화로 인한 메모리 오버플로

브라우저-서버 기반 경영 정보 시스템

### 개요

* 듀얼 프로세서
* 8GB 메모리
* 미니 컴퓨터 2대에 3개씩 노드를 구성하여 구동.



### 문제

* 공유 데이터를 데이터베이스로 관리했지만, 읽기, 쓰기 경합 발생
* 이를 해결하기 위해 글로벌 캐시를 뒀지만, 메모리 오버플로 발생



### 원인

1. -XX:+HeapDumpOnOutOfMemoryError 매개변수를 추가하고 스냅숏을 분석.
2. 수 많은 org.jgroups.protocols.pbcast.NAKACK 객체 발견

<details>

<summary>org.jgroups.protocols.pbcast.NAKACK 객체</summary>

JBossCache는&#x20;

* 클러스터 사이의 데이터 통신에 JGroups이라는 개념 적용.&#x20;

- JGroups는 데이터를 패킷으로 보내고 받는데 필요한 다양한 필수 특정을 자유롭게 조합할 수 있는 프로토콜 스택 이용.

* 프로토콜 스택 계층의 up()과 down() 메서드를 호출하여 패킷들을 순차적으로 주고받는 식.

</details>

3. 데이터 전송 실패시 재전송을 위해 그룹 멤버십 서비스에 등록된 모든 노드가 데이터를 제대로 수신했지 확인할때 까지 메모리에 보관해야함.
4. 유저의 요청이 페이지당 수십개의 요청이 일어나는 상황에서, 클러스터 노드들 사이의 네트워크 통신이 빈번하게 발생.
5. 이때 네트워크가 데이터 전송량을 다 처리하지 못하게 되면, 재전송된 데이터가 메모리에 계속 쌓이다가 오버플로를 발생시킴.



### 해결책 1

메시지를 메모리에 저장하는 상황에서 문제가 발생한다면, 메시지의 크기를 줄일 수 있음

네트워크 전송량을 처리할 수 있도록 압축을 할 수 있음. 다만, CPU 부하가 발생



### 해결책 2

JGroup의 전송 대기열의 사이즈를 제한할 수 있음.&#x20;

다만, 이때 큐를 넘어서는 메시지는 버려지거나, 블로킹되거나, 예외가 발생하는 단점이 있음.



## 3. 힙 메모리 부족으로 인한 오버플로 오류

* 브라우저-서버 기반 온라인 시험 시스템
* 서버 푸시 기술을 활용해 클라이언트가 서버로부터 시험 데이터를 실시간으로 받음.

### 개요

* i5 CPU
* 4GB 메모리
* 32비트 윈도우



### 문제

* 테스트 중 서버에서 메모리 오버플로 가끔 발생.
* 오류가 발생하면 온라인 시험 자체가 엉망이 됨.
* 힙 용량을 키우고, -XX:+HeapDumpOnOutOfMemoryError를 설정해도, 시스템은 덤프 파일을 생성하지 못함.
* jstat을 보니, 힙, 메서드 영역은 모두 안정적이고, GC는 자주 발생하지 않았다.

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2Fnk9H3Verjx9i4vXLPgyW%2Fimage.png?alt=media&#x26;token=f08e607e-ae88-4a11-9fd2-bad814089353" alt=""><figcaption><p>시스템 로그</p></figcaption></figure>

### 원인

* 운영체제에서는 개별 프로세스가 관리할 수 있는 메모리 최대 크기에 제한이 있음
* 2GB중 1.6GB를 힙에 할당.
* 다이렉트 메모리 역시 GC의 대상인데, 공간이 부족하더라도 해당 메모리는 GC에 능동적으로 알리지 못함.
* 힙의 구세대가 꽉차길 기다리면서 GC가 수행되길 기다려야함.
* 다이렉트 메모리 부족\
  OutOfMemoryError
* 스레드 스텍  메모리 부족\
  StackOverFlowError (가상머신 허용치를 넘는 깊이)\
  OutOfMemoryError (용량을 동적으로 확장하려할때)
* 소켓 버퍼 영역 메모리 부족\
  (소켓 연결에 receive와 send라는 두가지 버퍼 영역이 할당. 각 (37KB, 25KB)
* 네이티브 메모리 부족\
  (네이티브 라이브러리를 이용하기 위해 JNI를 호출하고, 이때 네이티브 라이브러리는 네이티브 메서드 스택과 네이티브 메모리 사용)
* 가상 머신과 가비지 컬렉터 구동 메모리 부족



### 해결책





## 4. 시스템을 느려지게 하는 외부 명령어

대학교 운영을 디지털화해주는 시스템.

### 개요

* 프로세서 4개가 장착된 솔라리스 10 시스템
* 글래스피시 미들웨어 사용



### 문제

* 동시성 스르테스 테스트시 응답 시간 저하



### 원인

* 사용자 애플리케이션이 아닌, 시스템이 자원을 대부분 소비



### 상황 분석

1. 프로세서 자원 대부분을 소비하는 시스템 콜이 무엇인지 식별(솔라리스10의 dtrace 스크립트)
2. fork 시스템 콜이 문제.\
   (새로운 프로세스를 생성할때 호출)
3. 사용자 요청을 처리하려면 특정 시스템 정보가 필요하여, 요청 각각 외부 셀 스크립트를 실행
4. Runtime.getRuntime().exec() 메서드로 실행되고, 이는 자원을 많이 소모함.\
   (현재 가상 머신과 똑같은 환경 변수 설정을 공유하는 프로세스를 복사 -> 새로운 프로세스에서 외부 명령 실행 -> 프로세스 종료 과정)



### 해결책

셀 스크립트를 실행하는 코드를 지우고, 필요한 정보를 자바 API로 가져오도록 변경





## 5. 서버 가상 머신 프로세스 비정상 종료

브라우저-서버 기반 경영 정보 시스템

### 개요

* 듀얼 프로세서
* 8GB 메모리
* 미니 컴퓨터 2대
* 총 6개의 노드의 선호도 클러스터로 구성된 시스템.



### 문제

* 클러스터 노드의 가상 머신 프로세스가 갑자기 닫히는 일이 빈번해짐
* 가상 머신 프로세스는 log파일만 남긴 채 사라지고, 두 컴퓨터의 모든 노드에서 프로세스 충돌 발생

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2F7LpAW4HltE1YaTjAZGFf%2Fimage.png?alt=media&#x26;token=5b9ca0b1-18dd-4a42-b373-f62ee8c0776a" alt=""><figcaption><p>"원격지에서 연결을 끊었다"</p></figcaption></figure>



### 원인

* 경영 정보 시스템에서 할 일 항목의 상태가 바뀌면, 사무 자동화 포털 시스템이 웹서비스를 통해 정보를 받아와서 동기화.
* 동기화 요청을 분석(SoapUI)하자, 3분후 timeout에 의한 연결 중단 응답을 받음
* 웹 서비스 호출을 비동기로 수행하고 있었고, 사무 자동화 시스템이 제때 응답해 주지 않아서 대기 중인 스레드와 소켓 연결이 점점 많아짐.



### 해결책

비동기 호출 부분을 생산자/소비자 방식의 메시지 큐로 변경하여 문제 해결





## 6. 부적절한 데이터 구조로 인한 메모리 과소비

64비트 자바 가상 머신을 이용하는 백그라운드 원격 프로시저 호출(RPC) 서버 사례



### 개요

* 메모리는 -Xms4g -Xms8g, Xmn1g로 설정
* 파뉴 + CMS 컬렉터 조합 이용
* 마이너 GC 시간은 30ms 이하.

<details>

<summary>-Xms4g -Xms8g, Xmn1g</summary>

#### ✅ `-Xms4g`

* **초기 힙(Heap) 크기**를 의미합니다.
* JVM이 시작할 때 확보하는 힙 메모리 크기를 4GB로 지정한 거예요.
* 예: `-Xms4g` → JVM 시작 시 힙 크기를 4GB로 잡음.

***

#### ✅ `-Xmx8g`

* **최대 힙(Heap) 크기**를 의미합니다.
* JVM이 사용할 수 있는 힙 메모리의 **최대치**를 8GB로 제한한 거예요.
* 예: `-Xmx8g` → 힙이 점점 커질 수는 있지만, 8GB를 넘을 수 없음.

***

#### ✅ `-Xmn1g`

* **Young Generation 크기**를 의미합니다. (`New Generation`이라고도 함)
* 힙 메모리는 크게 Young / Old 로 나뉘고, Young은 Garbage Collection이 자주 일어나는 공간입니다.
* `-Xmn1g` → Young 영역 크기를 1GB로 설정.

</details>

### 문제

* 데이터 분석을 위해 10분 단위로 80MB 크기의 파일을 메모리로 읽는 과정에서, 100만 개 이상의 HashMap\<Long,Long> 객체를 생성
* 마이너 GC가 해당 객체들을 검사하는 과정에서 STW 발생

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2F4klhMajJNdR7CfLbYWEH%2Fimage.png?alt=media&#x26;token=0b4c9ac7-6f41-4797-9e37-99c78a144c55" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FBtF6QL41nMjj6X3kxqqZ%2Fimage.png?alt=media&#x26;token=245b2818-be6a-461a-ae0d-f79aa2da5ac5" alt=""><figcaption></figcaption></figure>



### 원인

* 데이터 파일을 분석하는 동안 총 800MB 용량의 에덴이 빠르게 채워져서 GC가 발생.
* 하지만 Minor GC 후에도 신세대 객체 대부분이 생존.
* 파뉴 컬렉터의 복사 알고리즘으로 인해, 복사하는 과정, 참조를 관리하는 과정이 무거움



### 해결책 1. GC 최적화

GC 최적화만으로 해결이 가능하지만 부작용이 큼.

* 생존자 공간을 제거하여 첫 번째 Minor GC 후 신세대에서 살아남은 객체들을 곧바로 구세대로 옮기는 방법. 죽은 객체 회수는 다음번 Major GC에 맡기는 것.



### 해결책 2. 데이터 구조 수정

Key와 Value를 담기 위한 객체는 long 값 두개만 필요하지만, Long 객체(24바이트) + Map.entry 저장(16바이트) + next 필드(8바이트) + 해시 필드(4바이트)로 구성되어 비효율적.





## 7. 윈도우 가상 메모리로 인한 긴 일시 정지

GUI 데스크톱 프로그램에서 발생



### 개요

* 심장 박동 데이터는 서드 파티 서비스로부터 15초마다 얻어 옴.
* 서드 파티 서비스가 30초 내로 회신하지 않으면 연결이 끊긴걸로 간주



### 문제

* 거짓 양성 (false positive) 데이터가 자주 섞여 들어오는 문제 발생



### 원인

* 거짓 양성이 생기는 이유는 프로그램이 약 1분 간격으로 로그 출력 없이 일시 정지 상태가 되기 때문.
* -XX:+PrintGCApplicationStoppedTime, -XX:+PrintGCDate-Stamps -Xloggc:gclog.log 변수를 추가한 후, GC가 문제임을 확인

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FVsM39xIYr41uSQ5mwfR0%2Fimage.png?alt=media&#x26;token=1aaa3471-8adb-482b-b4e1-c5f73a1a1c1a" alt=""><figcaption></figcaption></figure>

* -XX: +PrintReferenceGC 매개 변수를 추가하여 로그 정보를 자세히 보니, 컬렉션 준비 단계에서 실제 시작까지 시간 소요가 큼.

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FA6OnbyMN5vCoFCKs4p2C%2Fimage.png?alt=media&#x26;token=02f85033-1dc2-4326-8dee-64b69f4e4ea1" alt=""><figcaption></figcaption></figure>

* 프로그램 창을 최소화하면 메모리 사용량이 급격하게 줄었지만, 가상 메모리에는 변화가 없음. 창을 최소화하면 작업 메모리가 디스크로 스왑된다고 짐작됨.
* 위 상황에서 GC를 수행하려면 스와프된 데이터를 메모리로 다시 불러와야함.



### 해결책

GUI 프로그램에서 이 현사은, -Dsun.awt.keepWorkingSetOnMinimize=true 로 설정하면 쉽게 해결



### 8. 안전 지점으로 인한 긴 일시 정지

컴퓨팅 작업을 처리하는 HBase클러스터

### 개요

* JDK8
* G1 컬렉터
* 매일 대량의 맵리듀스나 스파크 오프라인 분석을 수행
* -XX:MaxGCPauseMillis를 500ms로 설정. (오프라인 분석에서 지연시간은 중요하지 않다고 생각)



### 문제

* GC의 STW가 3초 이상까지 길어지는 일이 자주 발생

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FIn0XCrNGaLndkPY9nUeO%2Fimage.png?alt=media&#x26;token=590ae88f-a982-4676-88c3-d4a6f41fdcae" alt=""><figcaption></figcaption></figure>

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2Fgx1lSwfj9QmevPPzoCEI%2Fimage.png?alt=media&#x26;token=715f155e-2dff-4b06-af39-80bb480245d0" alt=""><figcaption><p>안전지점에서의 통계로그</p></figcaption></figure>

<figure><img src="https://2804243633-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FfV6clXG953LqFieNgWUf%2Fuploads%2FnvumPYJQjwnoRd7tx82F%2Fimage.png?alt=media&#x26;token=64a9d44a-fac3-44ab-b962-7ea67a2bd89f" alt=""><figcaption><p>스레드들이 안전 지점에 도착할 때까지 가상 머신을 기다리게 함. 그 위, 2000ms후 타임아웃을 발생시킨 후 결과</p></figcaption></figure>

### 원인

* GC가 객체를 회수하는 시간은 x00ms밖에 안걸림.
* 다만, 사용자 스레드 모두가 안전 지점에 도착할 때까지 기다리게 설정되어 있음
* RpcServer.listener,port=24600 스레드가 원인
* 순환문이 안전 지대로 설정되지 않음.\
  보통 핫스팟 가상 머신은 순환문을 평가하여 최적화를 함. int 이하의 범위는 안전 지점으로 설정하지 않음. (카운티드 루프. 순환문의 실행 시간이 결정요인이 아님.)



### 해결

loop타입의 변수 타입을 long으로 변경.\
(-XX:+UserCountedLoopSafePoints를 사용할 수 있지만 버그가 존재하여 사용하지 않음)









## 성능 최적화 실전

### 1. JDK 업그레이드

* **동작 원리**: JVM은 버전이 올라갈수록 내부적으로 JIT 컴파일러 성능, GC 알고리즘, 클래스 로딩, 런타임 최적화 등의 성능이 향상됩니다.
* **장점**:
  * 최신 GC(G1, ZGC 등) 지원
  * 전반적인 실행 성능 향상 (코드 변경 없이)
  * JVM 내부 최적화 반영됨 (GC/클래스 로딩/JIT 등)
* **단점**:
  * 일부 레거시 라이브러리나 플러그인이 최신 JDK와 호환되지 않을 수 있음
  * 환경 설정 변경 필요 (특히 Eclipse에서 `-vm` 설정 필요)



### 2. -Xverify:none

* **동작 원리**: JVM은 클래스 로딩 시 바이트코드를 검증하는데, 해당 옵션은 이 검증을 생략합니다.
* **장점**:
  * 클래스 로딩 속도 개선 → 전체 구동 시간 단축
  * 안정된 환경(검증된 코드)에서는 불필요한 검증 비용 제거 가능
* **단점**:
  * 보안 취약점 가능성 (잘못된 바이트코드도 로딩될 수 있음)
  * 외부 라이브러리 사용 시 문제가 발생할 수 있음



### 3. -Xms, -Xmx (Heap Size 설정)

* **동작 원리**: JVM의 힙 영역 크기를 초기/최대로 고정시켜, GC 발생 빈도를 조절합니다.
* **장점**:
  * GC 발생 횟수 감소 → 성능 안정화
  * 힙 크기 예측 가능 → 서버 환경에서 유리
* **단점**:
  * 메모리 할당이 고정되기 때문에 시스템 리소스 부족 시 병목 발생 가능
  * 너무 크게 설정하면 메모리 낭비



### 4. -XX:+DisableExplicitGC

* **동작 원리**: 코드 내에서 `System.gc()` 호출이 있어도 무시하도록 하여 강제 GC를 방지합니다.
* **장점**:
  * 불필요한 전체 GC 수행 방지 → 지연 시간 감소
  * GC 타이밍을 JVM에게 완전히 위임 가능
* **단점**:
  * 일부 라이브러리는 `System.gc()`를 통한 명시적 메모리 해제를 기대할 수 있음 → 예상치 못한 메모리 사용 증가



### 5. -Xlog:gc\*, -Xlog:gc:gc.log

* **동작 원리**: GC 발생 시점, 시간, 이유 등을 로그로 남겨주는 옵션. `gc.log` 파일로 저장 가능.
* **장점**:
  * GC 동작 분석 가능 → 성능 튜닝 필수 도구
  * 병목 분석에 효과적
* **단점**:
  * 로그 크기 증가 → 장기 운영 시 디스크 사용량 증가
  * GC 로그 해석에 대한 이해 필요



### 6. -XX:+UseG1GC, -XX:+UseZGC

* **동작 원리**:
  * **G1GC**: 힙을 여러 영역으로 나눠서 병렬로 GC를 수행하여 지연시간을 줄이는 방식
  * **ZGC**: GC 수행 중 애플리케이션 정지를 거의 0에 가깝게 만드는 저지연(低遲延) GC
* **장점**:
  * G1: 짧은 정지 시간(평균 37ms 수준), 안정적
  * ZGC: GC activity가 0%에 가까움, 초대형 힙에 적합
* **단점**:
  * G1: 메모리 부족 상황에선 Minor GC가 자주 발생
  * ZGC: 성숙도가 낮고, 분석 툴 호환성이 떨어질 수 있음



### 7. -Xint, -Xcomp (JIT 설정)

* **동작 원리**:
  * `-Xint`: JIT 컴파일을 하지 않고 모든 바이트코드를 인터프리터 방식으로 실행
  * `-Xcomp`: 시작부터 모든 코드를 컴파일하려 시도 → 최적화된 실행 경로 확보
* **장점**:
  * `-Xint`: 디버깅 용도나 JIT 영향을 제거한 성능 측정 가능
  * `-Xcomp`: 최적화된 코드로 빠르게 실행될 가능성
* **단점**:
  * `-Xint`: 매우 느림 (30초 이상 소요되는 경우도 있음)
  * `-Xcomp`: 구동 속도가 느려질 수 있고, 예외나 초기 오버헤드 발생



### 8. -client / -server 모드

* **동작 원리**:
  * `-client`: 빠른 시작과 짧은 생명주기에 적합한 JIT 설정 (GUI에 적합)
  * `-server`: 실행 성능 최우선 → 서버나 장기 실행 애플리케이션에 적합
* **장점**:
  * `-client`: GUI 환경에서 빠른 시작 가능 (이클립스처럼)
  * `-server`: 장기 실행에 적합, 최적화된 성능
* **단점**:
  * JDK 9부터는 `-client` 모드 제거됨 (즉, 최신 환경에선 무효)

