Post

[Java] 메모리

Java의 메모리 영역

Java 프로그램이 실행되면 JVM(자바 가상 머신)은 OS로부터 메모리를 할당받고, 그 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.
JVM의 메모리 공간(Runtime Data Area)는 크게 Method(Static) 영역, Stack 영역, Heap 영역으로 구분되고 데이터 타입에 따라 각 영역에 나눠서 할당 되게 된다.

JVM이란?

  • 자바 프로그램 실행환경을 만들어주는 소프트웨어
  • 자바 코드를 컴파일하여 .class 바이트 코드로 만들면 해당 코드가 JVM 환경에서 실행된다
  • JVM은 JRE(Java Runtime Environment)에 포함되어 있다.

Java는 어떠한 플랫폼에 영향을 받지 않는다.

JVM을 사용하면 하나의 바이트 코드(.class)로 모든 플랫폼에서 동작하도록 할 수 있다.

.class : 바이트 코드라고 하며 사람이 쓰는 자바 코드에서 컴퓨터가 읽는 기계어의 중간 단계이다.


[예시]

C / C++에서는 컴파일 플랫폼(OS)과 타겟 플랫폼이 다를 경우 프로그램이 동작하지 않는다.

하지만 Java의 경우 플랫폼에 설치되어 있는 JVM이 운영체제에 맞는 실행 파일로 변환 시켜주기에 컴파일한 바이트 코드로 모든 플랫폼에서 동작이 가능하다.

즉 Java는 플랫폼에 종속적이지 않지만 JVM은 플랫폼에 종속적이다.



자바 변수

메모리 영역에 대한 이해도를 높이기 위해선 자바 변수의 종류에 대한 이해가 우선되어야 한다.

변수명선언위치생성시기설명
클래스 변수
(class variable)
(= static 변수)
클래스 영역클래스가 메모리에 올라갈 때클래스 영역에서 타입 앞에 static이 붙는 변수
객체를 공유하는 변수로 여러 객체에서 공통으로 사용하고 싶을 때 정의
인스턴스 변수
(instance variable)
클래스 영역인스턴스가 생성되었을 때클래스 영역에서 static이 아닌 변수
개별적인 저장 공간으로 객체/인스턴스마다 다른 값 저장 가능
※ 객체/인스턴스 생성만 하고 참조 변수가 없는 경우 가비지 컬렉터에 의해 자동 제거됨
지역 변수
(local variable)
메서드 영역위치하고 있는 메서드가 수행되었을 때메서드 내에서 선언되고 메서드 수행이 끝나면 소멸되는 변수
초기값을 지정한 후 사용할 수 있음
매개 변수
(parameter)
메서드 영역위치하고 있는 메서드가 수행되었을 때메서드 호출 시 ‘전달하는 값’을 가지고 있는 인수
(지역 변수처럼 선언된 곳부터 수행이 끝날 때까지 유효함)

Method(Static) 영역

  • JVM이 구동될 때 생성되며 모든 스레드가 공유하는 영역
  • JVM이 읽어들인 클래스와 인터페이스에 대한 런타임 상수 풀(runtime constant pool), 멤버 변수(필드), 클래스 변수(Static 변수), 상수(final), 생성자(constructor)와 메소드(method)등을 저장하는 공간
  • Method(Static) 영역의 데이터는 프로그램의 시작부터 종료될 때까지 메모리에 남아있다
    • 장점 : 프로그램이 종료될 때까지 어디서든 접근 및 사용 가능
    • 단점 : 무분별하게 많이 사용할 경우 메모리 부족 현상이 일어날 수 있게 된다.
* 런타임 상수 풀(runtime constant pool)
- Type에서 사용된 상수를 저장하는 곳  
- 문자 상수, 타입, 필드 Method reference도 상수 풀에 저장  
- final class 변수의 경우에도 상수 풀에 값 복사

[예시]

public class Main {
    public static int s = 10;

    public static void main(String[] args) {
        int a = 5;
        int b = 5;
        int result1 = a + b + Main.s;
        System.out.println(result); // 20

        Counter sub = new Counter();
        twice(sub);
        int result2 = sub.get();
        System.out.println(result2); // 100
    }

    public static void twice(Counter c) {
        c.plus(10);
        c.plus(20);
    }
}

class Counter {
    public int state = 50;
    public final int count = 20;

    public int get() {
        return state + count;
    }

    public void plus(int n) {
        state += n;
    }
}


  • 클래스 변수(static)와 메소드는 무조건 Method 영역에 적재
  • 일반 인스턴스 변수인 Counter 클래스의 변수 state 그리고 count는 final 키워드가 붙어있음에도 Method 영역에 들어가지 않음



Stack 영역

  • 원시(primitive) 타입의 데이터(int, double, byte, long, boolean 등)에 해당되는 지역변수, 매개 변수 데이터 값이 저장
  • 메소드가 호출 될 때 메모리에 할당되고 종료되면 메모리에서 사라짐
  • 후입선출(LIFO)의 특성을 가지며 스코프(scope)의 범위를 벗어나면 메모리에서 삭제
  • Stack 영역은 각 스레드가 생성되는 순간마다 해당 스레드를 위한 stack 영역이 생성된다.
  • 서로 다른 스레드의 stack 영역에는 접근 불가
  • 메소드가 호출될때 스택 영역에 스택 프레임이 생기고 그 안에 메소드를 호출 ```
  • 스택 프레임(stack frame) 하나의 메서드에 필요한 메모리 덩어리를 묶어서 스택 프레임(stack frame)이라고 한다. 하나의 메서드당 하나의 스택 프레임이 필요하며, 메서드를 호출하기 직전 스택 프레임을 자바 Stack에 생성한 후 메서드를 호출하게 된다. 스택 프레임에 쌓이는 데이터는 메서드의 매개변수, 지역변수, 리턴값 등이 있다. 만일 메서드 호출 범위가 종료되면 스택에서 제거된다. ```


[예시]

1
2
3
4
5
6
7
8
9
10
public class Main {
    public static int s = 10;

    public static void main(String[] args) {
        int a = 5;
        int b = 5;
        int result1 = a + b + Main.s;
        System.out.println(result); // 20
    }
}


Heap 영역

  • 참조형(Refenrece Type) 데이터 타입을 갖는 객체(인스턴스), 배열 등이 저장되는 공간
  • 단, Heap 영역에 있는 오브젝트들을 가리키는 레퍼런스 변수는 stack에 적재
  • Heap 영역은 Stack 영역과 다르게 보관되는 메모리가 호출이 끝나더라도 삭제되지 않고 유지된다. 그러다 어떤 참조 변수도 Heap 영역에 있는 인스턴스를 참조하지 않게 되면, GC(가비지 컬렉터)에 의해 메모리에서 청소된다.
  • Stack은 스레드 수에 맞춰 각각 생성되지만, Heap은 몇 개의 스레드가 존재하든 상관없이 단 하나의 Heap 영역만 존재한다.


실행 과정

[예시]

public class Main {
    public static int s = 10;

    public static void main(String[] args) {
        int a = 5;
        int b = 5;
        int result1 = a + b + Main.s;
        System.out.println(result); // 20

        Counter sub = new Counter();
        twice(sub);
        int result2 = sub.get();
        System.out.println(result2); // 100
    }

    public static void twice(Counter c) {
        c.plus(10);
        c.plus(20);
    }
}

class Counter {
    public int state = 50;
    public final int count = 20;

    public int get() {
        return state + count;
    }

    public void plus(int n) {
        state += n;
    }
}


1. Method 영역 확인

  • Method 영역에 런타임 상수 풀, 멤버 변수(필드), 클래스 변수(Static 변수), 상수(final), 생성자(constructor)와 메소드(method) 등을 저장

2. Counter 생성자 호출

  • Heap 영역에 Counter 클래스 인스턴스 변수들 저장
  • Stack 영역에 지역 변수 sub가 주소값을 가진채로 저장

3. twice 메소드 실행

  • 새로운 메소드를 실행하는 것이기에 Stack 영역에 새로운 스택 프레임 생성
  • 클래스를 가리키는 매개변수 c는 Heap의 Counter 클래스 주소값을 가진채 Stack 영역에 저장

4. plus 메소드 실행

  • 객체 Counter에 정의된 새로운 메소드를 실행하는 것이기에 새로운 스택 프레임 생성
  • this라는 암묵적인 변수 자동 생성, 이 this는 자동으로 Heap 영역의 Counter를 가리키게 된다.
  • 연산이 이루어져 Heap 영역의 인스턴스 변수 state 값이 변환된다

5. get 메소드 실행

  • 실행 완료한 plus 스택 프레임 제거
  • get이라는 새로운 메소드를 실행했기에 새로운 스택 프레임 생성
  • this변수가 Heap 영역의 객체를 가리키고 해당 객체의 인스턴스 변수를 반환
  • 실행 완료한 get 스택 프레임 제거
  • 지역 변수 result2가 main 스택 프레임에 추가

6. 마지막 코드 실행 후 main 스택 프레임 제거

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

7. GC(가비지 컬렉터) 청소 시간

  • 가비지 컬렉터는 heap 영역에 참조되지 않고 남은 객체들을 삭제
  • 코드 실행이 모두 끝나면 Method(Static) 영역도 비워지게 된다.


[추가] Heap과 Stack 메모리의 차이점

  • Heap 메모리는 어플리케이션의 모든 부분에서 사용되는 반면, Stack 메모리는 하나의 스레드가 실행될 때 사용
    • Heap과 Method 영역에 저장된 객체는 어디서든지 접근이 가능하지만, Stack 메모리는 다른 스레드가 접근 불가
  • 객체 생성 시 항상 Heap 영역에 저장되며, 스택 메모리는 Heap 영역에 있는 객체를 참조
    • Stack 메모리는 원시(primitive) 타입의 지역 변수와 힙 공간에 있는 객체 참조 변수만 갖고 있다.
  • Stack 메모리의 생명주기는 매우 짧고 Heap 메모리는 어플리케이션의 시작~끝까지 살아남는다.
  • Java 코드를 실행할 때 따로 -Xms-Xmx 옵션 사용을 통해 Heap 메모리의 초기 사이즈와 최대 사이즈를 조정 가능하다.
  • Stack 메모리가 가득차면 java.lang.StackOverFlowError를 발생
  • Heap 메모리가 가득차면 java.lang.OutOfMemoryError : Java Heap Space에러를 발생
  • Stack 메모리 사이즈는 Heap 메모리와 비교했을 때 매우 적다. 하지만 Stack 메모리는 LIFO를 통해 간단하게 메모리를 할당하기에 Heap 메모리보다 빠르다.
This post is licensed under CC BY 4.0 by the author.

Trending Tags