자바 메모리 관리 : JVM의 내부 작동 원리 이해하기

이강용·2024년 2월 6일
1

CS

목록 보기
15/109
post-thumbnail

자바와 JVM의 메모리 관리

자바는 관리되는 언어(managed language)로, 자바 가상 머신(JVM) 위에서 실행되며 메모리 관리를 자동으로 수행한다. JVM은 메모리 할당과 가비지 컬렉션(GC)을 담당하여 개발자가 메모리 관레 드는 수고를 덜 수 있게 한다. 이는 메모리 누수를 줄이고, 프로그램의 효율성과 안정성을 향상시키는 데 도움을 준다.

자바의 메모리 관리가 중요한 이유

메모리 관리는 자바 프로그램의 성능과 안정성에 직접적인 영향을 미친다. 효율적인 메모리 관리는 실행 속도를 최적화하고, 자원을 절약하며, 시스템의 안정성을 보장한다. 반면, 부적절한 메모리 사용은 속도저하, 프로그램의 실패, 그리고 비효율적인 자원 사용으로 이어질 수 있다.

JVM 메모리 구조

JVM 메모리는 크게 Method Area,Heap Area,Stack Area,Native Method Stack으로 구분된다. 각 영역은 프로그램 실행에 있어 서로 다른 목적을 가지고, 다른 유형의 데이터를 저장한다.

우리가 JVM 메모리 구조를 알아야 하는 이유?

  1. 가지비 컬렉션(GC) 최적화
  • GC는 불필요한 객체를 자동으로 정리하지만(Java, Python), 객체의 생명주기와 메모리 할당 패턴을 이해하면 불필요한 객체 생성을 피하고, 메모리 사용률을 최적화하여 GC 오버헤드를 줄일 수 있다. 이는 애플리케이션의 성능과 반응성을 직접적으로 향상시킨다.
  1. 스레드 관리 및 동기화
  • JVM 내에서 스레드는 스택 메모리를 통해 각각의 실행 컨텍스트를 가진다. 스레드 사이의 올바른 동기화와 데이터 공유를 관리하기 위해서는 스택과 힙 메모리의 작동 방식을 이해해야 한다. 이는 멀티스레드 애플리케이션의 동시성 문제를 방지하고, 데이터 일관성을 유지하는데 중요하다.
  1. 성능 문제 진단 및 튜닝
  • 메모리 사용에 대한 통찰력은 성능 관련 문제를 진단하고 해결하는 데 필수적이다. JVM의 메모리 구조를 이해하면 OutOfMemoryError와 같은 메모리 문제를 해결하고, 애플리케이션의 성능을 튜닝하기 위한 기법을 마련할 수 있다.

Java의 메모리 관리 및 변수 유효 범위

  • 자바는 각 변수 유형에 따라 다른 메모리 영역에 데이터를 저장한다. 이러한 구분은 변수의 생명주기와 접근성을 결정하며, 효과적인 메모리 관리에 핵심적이다. 변수는 선언 위치에 따라 크게 네 가지 유형으로 분류할 수 있다.
  1. 클래스 변수(static 변수) : 클래스가 메모리에 로드될 때 생성되며, 클래스의 모든 인스턴스에 의해 공유되며 이 변수들은 메서드 영역에 저장된다.
  2. 인스턴스 변수 : 객체가 생성될 때 힙에 할당되며, 각 객체 인스턴스에 대해 고유함
  3. 지역 변수 : 메서드 내에서 선언되며, 메서드 호출 시 스택에 저장된다. 메서드가 종료되면 이 변수들은 소멸한다.
  4. 매개 변수 : 메서드 호출과 함께 전달되며, 메서드의 스택 프레임 내에 위치한다.

public class MemoryExample {

    // 이 변수는 메서드 영역에 저장되는 정적 변수입니다.
    static int staticVar = 10;

    // 이 변수는 실행시 힙 영역에 저장되는 인스턴스 변수입니다.
    int instanceVar;

    // 생성자
    public MemoryExample(int var) {
        // 생성자의 매개변수는 스택에 저장됩니다.
        this.instanceVar = var;
    }

    // 메서드 호출시 각 매개변수는 스택 영역에 저장됩니다.
    public void show(int a) {
        // 로컬 변수는 메서드 호출 시 스택 영역에 저장됩니다.
        int localVar = a;
        System.out.println("Static Variable: " + staticVar);
        System.out.println("Instance Variable: " + instanceVar);
        System.out.println("Local Variable: " + localVar);
    }

    public static void main(String[] args) {
        // 객체 생성 구문 'new'는 힙 영역에 저장되는 인스턴스를 생성합니다.
        MemoryExample example = new MemoryExample(5);
        // 메서드 호출은 스택 영역에 프레임을 생성합니다.
        example.show(20);
    }
}
변수 유형선언 위치설명
클래스 변수클래스 영역static 키워드를 사용하여 클래스 영역에 선언된 변수입니다. 모든 인스턴스가 공유합니다.
인스턴스 변수클래스 영역static이 아닌 변수로, 객체 생성시 힙에 할당됩니다. 각 인스턴스마다 별도로 존재합니다.
지역 변수메서드 내메서드 내에서 선언되고 메서드가 실행될 때 스택에 존재합니다. 메서드 실행이 끝나면 사라집니다.
매개 변수메서드 선언부의 괄호 내메서드 호출 시 전달되는 값으로, 메서드 스택 프레임 내에 존재합니다. 메서드 호출 시 사용됩니다.

메모리 영역별 상세 설명

Method(Static) 영역

Method 영역 또는 Static 영역은 JVM이 프로그램의 런타임 데이터를 저장하는 곳 중 하나로, 모든 스레드에 의해 공유되는 메모리 영역이다. 이 공간에는 클래스와 인터페이스의 메타데이터, 실행되는 클래스의 바이트코드, 상수(static final), 정적(static) 변수가 저장된다. 여기에는 생성자 코드와 모든 메서드의 코드도 포함되며, 이는 클래스 또는 객체가 아닌 클래스 자체와 연결된다. Method 영역은 가비지 컬렉션의 대상이긴 하지만, 힙 영역과 비교하여 가비지 컬렉션이 수행되는 빈도는 상대적으로 낮다.

클래스 변수와 메서드의 저장

클래스 변수(정적 변수)는 클래스가 JVM에 로드될 때 생성되어 Method 영역에 저장된다. 이러한 변수들은 해당 클래스의 모든 인스턴스에 의해 공유되며, 클래스가 언로드될 때까지 유지된다. 또한, 모든 클래스의 메서드 코드도 이 영역에 저장되므로, 모든 인스턴스와 스레드가 같은 메서드 코드를 공유할 수 있다.

static 키워드의 중요성과 메모리에 미치는 영향

static 키워드는 특정 필드나, 메서드, 블록을 클래스의 인스턴스가 아니라 클래스 자체와 연결시키는 데 사용된다. static 변수는 힙 영역의 객체와는 독립적으로 Method 영역에 저장되기 때문에 클래스의 모든 인스턴스가 같은 메모리를 참조하여 사용할 수 있다. 이는 메모리 사용량을 줄이는 데 도움이 될 수 있지만, 동시에 데이터가 공유되므로 스레드 안정성에 주의를 기울여야 한다. 또한, static 메서드는 인스턴스를 생성하지 않고도 호출할 수 있으며, 클래스 로더에 의해 클래스가 로드될 때 메서드 영역에 저장되어 프로그램 전체에서 접근 가능하다.

public class Main {
	
	
	public static void main(String[] args) {
		int a = 100;
		a = wow(a);
	}
	
	public static int wow(int num) {
		int b = num * 4;
		return b;
	}

}

Stack 영역

스택 영역은 각 스레드에 의해 사용되는 메모리 부분으로, 메서드 호출과 지역 변수 할당에 사용된다. 이 영역은 스레드마다 별도로 존재하며, 메서드 호출 시 생성되는 스택 프레임을 저장하는 데 사용된다.

작동 방식과 메모리 할당 프로세스

스택 영역은 메서드가 호출될 때마다 스택 프레임을 쌓는 방식으로 동작한다. 각 스택 프레임은 호출된 메서드의 매개변수, 지역변수, 반환 값 및 연산 중간 결과 등을 포함한다. 메서드가 종료되면 해당 스택 프레임은 스택에서 제거되고, 메모리가 반환된다. 이 메모리 할당 및 반환 프로세스는 매우 빠르며, 스택 포인터의 이동만으로 관리된다.

스택 프레임이란?

스택 프레임은 특정 메서드의 호출과 실행에 필요한 정보를 포함하는 데이터 블록이다. 메서드가 호출되면 하나의 스택 프레임이 스택 영역에 할당되고, 이는 해당 메서드의 매개변수, 지역변수, 리턴 주소, 그리고 연산에 필요한 다른 정보들을 포함한다.

지역 변수와 매개변수의 스택 프레임 관리

스택 프레임 내에서 지역변수와 매개변수는 메서드가 실행될 때 할당되고, 메서드가 종료될 때 스택에서 제거된다. 이들은 메서드가 활성 상태일 때만 존재하며, 메서드가 종료되면 이들의 스코프(유효범위)가 끝나고, 해당 스택 프레임은 스택에서 제거되어 메모리가 해제된다.

스택의 LIFO 특성과 스코프

스택은 Last In, First Out(LIFO)의 특성을 가지고 있다. 가장 마지막에 들어간 요소가 가장 먼저 나온다. 이 특성은 메서드 호출과 반환에 따라 스택 프레임이 추가되고 제거되는 방식에 적용된다. 스택의 LIFO 특성은 메서드 호출 순서와 실행 흐름을 관리하는 데 중요하며, 프로그램의 스코프를 정의하는 데에도 사용된다. 각 메서드의 스코프 내에서 선언된 지역 변수는 해당 스코프 안에서만 존재하며, 스코프를 벗어나면 더 이상 접근할 수 없다.

💡 따라서, wow 스택 프레임은 스택에서 제거(pop)되며, main 메서드의 스택 프레임으로 돌아간다. main 메서드의 a 변수에 wow 메서드로부터 반환받은 값 400이 할당된다.

Heap 영역

자바 메모리 관리의 핵심 부분으로, 동적으로 할당되는 모든 객체와 배열의 저장소이다. 즉, 프로그램이 실행되는 동안 필요에 따라 new 키워드를 통해 객체가 생성되면, 이 객체들은 힙 영역에 저장된다. 객체가 더 이상 필요하지 않게 되면( 어떠한 참조 변수도 그 객체를 가르키지 않게 되면), 가비지 컬렉션(GC)에 의해 자동으로 메모리에서 해제된다.

힙 영역의 중요한 특징 중 하나는 그것이 프로그램 전체의 실행 동안 지속되고 모든 스레드에 의해 공유된다는 것이다. 이는 객체의 생명주기가 스택 메모리에서와 같이 메서드 호출의 시작과 종료에 의존하지 않음을 의미한다. 대신, 객체는 프로그램의 실행 흐름과 애플리케이션 로직에 따라 생성되고 유지된다.

가비지 컬렉션이란?

가비지 컬렉션(Garbage Collection, GC)은 힙 메모리 영역에서 사용되지 않는 객체를 자동으로 검색하고, 해제하는 JVM의 프로세스이다. 이는 메모리 누수를 방지하고, 프로그램이 메모리를 더 효율적으로 사용할 수 있도록 도와준다.

package programmers;

public class Main {
	
	
	public static void main(String[] args) {
		Counter c = new Counter();
	}
	

}

class Counter {
	private int state = 0;
	public void increment() {
		state++;
	}
	
	public int get() {
		return state;
	}
}



package programmers;

public class Main {
	
	
	public static void main(String[] args) {
		Counter c = new Counter();
		two(c);
	}
	
	public static void two(Counter c) {
		c.increment();
		c.increment();
	}
	

}

class Counter {
	private int state = 0;
	public void increment() {
		state++;
	}
	
	public int get() {
		return state;
	}
}

💡 두 가지의 예제에서 강조되는 부분은 호출되는 메서드가 파라미터로 객체값을 전달받아 객체의 상태를 변경하게되면, 메서드 종료(스택 제거) 이후에도 힙 영역에 있는 객체의 상태는 쭉 유지된다는 점이다.

main의 마지막 코드가 실행되면 main 스택 프레임은 스택 영역에서 제거된다.

  • 스택 영역은 메서드의 끝을 알리는 닫는 중괄호 }를 만나면 자동으로 메모리에서 제거된다.
  • 그러나 Heap 영역에는 여전히 객체 데이터가 메모리에 상주하게 된다.

가비지 컬렉션(GC)가 Heap 영역을 청소한다.

  • 가비지 컬렉션은 힙 영역에 참조되지 않고 남아 있는 객체들을 식별해 힙 영역을 청소한다.
    👉🏻 이는 개발자가 직접 수행하지 않고, JVM이 자동으로 수행한다. GC가 언제 발생할지는 예측할 수 없으며, JVM에 의해 결정된다.
  • 추가로 코드 실행이 모두 끝나면 Method(Static) 영역도 비워지게 된다.

Stack 메모리와 Heap 메모리의 차이점

구분Stack 메모리Heap 메모리
작동 방식LIFO 원칙, 메서드 실행 시 생성 및 종료 시 해제동적 할당, 가비지 컬렉션에 의한 메모리 관리
저장 데이터지역 변수, 매개변수객체, 참조형 변수
메모리 관리컴파일 시 결정, 자동 할당 및 해제런타임 시 결정, 수동 할당 및 자동 해제
접근 속도빠름상대적으로 느림
스레드 공유각 스레드별 독립적모든 스레드가 공유
메모리 한계제한적, StackOverflowError 가능성확장 가능, OutOfMemoryError 가능성
profile
HW + SW = 1

0개의 댓글