JVM
JVM(Java Virtual Machine)은 쉽게 말하면 '자바를 돌리기 위한 기계'라고 생각하시면 됩니다. 우선 JVM까지 가기 전에 저희가 짠 자바코드가 어떻게 실행되는지 알아봅시다.
출처 : http://www.ntu.edu.sg/home/ehchua/programming/java/j1a_Introduction.html
자바로 코드를 자면 Java Compiler에 의해 컴파일이 됩니다. 이를 통해 .java 파일이었던 것이 .class파일로 바뀌는데요. 이는 바이트코드로 바꿔주는 과정입니다. 저희가 짠 코드와 기계가 이해하는 코드의 중간 언어라고 볼 수 있습니다. 그리고 이 바이트코드가 JVM를 통해 기계가 이해하는 언어로 되는 것입니다.
사실 JVM만으로는 실행시킬 수 없습니다. JVM은 JRE(Java Runtime Environment) 안에 속해있구요. 다른 여러 라이브러리와 함께 가동함으로써 실제로 실행시킬 수 있습니다.
출처 : https://medium.com/@mannverma/the-secret-of-java-jdk-jre-jvm-difference-fa35201650ca
JVM 구성
그럼 JVM 안에는 어떻게 이루어져있을까요?
우선 JVM의 구성을 다음 3개의 영역으로 나눌 수 있습니다.
Class Loader
- 클래스 로더에서 Runtime Data Areas에 클래스들을 적재시킵니다.
Runtime Data Areas
위 그림에서 보는바와 같이 5개 영역으로 나뉩니다.
중요한 것은 쓰레드가 어떻게 쓰느냐인데요. 다음과 같이 분류할 수 있습니다.
모든 쓰레드마다 공유
- Heap, Method Area(=Static Area)
각각의 쓰레드로 사용
- Stack, Native Method, PC Registers
특히 중요한 부분은 Heap과 Stack입니다. Heap은 모든 쓰레드마다 공유된다는 점, 반면 Stack은 각각의 쓰레드로 사용되고 있다는 점을 꼭 기억해야 합니다.
이전에 java-was 프로젝트 중 제가 Heap영역에 보관하고 있는 변수(인스턴스 변수)가 있었습니다. 이 변수는 각각의 쓰레드마다 공유되어서는 안되는데 제가 미처 생각하지 못하고 인스턴스 변수를 만드는 바람에 결과가 이상하게 나왔던 적이 있습니다.
각 영역 중 중요한 영역에 대해 짧게 설명하겠습니다.
- Method Area(= Static Area) : 클래스에 대한 모든 정보를 담고 있습니다.
- Heap : GC(Garbage Collection)의 대상, 객체와 배열을 저장합니다.
- Stack : 메서드 관련한 일을 수행합니다. Static 메서드, 인스턴스 메서드 모두 여기서 실행됩니다.
Execution Area
- 위 Runtime Data Areas 영역의 byte code를 해석 / 실행합니다.
가비지 컬렉션(Garbage Collection)
자바는 이전의 C, C++과 달리 메모리 정리를 자동으로 해줍니다. 그럼 정리 대상과 기준이 무엇인지 알아야겠죠?
- Heap에서 더이상 참조되지 않는 객체
- Stack에서 도달불가능한(Unreachable) 객체
한 줄로 다음과 같이 설명할 수도 있다고 하네요
Mark & Sweep & Compact
즉, reachable한 것을 표시해두고 unreachable한 것을 뽑아내어 compact하게 만든다는 뜻이죠!
하지만 GC가 무조건 좋은 것만은 아닙니다. 어디든 trade off가 따르긴 마련이죠. GC를 사용할 때는 애플리케이션이 정지됩니다. 꼭 필요한 작업이긴 하지만 최소한 사용하면서 가장 필요한 시점에 사용하는 것이 좋습니다. 강제적으로 사용할 때는 system.gc()
라는 명령어를 사용할 수도 있다고 하는데요. 엄청 나게 느려진다고 합니다. 그리고 안 먹힐 수도 있다고 하네요. 왜냐하면 그냥 실행제안을 하는거라서요..
GC는 다음과 같이 구성되어 있습니다. 순서를 간략하게 설명해볼게요.
우선 처음 객체가 생성이 되면 Eden에서 만들어집니다.
Eden의 공간이 꽉차게 되면 Survivor1이나 Survivor2 공간 중에 빈 공간으로 이동합니다.
- 이 때 Unreachable한 객체는 GC의 대상이 되어 없어집니다.
- Survivor1과 Survivor2 중 하나는 비어 있습니다. 만약 이번에 Survivor1로 모아졌다면 다음엔 Eden이 꽉찼을 때 Survivor1에 있는게 Survivor2로 Eden에서 살아남은 객체와 함께 이동하는 것이죠.
Survivord에서 Reachable 한 객체는 오래 살아남을텐데요. 사실 Survivord로 한번씩 이동할 때마다 age가 +1이 되기 때문에 만약 특정 age가 되면 해당 객체는 Tenured영역으로 이동하게 됩니다.
위 영역에서 발생하는 GC를 아래와 같은 용어로 설명됩니다.
- Minor GC : Young Generation에서 발생하는 GC(Eden + Survivor 에서 발생하는 GC)
- Major GC : Old Generation에서 발생하는 GC(Tenured에서 발생하는 GC)
- Full GC : 전체 GC
제가 생각하기엔 GC도 '시간 지역성'의 개념을 사용하고 있는것 같습니다. 아무래도 최근에 사용된 것은 다시 재사용될 가능성을 염두해두고 저렇게 설계된 것이라 생각합니다.
GC튜닝은 GC 상황을 모니터링 하면서 GC의 주기 & 처리 시간을 확인하며 최적의 GC타이밍을 정해야합니다. 사실 쉽지 않죠. GC 성능은 자바 버전이 올라감에 따라 계속 업그레이드 되어 왔었는데요. 자바가 버전이 높아짐에 따라 다양한 기능이 추가되었지만 GC성능 개선된 것도 참 중요한 부분이라고 합니다.
Serial → Parallel → CMS → G1
지금까지 JVM에 대해 정리해보았는데요. Java의 JVM이 이렇게 잘 설계되어있다보니 사실 다른 언어(Kotlin, Groovy, Clojure 등) 들도 따로 JVM와 같은 것을 각각 만들 필요없이 각자 언어를 컴파일하여 바이트코드로 만든 후 JVM에 돌려서 사용한다고 합니다.
'Language > Java' 카테고리의 다른 글
제네릭(Generic) (0) | 2018.11.23 |
---|---|
예외처리 (0) | 2018.10.25 |
enum (2) | 2018.10.22 |
인터페이스(Interface) (0) | 2018.10.15 |
상속(Inheritance) (3) | 2018.10.11 |