개발자는 기록이 답이다

JVM이해하기 - JVM의 구조 본문

언어/Java

JVM이해하기 - JVM의 구조

slow-walker 2023. 11. 20. 16:52

 

클래스 로더 시스템

  • .class 에서 바이트코드를 읽고 메모리에 저장
  • 로딩: 클래스 읽어오는 과정
  • 링크: 레퍼런스를 연결하는 과정
  • 초기화: static 값들 초기화 및 변수에 할당
    • 아래 사진처럼 스태틱한변수, 스태틱 블럭을 사용해서 static 값을 할당하는데, 다른 클래스에서도 클래스 이름에 점 찍고 바로 참조해서 쓸 수 있는데, 이런 값을 초기화 하는 과정을 뜻한다.

메모리

  • 메소드 영역에는 클래스 수준의 정보 (클래스 이름, 풀패키지 경로, 부모 클래스 이름, 메소드, 변수) 저장. 공유 자원이다.
    • 다른 영역에서도 참조할 수 있는 정보
    • 아래 사진은 메소드 영역에 저장된 클래스가 명시적인 상속이 없더라도 오브젝트 클래스 상속을 받았기 때문에, 메소드 영역에 저장된 부모 클래스가 출력되는것을 볼 수 있습니다.

  • 힙 영역에는 객체,인스턴스를 저장. 공유 자원이다.
    • 명시적으로 String으로 만들거나, new 키워드로 객체를 생성하거나, 클래스 로딩만 해도 저런 객체들이 힙 영역에 저장된다

 

  • 스택 영역에는 쓰레드 마다 런타임 스택(runtime stack)을 만들고, 그 안에 메소드 호출을 스택 프레임이라 부르는 블럭으로 쌓는다. 쓰레드 종료하면 런타임 스택도 사라진다.
    • 스택 PC, 네이티브 메소드 스택은  Thread에 국한되서 특정 Thread에서만 공유 되고 힙, 메소드 영역처럼 다른 모든 영역에 공유하는 자원은 아니다.
    • stack frame은 method call이다. 그래서 error message를 볼 때 아래 사진처럼 method 호출 스택이 쭈욱 쌓여있는걸 볼 수 있다.
    • 이런 스택은 쓰레드 마다 하나씩 만들어지고, 그렇게 만들어진 스택에 메소드를 쌓았는데 현재 어느 위치를 실행하고 있는지 가르키는 PC 레지스터라는 것이 있다.

  • PC(Program Counter) 레지스터: 쓰레드 마다 쓰레드 내 현재 실행할 instruction의 위치를 가리키는 포인터가 생성된다.
  • 네이티브 메소드 스택

 

실행 엔진

  • 인터프리터: 바이트 코드를 한줄 씩 읽어서 네이티브 코드로 컴파일해서 실행

  • JIT 컴파일러: 인터프리터 효율을 높이기 위해, 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러로 반복되는 코드를 모두 네이티브 코드로 바꿔둔다. 그 다음부터 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용한다.
  • GC(Garbage Collector): 더이상 참조되지 않는 객체를 모아서 정리한다.
    • 인터프리터, JIT는 동작 원리만 이해하면 그만인데, GC는 경우에 따라 커스터마이징, 옵션을 조정해야 할때도 있다.
    • 실제 프로파일링할때도 우리가 사용하는 GC가 어떤 GC인지 또는 애플리케이션을 실행하기 전에 우리가 사용할 GC를 선택해야 되는 경우도 있다
    • 크게 GC를 둘로 나누면 이제 Throughput 위주의 GC, Stop the world를 줄이는 GC가 있다.
    • 서버 운영중에 굉장히 많은 객체를 생성하고 Response 타임이 굉장히 중요한 경우  Stop the world, GG를 발생할때 pause현상을 최소화 할 수 있는 GC를 사용하는게 좋다.
📕 정리 :

클래스 로더는 클래스 파일을 읽어와 메모리에 배치합니다. 이때, 힙이나 메소드 영역에 해당 클래스의 구조와 데이터가 적절히 배치됩니다. 프로그램이 실행될 때, 새로운 스레드가 생성되면 해당 스레드에는 Thread Stack, PC Register, Native Method Stack 등이 할당됩니다.

실행 엔진은 이러한 메모리 구조에서 바이트 코드를 한 줄 씩 해석하고 실행합니다. 이때, 코드의 일부는 스택 영역에 저장되고, 필요에 따라 스택에서 데이터를 추출하거나 새로운 데이터를 추가합니다. 그러나 한 줄씩 읽어나가는 방식은 효율적이지 않습니다. 이에 JIT(Just-In-Time) 컴파일러가 사용되어 중복되는 코드를 미리 기계어로 변환하여 최적화합니다.

또한, 실행 엔진은 메모리 최적화를 위해 사용되지 않는 레퍼런스를 찾아내고 정리하는 작업도 수행합니다. 이는 가비지 컬렉션을 통해 메모리 누수를 방지하고, 더 효율적인 자원 활용을 가능케 합니다.

요약하면, JVM은 클래스를 메모리에 로드하고, 스레드에 필요한 메모리를 할당한 후, 실행 엔진이 바이트 코드를 해석하고 최적화하는 일련의 과정을 통해 프로그램을 실행합니다. 이 과정에서 JIT 컴파일러와 가비지 컬렉션 등이 효율적인 실행을 지원합니다.

JNI(Java Native Interface)

네이티브 메소드 라이브러리

  • C, C++로 작성 된 라이브러리



참고