반응형

0. 들어가며


자바 애플리케이션을 운영하면서 JVM의 다양한 옵션을 접하게 됩니다. 그 중 성능 튜닝에 유용한 옵션으로 -XX:+AlwaysPreTouch가 있습니다.

이번 글에서는 JVM 메모리 구조와 운영 체제의 메모리 관리 기초를 살펴본 뒤, -XX:+AlwaysPreTouch 옵션이 무엇을 하는지, 활성화/비활성화 시의 차이는 무엇인지, 어떤 상황에서 유용한지와 성능 영향, 그리고 간단한 실험 결과까지 알아보겠습니다.

 

 

1. JVM 메모리 구조와 메모리 관리 기초


1.1 JVM 메모리 구조

JVM은 실행 시 여러 메모리 영역을 사용합니다. 그 중 힙(heap)은 보통 가장 큰 영역으로, 애플리케이션이 동적으로 할당한 객체들이 저장되는 공간입니다.

-Xmx 옵션으로 설정한 최대 힙 크기까지 객체를 저장할 수 있고, -Xms 옵션으로 초기 힙 크기를 지정할 수 있습니다. 일반적으로 서버 환경에서는 힙의 초기 크기(Xms)와 최대 크기(Xmx)를 동일하게 설정해 JVM 실행 초기에 힙을 한 번에 할당해두기도 합니다.

 

Production environments often set the -Xms and -Xmx options to the same value so that the heap size is fixed and pre-allocated to the JVM. (출처)

 

 

힙은 가비지 컬렉션에 의해 관리되며, 자바 8 이후로 Young Gen과 Old Gen 등으로 나뉘어 관리됩니다. 메타스페이스는 클래스 메타데이터를 저장하는 영역으로, 힙과 별도로 네이티브 메모리에 할당됩니다. 이 외에도 스택은 각 스레드마다 할당되어 메서드 호출 시 지역 변수, 매개변수, 리턴 주소 등을 저장하며 LIFO(후입선출) 방식으로 관리됩니다. 스택은 스레드마다 분리되어 있어 thread-safe한 영역입니다. 각 메서드 호출 시 스택 프레임이 생성되고 종료 시 해제되는 방식으로 동작하고, 스택 용량을 넘어서면 StackOverflowError 가 발생합니다.

 

이러한 힙, 스택, 메타스페이스 이외에도 JIT 컴파일된 코드가 저장되는 코드 캐시(code cache)나, Direct ByteBuffer와 같은 직접 메모리 영역 등도 존재하지만, 이번 글의 초점은 힙 메모리와 관련 옵션이므로 핵심 개념만 짚고 넘어가겠습니다.

 

1.2 가상 메모리와 물리 메모리의 관계

운영체제는 프로세스마다 가상 메모리 주소 공간을 제공합니다. 자바 프로세스(JVM)를 실행하면서 -Xmx로 힙 최대 크기를 지정하면, JVM은 해당 크기만큼 가상 메모리를 예약(reserve)합니다.

 

Before discussing about the flag, we should understand how the JVM set its virtual memory. When you run the JVM, you are setting its heap size with the parameters -Xmx and -Xms. At that time, the Operating System (OS) reserve only the virtual memory. Except if the JVM wants to use the memory, then the OS physical memory will be allocated. Thus, the flag “AlwaysPreTouch” comes in handy, when enabling it, it allows the physical memory to be allocated to the JVM instead of the virtual memory. it forces all freshly committed pages to be pre-touched. This gets all pages into memory before entering the main() method. It speeds up the performance of the application, but nothing is perfect, it has some disadvantages. Basically, allocating physical memory to the JVM at startup will affect the JVM startup’s time and cause it to decrease. (출처)

 

 

예를 들어 -Xmx4g로 실행하면 JVM은 4GB에 해당하는 주소 공간을 힙으로 잡아두지만, 이 단계에서는 실제 물리 메모리(RAM)가 바로 할당되는 것이 아닙니다. 운영체제는 예약된 가상 메모리 영역만 확보해 두고, 해당 페이지들이 실제로 접근될 때에야 비로소 물리 메모리를 할당합니다. 이를 지연 할당(lazy allocation) 또는 수요 기반 페이징(demand paging)이라 합니다. 모든 가상 메모리 페이지는 처음 접근 시 페이지 폴트(page fault)를 발생시키고, 운영체제는 그 시점에 해당 페이지를 실제 메모리에 맵핑하면서 0으로 초기화(demand-zero)합니다. 그 전까지는 메모리가 물리적으로 차지되지 않으므로, 예를 들어 JVM에 100GB의 힙을 예약해도 실제 사용이 적다면 초기 메모리 사용량은 낮게 유지됩니다.

 

The JVM max heap is allocated in virtual memory, not physical memory: it is recorded in an internal data structure to avoid it being used by any other process. Not even a single page will be allocated in physical memory until it's indeed accessed. When the JVM needs memory, the operating system will allocate pages as needed. (출처)

 

 

이처럼 예약(reserve)과 커밋(commit)의 개념을 이해하는 것이 중요합니다. -Xmx로 예약한 힙 공간은 필요할 때 커밋되어 사용됩니다. 반면 -Xms 옵션은 힙의 초기 크기를 지정하는데, 이 값만큼은 JVM 시작 시점에 바로 커밋합니다. 일반적으로 작은 힙에서는 -Xms 설정으로 인한 추가 지연이 미미하지만, 매우 큰 힙에서는 초기 커밋 동작만으로도 수 초 이상의 시간이 걸릴 수 있다고 합니다. 이는 초기 커밋 과정에서 OS가 큰 메모리 영역을 관리 구조에 등록하고 일부 페이지를 zero-fill 하는데 시간이 들기 때문입니다. (출처)

 

페이지와 프레임?

현대 OS에서 메모리는 보통 4KB 크기의 페이지 단위로 관리됩니다. 프로세스의 가상 주소 공간은 페이지 단위로 쪼개지고, 각 페이지마다 물리 메모리의 프레임이 할당됩니다. 4KB 페이지 이외에도 2MB 또는 1GB 크기의 대용량 페이지(Huge Page)를 지원하는 시스템도 있습니다. 대용량 페이지를 사용하면 페이지 테이블 관리 오버헤드나 TLB 미스가 줄어들어 메모리 접근 성능이 향상될 수 있습니다. JVM에서도 -XX:+UseLargePages나 -XX:+UseTransparentHugePages 등의 옵션으로 이 기능을 활용할 수 있습니다. (출처)

 

1.3 NUMA란 무엇인가?

컴퓨터 하드웨어 아키텍처 중에는 NUMA(Non-Uniform Memory Access) 구조가 있습니다. 멀티 프로세서 시스템에서 각각의 CPU가 자신의 지역 메모리(RAM)를 가지고 있고, 다른 CPU의 메모리에 접근할 수도 있지만 속도가 느린 형태를 말합니다. (출처) 간단히 말해, CPU와 메모리가 한 쌍으로 노드(node)를 이루고, 자신의 노드 메모리에 접근할 때는 지연이 낮고 대역폭이 높지만, 원격 노드 메모리에 접근하면 지연이 커지고 성능이 떨어집니다. 이러한 NUMA 시스템에서는 메모리 지역성(locality)이 성능에 중요하게 작용합니다. 운영체제는 일반적으로 첫 터치(first touch) 정책으로 동작하는데, 어떤 CPU 코어가 메모리 페이지를 처음 할당하면 그 페이지를 해당 코어가 속한 NUMA 노드의 메모리에 매핑하려고 합니다. 예를 들어 2개의 CPU 시스템에서 JVM이 처음 힙의 특정 부분을 사용하게 될 때, 그 동작을 수행한 스레드가 1번 CPU에 속해 있었다면 해당 메모리 페이지가 1번 CPU 노드에 있는 RAM에 할당되는 식입니다. 이후에 2번 CPU가 그 메모리를 사용하면 원격 접근이 발생하여 성능이 저하될 수 있습니다.

 

이 문제를 완화하기 위해 JVM에는 -XX:+UseNUMA 옵션이 존재하며, NUMA 시스템에서 힙을 노드별로 나누어 균등하게 분산 할당하려는 시도를 합니다. 예컨대 2개의 노드가 있는 시스템에서 40GB 힙을 사용하면, 각 노드에 20GB씩 할당하는 식입니다. 또한 가비지 컬렉션 시에도 각 노드의 메모리를 고려하여 최적화합니다. 이러한 NUMA 최적화는 멀티 프로세서 머신에서 GC 성능과 메모리 대역폭 활용을 개선할 수 있습니다. 다만, NUMA 최적화 여부와 관계없이 처음 어떤 스레드가 메모리를 터치하느냐가 중요하므로, 바로 다음에 설명할 AlwaysPreTouch 옵션이 NUMA 환경에서 어떻게 동작하는지도 눈여겨봐야 합니다.

 

2. XX:+AlwaysPreTouch 옵션 자세히 알아보기


2.1 AlwaysPreTouch 옵션의 역할

XX:+AlwaysPreTouch를 활성화하면 JVM이 애플리케이션 시작 전에 힙 영역을 모두 미리 터치(pre-touch)하도록 합니다. 즉, JVM 초기화 과정에서 힙의 모든 페이지에 대해 한 번씩 접근(메모리 쓰기)을 수행하여, 운영체제가 해당 페이지들을 즉시 물리 메모리에 할당하고 0으로 초기화하게 만듭니다. 평소에는 객체가 필요할 때마다 힙의 새로운 페이지가 처음 쓰여질 때 OS가 페이지를 할당하지만, AlwaysPreTouch를 켜두면 main 메서드가 실행되기 이전에 미리 이 작업을 완료하는 것입니다. 다시 말해, 힙을 미리 모두 커밋하고 접근까지 해둠으로써, 애플리케이션이 동작하는 동안에는 페이지 폴트로 인한 지연이 발생하지 않도록 하는 것이 이 옵션의 목표입니다.

 

If you add -XX:+AlwaysPreTouch to the JVM arguments, the JVM will pre-touch the entirety of the heap at startup time to avoid unpredictable page faults through the life of the application. (출처)

 

Oracle 공식 문서에서도 “JVM 초기화 시 자바 힙의 모든 페이지를 미리 0으로 채운다”고 이 옵션을 설명하고 있습니다.

 

Pre-touch the Java heap during JVM initialization. Every page of the heap is thus demand-zeroed during initialization rather than incrementally during application execution. (출처)

 

2.2 활성화 시와 비활성화 시의 차이

AlwaysPreTouch를 비활성화(default)한 상태에서는 JVM이 힙 최대 크기만 가상메모리로 확보할 뿐, OS가 물리 메모리를 할당하는 것은 나중에 일어납니다. 힙에 객체를 할당하거나 GC를 위해 힙을 스캔하면서 해당 페이지들을 처음 touch 할 때, 그 순간마다 필요한 페이지만 물리 메모리를 매핑합니다. 이 과정은 한 번에 몇 페이지씩 산발적으로 일어나므로, 초기에는 JVM 프로세스의 실제 메모리 사용량이 낮다가 점차 올라가게 됩니다. 각 페이지를 할당할 때 OS 레벨에서 페이지 폴트(minor page fault)가 발생하지만, 워낙 짧은 작업이라 보통 애플리케이션 전체에 큰 영향을 주지는 않습니다.

 

반면 AlwaysPreTouch를 활성화하면 JVM 시작 시점에 이러한 페이지 할당 작업을 한꺼번에 수행합니다. JVM이 힙 전체를 차례로 훑으면서 모든 페이지에 ‘0’을 써넣는 방식으로 접근하고, 그 결과 OS는 해당 힙 영역 전체를 실제 메모리에 매핑합니다. 이 과정이 끝나면 힙의 모든 페이지가 물리 메모리와 연결되고 초기화까지 완료된 상태가 됩니다. 따라서 애플리케이션이 본격적으로 객체를 할당하고 사용할 때는 이미 메모리가 준비되어 있으므로 페이지 폴트가 일어나지 않으며, 메모리 접근 지연이 일정하고 예측 가능해집니다. 특히 실시간성 혹은 지연에 민감한(Latency-sensitive) 애플리케이션에서는 이러한 이점이 중요합니다. 예를 들어 고빈도 거래(HFT) 시스템에서 Java를 사용하는 경우, 거래 시점의 예측 불가능한 메모리 지연을 피하기 위해 AlwaysPreTouch 옵션을 켜는 일이 있다고 합니다.

정리하면, 아래와 같습니다.

 

  • AlwaysPreTouch OFF (기본값)
    • 힙 크기만큼 가상 메모리를 예약하되, 실제 물리 메모리는 점진적으로 할당. 애플리케이션이 객체를 할당하거나 접근할 때 필요한 페이지만 그때그때 매핑.
    • 장점: JVM 시작 속도 빠름, 초기 메모리 사용량 낮음.
    • 단점: 런타임 중 간헐적으로 페이지 폴트 발생 가능, 특히 처음 큰 객체를 할당하거나 GC로 많은 페이지를 스캔할 때 일시 지연이 생길 수 있음. 또한 멀티 프로세스 환경에서 초기 스레드에 따라 메모리 할당 노드가 결정되므로 메모리 분포가 비균일해질 수 있음.
  • AlwaysPreTouch ON
    • JVM 시작시 힙의 모든 메모리를 미리 할당 및 초기화.
    • 장점: 런타임 중 페이지 폴트가 제거되어 메모리 접근 지연이 일관되고 예측 가능함. 필요 시 OS 스왑을 미리 감지하거나(시작 단계에서 메모리 부족이면 바로 실패) NUMA 환경에서 올바른 메모리 분배를 유도할 수도 있음.
    • 단점: JVM 시작 시간이 길어짐. 힙이 클수록 부팅에 상당한 시간이 소요될 수 있음. 또한 프로세스 시작 시점에 대량의 물리 메모리를 잡아먹으므로, 다른 프로세스나 OS 자체에 메모리 압박(memory pressure)을 줄 수 있음. 메모리를 미리 모두 사용한 상황에서는 OS가 페이지 캐시를 정리하거나 스왑을 준비하는 등의 작업에 시간이 더 걸릴 수도 있습니다.

특히 NUMA 시스템에서 AlwaysPreTouch의 영향은 두 가지로 생각해볼 수 있습니다. 하나는, 메모리 지역성 향상 관점입니다. JVM이 힙을 미리 터치할 때 멀티스레드를 이용하여 병렬로 수행하면, 각 스레드가 다른 NUMA 노드의 메모리를 할당받아 힙이 골고루 분산될 가능성이 있습니다. 실제로 JDK 9 이후의 G1 GC의 경우 힙 pretouch를 병렬화하여, 여러 워커 쓰레드가 힙 영역을 나누어 pre-touch를 수행하도록 개선되었습니다. (JDK 8의 Parallel GC 등은 pretouch가 단일 스레드로 동작했습니다.) 이러한 병렬 pretouch는 NUMA 환경에서 힙을 보다 균등하게 노드 별 할당하는데 도움이 됩니다. 반면, 만약 pretouch 작업이 한 스레드에서 직렬로 이루어진다면, 그 스레드가 속한 CPU 노드의 메모리로 힙 전체가 할당되어 오히려 메모리 쏠림 현상 이 발생할 수 있습니다. 다행히 최신 JVM들은 이를 잘 처리하고 있지만, 확실한 NUMA 제어를 원한다면 numactl로 프로세스를 노드에 바인딩하거나 JVM의 -XX:+UseNUMA 옵션을 함께 사용하는 것이 권장됩니다.

 

2.3 언제 유용하고, 어떤 효과가 있을까?

이 옵션은 대용량 힙을 사용하는 애플리케이션에서 특히 유용합니다. 힙 사이즈가 수십 GB에 달하는 경우, 애플리케이션이 초기화되고 실제 트래픽을 처리하기 시작할 때까지 지속적으로 페이지 폴트가 발생하면서 지연 시간이 들쑥날쑥할 수 있습니다. AlwaysPreTouch를 켜두면 이러한 페이지 폴트를 애플리케이션 가동 전에 집중시켜 두므로, 서비스 운영 중에는 메모리 관련 지연이 눈에 띄게 줄어듭니다. 또한 JVM 장기 실행(long-running) 환경에서 메모리가 꾸준히 사용되는 경우 (예: 빅데이터 처리, 대규모 캐시 서버, 애플리케이션 서버 등), 미리 메모리를 할당해 두는 것이 메모리 사용량 예측안정적 성능 유지에 도움을 줍니다. 가비지 컬렉션 성능 면에서도, GC 스레드들이 힙을 스캔하거나 객체를 이동할 때 이미 물리 메모리가 매핑되어 있으므로 불필요한 지연을 줄일 수 있습니다.

 

다만, 애플리케이션의 특성에 따라 고려해야 할 점도 있습니다. 예를 들어 짧게 실행되는 작업성 프로그램이나 힙 사용량이 크지 않은 애플리케이션에는 AlwaysPreTouch가 오히려 부하만 추가할 수 있습니다. 초기화 시 불필요하게 많은 메모리를 미리 할당하고, 정작 사용은 다 안할 수도 있기 때문입니다. 또한 클라우드/컨테이너 환경에서는 컨테이너에 메모리 할당량을 정해둔 경우가 많은데, AlwaysPreTouch를 켜면 컨테이너 시작 시 바로 해당 메모리를 모두 점유하므로 스케줄링 지연이나 메모리 과사용 감지 등의 이슈를 유발할 수 있습니다. 따라서 스타트업 시간이 중요한 애플리케이션(예: 단기 배치작업 등)이나 메모리 사용량이 가변적인 워크로드에는 이 옵션을 피하는 것이 좋습니다. 반대로, 한 번 뜨면 오래 유지되는 서버 애플리케이션, 특히 메모리를 많이 쓰고 성능 민감한 서비스라면 AlwaysPreTouch를 통해 얻는 이점이 충분히 클 수 있습니다.

 

3. 실험과 예제로 보는 AlwaysPreTouch 효과


이론만으로는 감이 잘 안 올 수 있으니, 간단한 예시와 실험 결과를 통해 AlwaysPreTouch의 효과를 눈으로 확인해보겠습니다. (출처)

3.1 힙 초기화 시간 비교 실험

먼저 JVM 힙을 크게 잡았을 때 시작 시간이 어떻게 달라지는지 보겠습니다. 아래와 같은 아주 간단한 자바 프로그램이 있다고 가정합니다:

public class EmptyApp {
    public static void main(String[] args) {
        System.out.println("JVM started");
    }
}

이 프로그램은 특별한 작업을 하지 않고 바로 종료되는데요, 이때 힙 크기 및 AlwaysPreTouch 옵션에 따른 실행 시간을 비교해보겠습니다. 리눅스 환경에서 time 명령을 이용하여 측정했다고 가정합니다.

힙을 작게 할당 (예: 50MB).

$ time java -Xms50m -Xmx50m EmptyApp

실행 시간: 약 1초 → 이 경우 힙이 작아 JVM 시작 속도가 매우 빠릅니다. -Xms와 -Xmx 모두 50MB로 지정하여 시작 시 50MB를 커밋하지만, 50MB는 워낙 적은 양이라 커밋 및 초기화가 거의 체감되지 않습니다

 

대용량 힙 (AlwaysPreTouch 꺼짐)

$ time java -Xms512g -Xmx512g EmptyApp

실행 시간: 약 9~10초 → 힙을 크게 512GB로 설정하고 AlwaysPreTouch는 사용하지 않은 경우입니다. 이때 JVM은 512GB의 가상 메모리를 힙으로 예약하고, 시작 시 Xms512g에 의해 512GB를 커밋하지만, OS는 실제로는 일부 페이지만 할당합니다. 그럼에도 불구하고 수백 GB에 달하는 주소 공간을 커밋하는 작업이 있기 때문에 약 10초 정도의 지연이 발생했습니다.

 

대용량 힙 (AlwaysPreTouch 켜짐)

힙을 512GB로 설정하고 + AlwaysPreTouch를 사용한 경우입니다. 하드웨어 성능에 따라 차이가 클 수 있겠지만 분명한 것은 AlwaysPreTouch를 켜면 꺼졌을 때보다 초기화 시간이 훨씬 길어진다는 점입니다.

$ time java -Xms512g -Xmx512g -XX:+AlwaysPreTouch EmptyApp

실행 시간: 약 14분 소요 → 위와 동일한 환경에서 -XX:+AlwaysPreTouch까지 주었다면 JVM은 512GB 전체를 미리 접근하게 됩니다. 4KB 페이지 기준으로 계산하면 512GB는 1억 34천만 개 이상의 페이지에 해당하므로, 이 모든 페이지를 순차 접근하는 데 상당한 시간이 소요됩니다.

 

위 결과를 정리하면, AlwaysPreTouch를 끈 경우 초기화 시간은 짧지만 런타임 중에 점진적으로 메모리를 할당하게 되고, 켠 경우 초기에 오래 걸리지만 이후에는 메모리 할당으로 인한 지연이 없다는 차이가 있습니다. 결국 “시간을 언제 지불하느냐"의 문제라고 볼 수 있습니다. AlwaysPreTouch는 메모리 비용을 애플리케이션 시작 시 한꺼번에 지불하여, 실행 중에는 더 이상 지연 비용을 남겨두지 않는 전략입니다.

 

3. 2 NUMA 환경에서의 메모리 분포

NUMA 멀티 프로세서 시스템에서는 AlwaysPreTouch의 이점이 또 있습니다. 바로 메모리의 NUMA 분산 할당입니다. 예를 들어 2 CPU 서버에서 64GB 힙을 사용한다고 합시다. AlwaysPreTouch가 꺼져 있다면, 힙 메모리를 처음 할당하는 주체(예를 들어 GC 쓰레드 등)에 따라 64GB 중 대부분이 한 노드의 메모리로 잡힐 수 있습니다. 만약 초기에 GC나 힙 할당을 담당한 쓰레드들이 모두 특정 CPU에 있었다면, 나중에 다른 CPU 코어들이 힙을 사용할 때 원격 메모리 접근이 빈번해져서 메모리 지연이 증가할 수 있습니다.

 

반대로 AlwaysPreTouch를 켜두면, JVM이 힙을 미리 할당할 때 내부적으로 여러 쓰레드를 활용하여 병렬로 페이지를 할당합니다. 이렇게 되면 각 쓰레드가 속한 NUMA 노드에 힙 일부가 할당되므로, 결과적으로 힙 메모리가 각 노드에 거의 균일하게 분포될 가능성이 높습니다. 이후 애플리케이션이 실제로 양쪽 CPU를 모두 활용하더라도, 메모리 접근의 상당 부분이 로컬 노드에서 이루어져 메모리 대역폭과 지연 측면에서 유리합니다. 물론 JVM이 자동으로 해주지 못하는 더 정교한 제어가 필요하다면, -XX:+UseNUMA 옵션이나 NUMA-aware 프로그래밍 기법을 병행해야 하지만, AlwaysPreTouch만으로도 기본적인 메모리 초기 분포를 잡아준다는 점은 의미가 있습니다.

 

4. 결론: 언제 AlwaysPreTouch를 활용할 것인가


정리하자면, -XX:+AlwaysPreTouch 옵션은 힙 메모리를 미리 준비시켜놓고 시작하겠다는 설정입니다. 서버 애플리케이션의 안정적인 메모리 지연을 확보하고 싶을 때, 특히 대용량 힙멀티코어/NUMA 환경에서 고려해볼 만한 옵션입니다. 초기 부하와 메모리 사용량 증가라는 비용이 따르지만, 그 대가로 런타임 성능의 예측 가능성최대 지연 시간 단축이라는 이득을 얻습니다.

 

If you add -XX:+AlwaysPreTouch to the JVM arguments, the JVM will pre-touch the entirety of the heap at startup time to avoid unpredictable page faults through the life of the application. (출처)

 

 

수십 밀리초의 GC pause가 중요한 시스템이나, 자바 애플리케이션의 워밍업 단계 지연을 없애고 바로 안정된 성능을 내야 하는 경우 (예: 금융권 트레이딩 시스템, 대규모 메모리 캐시 서버 등) AlwaysPreTouch를 적극 활용할 수 있습니다. 실제로 ElasticSearch, Cassandra 등의 일부 서버 솔루션 튜닝 가이드에서도 큰 힙을 쓸 때 AlwaysPreTouch 사용을 권장하기도 합니다 (페이지 폴트로 인한 응답 지연을 피하기 위해).

 

반대로 애플리케이션 규모가 작거나, 빠른 시작이 더 중요하거나, 메모리 사용 패턴이 유동적인 경우에는 굳이 이 옵션을 사용해 초기 지연을 늘릴 필요가 없습니다. “필요한 경우에만 선별적으로” 적용하는 것이 좋습니다. 예컨대 개발 환경이나 테스트 단계에서는 켜지 않고, 운영 환경에서만 켜서 사용하는 식으로 구분할 수도 있습니다.

 

마지막으로, AlwaysPreTouch를 사용할 때는 시스템의 물리 메모리 충분성을 항상 염두에 둬야 합니다. 이 옵션을 켰는데 물리 메모리가 부족하면 JVM이 시작 단계에서부터 메모리 확보에 실패하여 프로세스가 뜨지 않거나, 운영체제가 스왑을 사용하는 바람에 오히려 성능이 악화될 수 있습니다. 따라서 힙 크기와 시스템 RAM 용량, 그리고 다른 프로세스의 메모리 사용까지 고려하여 설정해야 합니다.

복사했습니다!