이 문서의 목적은 Java 로 작성된 프로그램을 오랜 기간동안 수행하면서 발생할 수 있는 기술적 이슈에 대응하는데 필요한 기본 지식을 정리하는데 있다.
모든 내용을 정리하기 보다는 필요한 내용만을 선별해서 요약한다.
HotSpot VM 기본
JDK 1.2 부터 HotSpot VM 이 제공됐다.
Java HotSpot VM 은 "Hot" 메소드를 선택적으로 컴파일해서 성능을 높이는 기술을 채택한 VM 이다.
Client VM 과 Server VM 으로 나뉘며, Client VM 은 초기 시작시간, 총 사용 메모리에 중점을 두고, Server VM 은 전체적인 실행속도에 중점을 둔다.
Client VM 과 Server VM 은 기본적인 런타임 코드는 공유하며, HotSpot 영역을 컴파일하는 컴파일러만 다르다.
Client VM 과 Server VM 은 다음과 같은 조건에 의해 선택된다.
Server VM
물리적 CPU 가 2개 이상이고, 물리적 메모리가 2GB 이상이거나,
-server 옵션을 명시적으로 사용했을 때
Client VM
물리적 CPU 와 물리적 메모리 요건이 Server VM 기준을 만족못하거나,
-client 옵션을 명시적으로 사용했을 때
Lager Heap Size
Solaris 의 경우 Solaris 9 과 JDK 1.4.1 이상을 쓴다면 MPSS (Multiple Page Size Support) 기능을 지원받아서 Virtual Memory 이용효율을 높일 수 있다. MPSS 를 이용하지 못하는 경우에는 ISM 기능을 쓸 수 있다.
64bit JVM 을 이용하면 보다 넓은 Heap 공간을 쓸 수 있다. 이 글을 쓰는 시점에서 6.0 버전에 대해 64bit JVM 이 제공되는 OS 는 다음과 같다.
Solaris (Sparc, x86, x64)
Linux (x86, x64)
Windows (x86, x64)
AIX (ppc)
HP (Integrity, PA-RISC,
Heap Generations
Java HotSpot VM 은 3개의 Generation 으로 Heap 을 구성하는데, Young, Old, Permanent 가 그것이다. 3가지로 구분하는 것은 각 Generation 별로 최적의 GC algorithm 을 적용하는 것이 더 효과적이기 때문이다.
Young Generation
Young Generation 에는 최초로 인스턴스가 생성되는 Eden 영역과 Eden 에서 살아남은 인스턴스들이 잠시 거쳐가는 From 과 To 영역이 있다. From 과 To 에서는 오래 유지되거나 너무 큰 인스턴스는 Old Generation 으로 옮겨진다. Eden 에서도 크기가 너무 큰 인스턴스의 경우 바로 Old Generation 으로 옮겨지기도 한다.
Old Generation
Old Generation 에는 오래 유지되는 인스턴스가 저장된다.
Permanent Generation
클래스, 메소드 등의 데이터가 인스턴스 형태로 저장된다.
Garbage Collector
상황에 따라 다음의 선택사항들중 하나가 선택된다.
Serial vs Parallel
Concurrent vs Stop-the-world
Compacting vs Non-compacting vs Copying
Serial Collector
Server 급이 아닌 환경에서 자동으로 선택된다. 명시적으로 지정하려면 -XX:+UseSerialGC 옵션을 이용한다. 클라이언트에서는 대부분의 경우 적합한 Collector 이다.
Young Generation
GC 될 때 Eden 에서 Garbage Collection 되지 않고 살아남은 인스턴스는 Surivvor Space 인 To 로 옮겨진다. To 에 충분한 공간이 없을 경우에는 바로 Old Generation 으로 옮겨진다. From 에서도 Gargabe Collection 이 연이어 일어나며, 여기에서 살아남은 인스턴스는 Surivvor Space 인 To 로 옮겨진다. 만약, 인스턴스의 생성시기가 기준에 비추어 오래되었거나 To 에 충분한 공간이 없을 경우에는 바로 Old Generation 으로 옮겨진다. Garbage Collection 이 끝나면 From 과 To 의 역할이 서로 바뀐다.
Old Generation and Permanent Generation
쓰이고 있는 인스턴스들을 Mark 한 다음, Mark 되지 않은 Garbage 들을 청소하고, 인스턴스들을 Compaction 하는 처리가 이루어진다.
Parallel Collector
CPU 가 2개 이상이고, 물리 메모리가 많은 Server 급 환경에서 자동으로 선택된다. 명시적으로 지정하려면 -XX:+UseParallelGC 옵션을 이용한다. GC 시간이 약간 길어도 별로 상관이 없는 실행환경에 적합하다.
Young Generation
Serial Collector 와 동일한 algorithm 으로 Gargabe Collection 이 일어나며, 동시에 2개 이상의 프로세서가 있을 때의 잇점을 이용한다.
Old Generation and Permanent Generation
Serial Collector 와 동일하다.
Parallel Compacting Collector
CPU 가 2개 이상이고, 물리 메모리가 많은 Server 급 환경에서 GC 시간이 너무 길지 않아야 하는 실행환경에 적합하다. 명시적으로 지정하려면 -XX:+UseParallelOldGC 옵션을 이용한다.
Young Generation
Parallel Collector 와 동일하다.
Old Generation and Permanent Generation
Parallel Collector 를 쓸 때 Garbase Collection 이 일어나면 3가지 상태로 진행된다.
첫 번째 상태는 marking phase 로 Application 실행을 잠시 멈추고, Application 에서 바로 접근가능한 인스턴스부터 시작해서 여러 스레드들에 의해 각각의 인스턴스가 사용중에 있음을 표시한다.
두 번째 상태는 summary phase 로 Heap 에는 왼쪽 영역으로 compaction 된 상태에서 또 다시 Garbase Collection 이 시작된 것이므로, 건드릴 필요가 없는 맨 왼쪽 포인트를 먼저 확인한다. 그리고, 거기서부터 오른쪽으로 조사하면서 살아남은 인스턴스들을 조사한다. 이 상태에서는 Application 실행을 잠시 멈춘 상태에서 하나의 스레드로만 작업을 하는데, 여러 스레드를 사용해서 얻는 이득이 별로 없기 때문이다.
세 번째 상태는 compaction phase 로 summary phase 에서 알아낸 정보를 토대로 인스턴스들을 왼쪽으로 최대한 채워서 오른쪽에 최대한 연속된 빈 공간을 만든다. 이 때에도 Application 실행이 잠시 멈춘다.
Concurrent Mark-Sweep Collector (CMS)
Application 실행속도 보다는 GC 시간을 줄이는 것이 더 좋은 실행환경에 적합하다. 하지만, Heap 크기가 충분히 큰 환경이어야 한다. 명시적으로 지정하려면 -XX:+UseConcMarkSweepGC 옵션을 이용한다. 추가로 incremental mode 를 지정하려면 +XX:+CMSIncrementalMode 옵션을 이용한다.
Young Generation
Parallel Collector 와 동일하다.
Old Generation and Permanent Generation
다음과 같은 순서로 작동된다.
처음 상태는 initial phase 로 Application 을 실행된 후에 최초로 한 번 실행되며, Application 실행을 잠시 멈추어서 Application 에서 바로 참조가능한 Instance 의 집합을 파악해둔다.
다음 상태는 mark phase 로 Application 실행 중 파악된 Set 의 변경사사항을 추적한다.
다음 상태는 remark phase 로 Set 에서 추적되지 않은 사항들을 알아내기 위해 Application 실행을 잠시 멈추고, Set 에 포함되지 않은 인스턴스들을 모두 추적한다.
다음 상태는 concurrent sweep phase 로 이 때에는 Application 실행이 재개되며, remark phase 에서 파악된 Garbage 들을 청소하는 스레드들도 같이 실행된다.
CMS 에서는 Compation 을 하지 않기 때문에 다음 그림과 같이 Heap 에 Fragmentation 이 발생한다. 이 빈 공간들은 리스트로 관리되어 운용되는데, 이로 인해 Application 실행속도가 다른 Collector 보다 저하된다. 또한, 메모리 할당을 할 때 충분히 큰 빈 공간이 없으면 Heap 이 계속 커져야 하거나 MemoryOutOfError 가 발생할 수 있으므로, 인접한 빈 공간을 리스트 상에서 합치거나 사용형태에 따라 적절히 빈 공간을 분리해서 낭비를 없애는 작업을 한다.
다른 Collector 와는 달리 Old Generation 이 꽉 찼을 때 GC 가 바로 실행되는 것이 아니라 꽉 차기 전부터 지속족으로 GC 가 실행된다. 만약, 꽉 차게 되었을 때에는 Serial Collector 나 Parallel Collector 와 같은 GC 를 실행하게 된다.
Stack 과 Heap 사이즈 지정
Thread Stack Size
JDK 1.3 까지는 -Xss 와 -Xoss 옵션이 별도로 있어서 각각 native thread stack size 와 java thread stack size 를 지정할 수 있었다.
현재는 Java Thread Stack size 와 Native Thread Stack Size 의 구분이 없이 -Xss 옵션만 존재하며, 하나의 Stack 을 이용해서 처리된다. 그래서, 현재는 -Xoss 옵션은 없어졌거나, AIX 같은 경우 하위호환을 위해 남겨두었으나 그 기능은 -Xss 와 동일하다.
Heap
-Xmx 로 최대치를, -Xms 로 최소치를 지정한다.
Young Generation
-XX:NewSize 로 최초 실행시의 값을 지정한다.
Young Generation 크기 비율
-XX:NewRatio 로 지정하며, Old Generation 에 대한 Young Generation 의 상대비율을 지정하는 형식이다. 이 값이 3이면 전체 Heap 영역에서 Young Generation 은 1/4 를 차지하며, Old Generation 은 3/4 를 차지한다.
Survivor 크기 비율
-XX:SurvivorRatio 로 지정하며, Eden 에 대한 Survivor 의 상대비율을 지정하는 형식이다. 이 값이 7이면 전체 Young Generation 영역에서 Survivor 가 1/9 씩 두 개가 할당되며, Eden 으로 7/9 가 할당된다. (Survivor 는 From 과 To 의 2가지가 있다.)
Permanent Generation 최대 크기
–XX:MaxPermSize 로 지정하며, 클래스가 로딩되면서 Permanent Generation 이 늘어나는 것의 최대치를 지정한다. Java 5 에서는 기본크기가 64m 이며, 64bit JVM 에서는 30% 더 크다.
JVM 실행중 문제가 발생했을 때의 대처법
JVM 이 실행중 문제가 발생했을 때 다음과 같은 옵션의 사용을 고려해볼 수 있다.
-XX:+HeapDumpOnOutOfMemoryError
OutOfMemoryError가 발생했을 때 Java Heap 을 파일에 덤프해준다. 기본으로 꺼져있다.
-XX:OnOutOfMemoryError="<cmd args>;<cmd args>"
OutOfMemoryError가 최초 발생했을 때 실행할 스크립트를 복수개 설정할 수 있다.
-XX:OnError="<cmd args>;<cmd args>"
JVM 에 fatal error 가 발생했을 때 실행할 스크립트를 복수개 설정할 수 있다.