자바 메모리구조 - JVM

|

자바 메모리 구조

  • JVM이란?
    • JVM(Java Virtual Machine, 자바 가상 머신)은 자바 바이트 코드를 실행할 수 있는 주체이다.
  • JVM의 역할
    • Java와 OS를 연결하며, Java가 OS에 독립적으로 실행되도록 한다.
    • Java 애플리케이션을 Class Loader를 통해서 읽어들여 Java API와 함께 실행한다
    • GC를 실행한다
  • JVM 실행과정
    • 프로그램이 실행되면 OS에서 JVM에 메모리를 할당한다. JVM은 할당받은 메모리를 용도에 따라 여러 영역으로 나누어서 관리한다.
    • 자바 컴파일러가 자바소스를 읽어들여 자바 바이트코드로 변환시킨다
    • 자바 바이트코드를 Class Loader를 통해 JVM 메모리 영역으로 로딩한다
    • 자바 바이트코드는 Execution engine을 통해 해석된다
    • 해석된 바이트코드는 Runtime Data Areas에 배치되어 실질적인 수행이 이루어진다. 이러한 과정에서 JVM은 필요에 따라 Thread Synchronization과 GC같은 관리작업을 수행한다.

image image

  • JVM 내부모듈
    • 클래스로더
      • JVM 내로 클래스를 Load 해서 Runtime Data Areas에 배치, 동적으로 클래스를 로드(컴파일 타임이 아닌 런타임에 클래스를 참조할 때 해당 클래스를 로드)

Runtime Data Area

  • JVM 이라는 프로그램이 OS에서 실행되면서 할당받는 메모리 영역
  • PC Register, JVM Stack, Native Method Stack은 스레드마다 생성, Heap, Method Area는 모든 스레드가 공유

PC Register

  • Thread가 생성될때마다 생성되는 영역으로, 현재 쓰레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다. 이것을 이용해서 쓰레드를 돌아가면서 수행할 수 있게 한다.

Native Method Stack

  • 자바 외 언어로 작성된 네이티브 코드를 위한 메모리 영역이다. 보통 C/C++등의 코드를 수행하기 위한 스택이다(JNI)

Method Area( Class Area, Code Area, Static Area )

  • 하나의 JAVA파일은 크게 필드, 생성자, 메소드로 구성된다. 그중 필드 부분에서 선언된 변수(전역 변수)와 각 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메소드 static 변수, 메서드의 바이트 코드등을 보관한다.

  • Method 영역의 데이터는 프로그램의 시작부터 종료가 될 때까지 메모리에 남아있게 된다. 다르게 말하면 전역변수가 프로그램이 종료될때까지 어디서든 사용될 수 있는 이유이기도 하다. 모든 스레드가 공유하고 있으며, jvm이 시작될 때 생성된다.

  • 따라서 전역변수를 무분별하게 선언하다보면, 메모리가 부족할 우려가 있어 필요한 변수만을 사용할 필요가 있다

Stack Area( 스택 메모리 영역 )

  • 우리가 현재까지 작성하던 메소드 내에서의 정의하는 기본 자료형(int, double, byte,long, boolean 등)에 해당하는 지역변수( 매개 변수 및 블럭문 내 변수 포함)의 데이터값이 저장되는 공간이 stack 영역이다.

  • 해당 메소드가 호출 될 때 메모리에 할당되고 종료되면 메모리가 해제된다. 예시로, main 메소드 호출될 때 Stack 영역에 할당되고 종료시 해제된다. 또한 예시로 a라는 변수에 5,4,3,2 순으로 값을 할당한 후 마지막에 a를 출력할 경우 2의값이 나온다. 이전 데이터는 지워진 것이고, 2라는 값만 출력되는 것은 이전의 값이 지워지는 구조라는 것이다.

  • 즉 Stack 영역은 LIFO(Last In First Out)의 구조를 갖고 변수에 새로운 데이터가 할당되면 이전 데이터는 지워진다는 것을 알 수 있습니다.

Heap Area( 힙 메모리 영역 )

  • 힙 영역에는 참조형의 데이터 타입을 갖는 객체(인스턴스), 배열 등은 Heap 영역에 저장된다. 이때 변수( 객체, 객체변수, 참조 변수 )는 Stack 영역의 공간에서 실제 데이터가 저장된 Heap 영역의 참조값(reference value, 해시코드 / 메모리에 저장된 주소를 연결해주는 값)을 new 연산자를 통해서 리턴받는다.

  • 다시 말하면 실제 데이터를 갖고 있는 Heap 영역의 참조 값을 Stack 영역의 객체가 갖고 있다. 이렇게 리턴 받은 참조 값을 갖고 있는 객체를 통해서만 해당 인스턴스를 핸들 할 수 있다.


public class HeapArea {
	public static void main(String[] args) {
		String str1 = new String("joker");
		String str2 = new String("joker");
		if(str1 == str2){
			System.out.println("같은 주소값 입니다.");
		} else {
			System.out.println("다른 주소값 입니다.");
		}
	}
}

  • 예시로, 위 코드를 실행하면 다른 주소값이 나온다. 이유는 무엇일까? 데이터가 저장된 heap 영역의 참조 값을 리턴 받아서 가지고 있다는 것이다. 그렇기 때문에 저장된 주소가 다른 값을 “==”으로 비교시 다른 주소값이 나오는 것이다.

  • 참고로 Heap에 저장된 데이터가 더 이상 사용이 불필요 하다면, 메모리 관리를 위해 JVM에 의해서 알아서 해제된다. 이러한 기능을 GC( 가비지 컬렉터 ) 라고 한다.

Runtime Constant Pool

  • Method Area 내부에 존재하는 영역, 어떤 메소드나 필드를 참조할 때 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리상 주소를 찾아서 참조하여 중복을 막는 역할

Execution Engine

Load된 클래스의 바이트코드를 실행하는 Runtime Module Class Loader를 통해 JVM 내의 Runtime Data Areas에 배치된 바이트 코드를 기계어로 변환 후 실행

  • 바이트 코드를 기계어로 변환하는 방식

  • 1) Interpreter

  • 바이트 코드를 한줄씩 실행해서 속도가 느림 한번만 실행하는 코드에 적합

  • 2) JIT Compiler

  • Interpreter의 단점을 보완 바이트 코드 전체를 컴파일 하고 해석한 코드를 보관해서 재실행시 빠르게 실행

Comments