JVM 이란

2020. 10. 30. 16:42programming

 

jvm은 간단히 얘기하면 java 실행 환경 이다.

이것을 아는 것은 왜 중요할까? 이는 jvm 이전의 프로그래밍 언어의 패러다임과 관련이 있다. 이전의 언어는 하드웨어를 직접 제어하기도, 메모리를 직접 관리하기도 했다. 이는 곧 특정 OS에 설치된 라이브러리에 의존하는 코드가 나오게 되고, OS가 바뀌거나 버전이 업그레이드 되면 영향을 받을 수 밖에 없다.

이 와중에..

Write Once, Run anywhere

라는 유명한 말과 같이 Java가 세상에 나오게 되며, 바로 저 말의 이면에는 JVM이 있는 것이다.

그렇다. 그 당시에 저 개념은 기존 소프트웨어 개발자들에게는 충격이었다.

 

이를 가능하게 하는 JVM은 개발자는 application 코드를 작성하고, 나머지 저수준의 메모리 관리 등은 가상머신으로 감싸고, 그 위에 독립된 인터페이스를 두어 application 코드가 JVM만 있으면 되게 만들어 버린 개념인 것이다.

그럼 조금 더 자세하게, 자바 코드를 작성해서 실행되는 과정을 아래의 이미지와 함께 추적해 보겠다.

 

JVM 구조

1. 먼저 java 파일을 만들 것이다. 그것이 hello world application 이든, 더 복작한 application이든 간에.

2. 이 java 파일은 java compiler로 인해 .class 파일로 만들어 진다. JVM이 읽을 수 있는 바이트 코드로 변환 시키는 것이다.

3. 이 .class파일은 JVM의 클래스로더에 의해 로드 된다.

- Runtime시에 동적으로 로딩된다.

- 로딩되는 것은 클래스의 구조와 같은 메타 데이터(필드, 메소드, 타입, 상수, static 정보 등) 이며, 이는 Runtime Data Area의 Method Area에 저장된다.

4. 저장된 클래스 정보를 실행시키기 위해서, Execution Engine이 실제 실행 가능한 바이너리 코드로 변환하는 역활을 수행한다.

- Interpreter 방식 : 한 줄씩 수행

- JIT(just-in-time) 방식 : 바이크 코드를 네이티브 코드로 변환/캐쉬하는 방식

5. Execution Engine은 클래스로더의 작업 후, application 실행을 위한 모든 것 (크게 메모리 관리와 그 이외의 것)을 관리한다.

6. 클래스가 new 객체로 생성이 되면 저장된 클래스의 메타 정보를 통해 Runtime Data Area의 Heap영역에 해당 클래스의 객체를 생성한다. (배열도 이 영역이다.)

7. 생성된 객체의 메소드를 호출하면, 해당 메소드 영역의 로컬 변수, 정보, 데이터 등은 모두 Runtime Data Area의 Stack에 임시 저장되었다가, 해당 스코프가 종료되면 삭제 된다.

8. PC Resister 영역은 Thread가 시작될 때 생성되는 공간으로, 스레드마다 하나씩 존재한다. Thread가 어떤 부분을 어떤 명령으로 실행해야할 지에 대한 기록을 하는 부분으로 현재 수행중인 JVM명령의 주소를 갖는다. 만약 Native Method를 수행한다면 PC Register은 Undefined상태가 된다.

9. Execution Engine은 더이상 사용되지 않는 Heap 메모리(Method Area도 대상-JVM밴더에 따라 아닐수도 있다고 한다. 음..할게 많을거 같은데..)를 GC를 통해 제거 한다.

10. 자바 네이티브 코드(JNI)를 위한 메모리 영역으로 Native Method Stack 존재.

11.결국 Thread를 공유하는 곳은 Method Area와 Heap 이다. 나머지는 Thread별로 존재 한다.

 

이제는, 저 Runtime Data Area중, 개발자에게 가장 많은 고민을 안겨주는 Heap과 Method Area영역을 조금만 더 자세히 보겠다.

(쓰레드를 공유하기 때문에 기본적으로 라이프 사이클이 길고, 그로 인한 메모리문제에 직접적인 연관이 있기 때문이다.)

 

Heap&Perm Memory Area

 

객체가 생성되고 소멸되는 과정을 보면,

 

1. Young Generation 영역

1) 새로 객체를 생성하면 eden 영역에 생성된다.

2) eden영역이 가득 차면, from 영역에 값을 복사 하고, eden 영역의 객체를 지운다.

3) eden영역과 from(survivor 0)영역이 일정치 수준을 넘어서면, 아직 참조 중인 객체를 조사하여, to (survivor 1)영역으로 복사한다.

4) to 영역을 제외한 영역의 객체를 지운다.

 

2. Old Generation 영역

1) 일정 시간 이상 참조 되는 객체들을 Tenured 영역으로 이동

2) Old 영역에 있는 모든 객체를 조사

3) 참조되지 않는 객체는 모두 삭제

 

1번의 GC는 minor 버전, 2번은 major 버전(Full GC)이라 하며, 특히 2번은 실행 시간도 길고 무겁다. 결정적으로 앱의 stop-the-world를 발생시킨다.

 

여기서 재미있는 것은 Method Area의 일부인 Permanent Generation 영역인데, 이 영역은 로드되는 클래스의 메타 정보(구조 정보) 및 static변수 등을 저장하는 곳이라는 것을 이미 알고 있다. 또한 이 영역은 리플렉션을 이용한 인스턴스 생성에도 이용 된다. 그리고, 신규 생성되는 String pool 저장소 이기도 하다.(""로 만들어지는 String은 자동으로 interned (string.intern()) 되어 Permanent Generation에 생성 됨).

마지막으로, 이 영역은 JVM시작 옵션으로 크기를 지정할 수는 있지만, 한 번 지정되면 고정 크기를 가진다.

이제 여기가 왜 재미있냐고 하면, 고정 크기 Perm Size -> spring framework + 라이브러리 등에 의한 클래스 증가, 프레임 워크내 리플렉션을 이용한 지속적인 동적 클래스 로딩 + application에서의 동적 클래스 로딩 + String 남발 -> Out of Memory!

그렇다. 이 영역은 결국 지금과 같은 framework 기반 세상에서는 이전보다 더 쉽게 Out of Memory로 빠질 수 있는 여지가 있는 곳이 되었다.

 

그래서 자바 8에서는 메모리 구조가 아래와 같이 바뀌게 된다.

 

 

보다 시피, Method Area, 즉 Permanent Generation 영역이 사라졌다. 대신, Metaspace라는 새로 정의된 메모리 영역으로 이동, 메모리가 부족할 경우 동적 할당이 가능하게 되었다. (모든 기존 Perm영역의 할당이 옮겨 진 것은 아니라고 한다. 일부는 Heap 영역등으로 저장 위치가 바뀌게 되었다.)

이제 8이상에서는 -XX:PermSize, -XX:MaxPermSize 옵션을 사용할 필요가 없다. 대신, MetaspaceSize, MaxMetaspaceSize 를 지정할 수 있다.

 

이는 결국 메모리의 효율화와 성능 이슈로 이어지게 되며, 우리가 8이상을 써야 할 이유가 되기도 한다.

 

'programming' 카테고리의 다른 글

Java 8 (Spider)  (0) 2020.11.27
java 버전별 특징(1.0~1.7)  (0) 2020.11.25
비트마스크  (0) 2020.10.19
객체지향 개발 원칙  (0) 2020.10.16