본문 바로가기

카테고리 없음

Java의 동작 방식과 JVM의 구조 (메모리)

 

메모리 ( Runtime Data Area )

JVM은 Java 프로그램의 실행을 담당하며, 이를 위해 Runtime Data Area라고 불리는 메모리 영역을 제공합니다.

Runtime Data Area는 JVM이 프로그램을 실행하는 동안 데이터와 코드를 저장하고 관리하는 중요한 구성 요소입니다.

메모리는 복잡해 보일 수 있는데 크게 다섯가지 영역으로 나눠져 있습니다. 각각의 영역을 살펴보도록 하겠습니다.

 

https://gngsn.tistory.com/252

 

 

 

[ Method Area ]

 

메소드 영역은 클래스 로더에 의해 로드된 클래스 파일의 바이트 코드와 메소드 정보, 상수 풀(Constant Pool) 등을 저장하는 영역입니다. (클래스 별 구조와 정보를 저장하는 영역)

 

  • 클래스/인터페이스/인스턴스 초기화에 사용되는 스페셜 메서드
  • 런타임 상수 풀, 필드/메서드 데이터
  • 생성자/ 메서드 코

 

이 영역은 JVM이 시작될 때 생성되며, 모든 스레드에서 공유되는 데이터를 저장합니다.

  • 메소드 영역에는 클래스 수준의 정보 (풀패키지 경로를 포함한 클래스 이름, 부모 클래스 이름, 메소드, 변수) 저장.
  • 공유 자원이다. (다른 영역에서도 참조할 수 있는 정보라는 뜻)

 

 

 

 

 

[ Method Area - Constant Pool ] 

 

  • 클래스/ 인터페이스가 로딩될 때 메서드 영역에 할당되는 자료구조
    • 컴파일 시 '.class' 파일에 생성되는 일반 상수 풀의 런타임 표현
  • 일반 상수 풀의 데이터를 기반으로 생성되며 스태틱 상수와 심볼릭 레퍼런스(또는 실제 참조)등을 포함
    • 상수 뿐 아니라 메서드, 필드 참조까지 여러 종류의 상수가 포함됨
  • 일반 프로그래밍 언어의 심볼 테이블과 유사하지만 그보다 더 넓은 범위에 데이터를 포함함
    • 실볼 테이블은 컴파일러/ 인터프리터가 프로그램을 분석/처리 시 사용하는 자료구조이며 코드의 식별자/상수/프로시저/함수 등과 관련된 정보를 저장함
  • Method Area 영역에서 허용 가능한 메모리를 초과하면 OutofMemoryError 발생

 

 

 

[ Heap Area ]

 

힙(Heap)은 동적으로 생성된 객체와 배열을 저장하는 영역으로, 프로그램에서 사용하는 모든 객체가 여기에 할당됩니다. 힙은 JVM의 가비지 컬렉션(Garbage Collection)에 의해 관리되며, 객체의 생성과 소멸을 관리하여 메모리를 효율적으로 사용합니다.

  • 힙 영역에는 객체를 저장. 공유 자원이다.
  • 모든 쓰레드에 공유되는 영역입니다.
  • GC가 처리되는 영역입니다.
  • 특정 스토리지 시스템에 종속적이지 않은 구조입니다.
  • 힙의 크기는 상황에 따라 고정/확장/축소 될 수 있습니다.

 

 

 

[ Stack ]

 

스택(Stack)은 각 스레드마다 생성되는 영역으로, 메소드 호출과 관련된 정보를 저장합니다. 

스택은 메소드 호출 시마다 스택 프레임(Stack Frame)을 생성하여 호출된 메소드의 로컬 변수, 매개변수, 임시 데이터 등을 저장합니다. 스택은 메소드 호출이 완료되면 해당 프레임을 제거하여 메모리를 반환합니다.

 

스택 영역에는 쓰레드 마다 런타임 스택을 만들고, 그 안에 메소드 호출을 스택 프레임이라 부르는 블럭으로 쌓는다. 쓰레드 종료하면 런타임 스택도 사라진다. (에러 메세지 로그가 스택으로 나오는데 이때 보여지는 것이 스택에서 나오는 것이다.)

 

 

 

 

[ Stack - Frame ]

 

스택에 생성되는 메서드 관련 정보 저장 단위 (Stack Frame, Activation Record)

메서드가 호출될 때 생성되며 종료되면 소멸됨.

 

  • 지역변수 배열 (Local Variables) - 매개 변수와 지역 변수 저장
    • 지역변수 배열의 길이는 컴파일 타임에 결정, 관련 메서드, 클래스 등의 정보와 함께 바이트 코드로 제공됨
    • 지역 변수는 원사 타입과 reference, returnAddress 등이 한 곳(slot)에 저장되며 특히 8바이트인 long, double 타입은 연속된 두 곳(slot)에 저장됨
    • 지역 변수 배열의 첫번째 인덱스는 0이며 array.length-1 까지 유효한 인덱스 범위로 간주
    • JMV에 의해 메서드 호출 시 매개 변수 (또는 Args)는 지역 변수 배열에 담겨 전달
      • 인스턴스 메서드 호출 시에 지역 변수 0은 인스턴스 메서드 객체 참조인 this를 전달하는데 사용 그 이후 1부터 모든 매개변수들이 표현됨
  • 오퍼랜드 스택 (Operand Stack) - 실행 중간 연산 결과 등을 임시로 저장
    • 일반적인 LIFO 방식의 스택으로 최대 길이는 컴파일 타임에 결정되며 관련된 메서드 코드와 함께 제공되며 최초 생성 시 오퍼랜드 스택은 비어있음
    • 지역 변수 배열과 다르게 인덱스가 아닌 push/pop을 할 수 있는 명령어에 의해 엑세스 가능
    • 저장되는 오퍼랜드의 타입은 JVM에서 제공하는 모든 타입의 값
    • JVM은 지역 변수, 필드의 값을 오퍼랜드 스택으로 로딩하는 명령 제공하며 다른 명령들을 통해 오퍼랜드 스택에서 값을 가져와 연산, 결과를 다시 저장함
  • 프레임 데이터 (Frame Data) - 반환 주소 등 기타 데이터 저장 
    • 연관된 메서드들의 심볼릭 레퍼런스와 메서드 반환에 필요한 데이터 저장
      • 예외가 발생한 경우 catch 블록 정보를 제공하는 Exception 테이블 참조 포함

 

 

 

 

 

[ 메모리 (Runtime Data Area) - PC Register]

 

PC 레지스터(Program Counter Register)는 각 스레드마다 현재 실행 중인 명령어의 주소를 저장하는 영역입니다. PC 레지스터는 JVM이 다음에 실행할 명령어를 가리키며, 명령어의 실행 위치를 제어합니다.

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

 

 

 

[ 메모리 (Runtime Data Area) - Native Method Stack ]

 

  • 쓰레드 마다 생기는데 네이티브 메소드 호출할 때 사용하는 별도의 메소드 스택이다.
  • Native Method는 다른 프로그래밍 언어로 구현된 자바 메소드입니다. 가령 C 나 C++ 등으로 작성될 수 있습니다. 
  • 해당 메모리 영역은 이러한 native method들의 정보를 담는 역할을 합니다. 
    • 네이티브 메소드 라이브러리를 쓰려면 JNI를 통해야하는데 JNI를 사용하는 메소드 스택은 네이티브 메소드 스택에 저장됩니다.
    • JNI가 구현된 것을 네이티브 메서드 라이브러리라고 보면 된다.
    • 라이브러리는 항상 JNI를 통해야 한다.
    • 네이티브 메서드라고 함은 메소드에 native라고 붙어있고 구현을 java가 아닌 c나 c++로 구현한 것이다.
    • https://javapapers.com/core-java/java-jvm-run-time-data-areas/#Program_Counter_PC_Register

 

 

 

힙영역과 메소드 영역은 모드 쓰레드에서 공용으로 사용 가능하며,
Stack,  pc register,  Native method stack 은 특정 쓰레드에 국한된 정보입니다.

 

 

 

[ 메모리 (Runtime Data Area) - Execution engine ]

 

  • 인터프리터: 바이트 코드를 한줄 씩 실행.
    • 바이트코드는 인터프리터가 이해할 수 있습니다. 한줄 한줄 실행하면서 네이티브 코드로 바꿔줍니다.
    • 한줄 마다 바이트 코드를 네이티브 코드로 컴파일 해서 실행하는 것입니다.
    • 똑같은 코드가 여러개 나와도 매번 네이티브 코드로 바꾸는 것이 비효율 적이기 때문에 반복적인 코드가 나오면 JIT 컴파일러로 보냅니다.
    • JIT 컴파일러는 자바 코드를 바이트 코드로 바구는 것이 아닌 바이트 코드를 네이티브 코드로 바꿔주는 것이며 반복된 코드를 찾아 미리 네이티브 코드로 바꿔주고, 인터프리터가 해당 라인에 걸리면 JIT에서 바꿔놓은 네이티브 코드를 사용하여 프로그램 효율을 높여주는 것입니다.
  • JIT 컴파일러: 인터프리터 효율을 높이기 위해, 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러로 반복되는 코드를 모두 네이티브 코드로 바꿔둡니다. 그 다음부터 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용합니다.
  • GC(Garbage Collector): 더이상 참조되지 않는 객체를 모아서 정리합니다.
    • GC 개념은 이해를 해야되는 것은 당연하며 경우에 따라 옵션을 변경해야 할 때도 있습니다. (customizing)
    • 프로젝트에 사용하는 GC를 알아야 하며, 어플리케이션 실행시 사용할 GC를 선택해야 할 때도 있습니다.
    • 가장 크게 GC를 둘로 나누면 throughput 위주의 GC 가 있고, stop the world를 줄이는 GC가 있습니다.
    • 서버 운영중에 굉장히 많은 객체를 사용하고 response time이 중요하다 -> stop the world를 최소화 할 수 있는 gc사용하는 것이 맞습니다.

 

 

 

[ JNI (Java Native Interface) ]

 

 

 

 

[ Native Method Library ]

  • JNI가 구현된 것을 의미합니다
  • 일반적으로 C 또는 C++로 작성 되어 있습니다.

 

 

 

 

 

JVM Run-Time Data Areas - 생성 시점 정리

 

  • JVM 실행 시
    • Heap
    • Method Area
  • 쓰레드 실행시
    • pc register
    • Java virtual Machine Stacks
    • Native Method Stacks (필요한 경우)
  • 클래스/인터페이스 생성 시
    • Run-Time constant Pool (Method Area에 저장)
  • 메서드 호출 시
    • Frame (Java virtual Machine Stacks에 저장)

 

 

 

JVM과 메모리 구조 (Stack Memory & Heap Memory)