본문 바로가기

Java

[Java] JVM의 Heap, Stack구조에 대한 공부

자바 메모리 구조에 대한 공부를 하면서 Heap과 Stack 영역에 대하여 아주 간단하게 지나갔는데 자세하게 공부하겠다.

위 그림(Runtime Data Area 영역)에서 JVM stack과 Heap의 대해 공부하겠다.

Heap영역

1. 프로그램을 실행하면서 생성한 모든 객체가 저장된다.

2. 주기적으로 GC가 제거하는 영역이다.

위 그림은 자바 7 이전과 8 이후의 JVM 구조이다. 위 그림처럼 Heap은 영역이 나누어지는데 이는 GC이 Heap을 효율적으로 삭제하기 위해서 3가지 영역으로 나누어진다.

 

New/Young 

생명 주기가 짧은 객체를 GC 대상으로 하는 영역

 

Eden

new를 통해서 생성된 객체가 위치한다. 정기적인 GC후 살아남은 객체들은 Survivor로 이동한다.

survivor1 / survivor2

각 영역이 채워지게 되면, 살아남은 객체는 비워진 Survivor로 순차적으로 이동한다.

Old

Survivor 영역의 객체가  GC에서 살아남아 다른 Survivor 영역으로 이동할 때마다 객체의 Age가 증가한다. 일정 Age이 이상이 되면 Old영역으로 오게 된다. 여기서도 GC에 영향을 받는데 앞에 GC와는 다른 GC이다. 

 

Permgen

Permgen영역은 Method Area으로 사용되었다.

메타데이터를 저장시키는 영역이다.  자바 내 메타데이터는 Class, Method Meta Data, Static Object Variable, constant pool 등을 관리했다. 

그림에서 보면 힙영역에 있지만 별도의 힙 영역을 설정해 주어서 관리해야 했다. 이 때문에 java.lang.OutOfMemoryError : PermGen space이라는 오류를 발생시켰다. 

Metaspace

자바 7까지 Permgen라고 불린 네이티브 메모리 영역이다. 자바 8부터는 Method Area가 MetaSpace라는 이름으로 변경되었다. 위와 같이 metaspace영역은 Method Area으로 사용된다. 네이티브 메모리에 합쳐지면서 JVM이 아니라 os에서 관리하는 것으로 변동되었다. 여기서 Static Object Variable은 heap영역에 저장되도록 변경되었다.

 

그러면 생각할 수 있다. 앞 포스트에서 static은 Method영역에서 있는 거 아니었나? 할 것이다. 여기서 static은 static이 아니라 static object이다. 즉 static 형태의 객체를 의미한다. 즉 static 객체는 자바 8 이전에는 Permgen에서 저장되다가 자바 8 이후에는 heap영역에서 저장되어 GC의 대상이 될 수 있다.

 

메타데이터

애플리케이션이 처리해야 할 데이터가 아닌, 컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지 알려주는 정보이다. 즉 데이터들의 데이터이다.

 

c heap

JVM에서는 자바 코드만이 아니라 성능 개선을 위해서 네이티브 코드를 사용할 때가 있다. C/C++와 같은 다른 언어로 작성된 코드를 자바 프로그램에서 호출하도록 해주는 영역이다.

 

 

Stack영역

메서드 호출 시 지역 변수, 매개변수, 함수 호출내역 등이 저장되는 영역이다.

스택 저장되는 데이터는 Frame이라는 자료구조로 저장된다.

 

 

Frame

스택 프레임은 메서드가 호출될 때마다 새로 생겨 스택에 push 된다.

스택 프레임 내에 Local Variables array, Operand Stack, Frame Data를 갖는다.

public class Main {


    public static void main(String[] args) {
        test(3);
    }

    public static int test(int check){
        int plus=check+1;

        return plus;
    }
}

위 예시 코드를 가지고 stack Frame에서 어떻게 나오는지 확인해 보자

 

먼저 위 코드에서  test부분을 그림으로 나타내면 위 그림과 같다.  

// class version 61.0 (61)
// access flags 0x20
class Main {

  // compiled from: Main.java

  // access flags 0x0
  <init>()V
   L0
    LINENUMBER 1 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this LMain; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 3 L0
    ICONST_3
    INVOKESTATIC Main.test (I)I
    POP
   L1
    LINENUMBER 4 L1
    RETURN
   L2
    LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static test(I)I
   L0
    LINENUMBER 7 L0
    ILOAD 0
    ICONST_1
    IADD
    ISTORE 1
   L1
    LINENUMBER 9 L1
    ILOAD 1
    IRETURN
   L2
    LOCALVARIABLE check I L0 L2 0
    LOCALVARIABLE plus I L1 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2
}

위 코드는 인텔리제이로 뽑은 바이트코드이다. 너무 길다.

터미널을 통해서 다시 보자. 

훨씬 보기 좋다. 순서는 main(0,1)->test->main(4,5) 순으로 들어가면서 살펴볼 것이다. 

코드를 살펴보자

바이트 코드 작동 해당 코드
0 : iconst_3 상수 3을 operand stack에 push한다. 3
1 : invokestatic #7 상수 풀에서 #7(test)를 호출한다. test(3)
0 : iload_0 local variables array(지역 변수 배열) 0(this)번 인덱스의 stack에 push한다. check
1 : iconst_1 상수 1을 operand stack에 push한다. 1
2 : iadd 읽은 두 값을 더한다. check+1
3 : istore_1 operand stack를 pop하여 지역 변수 배열 1번 인덱스에 저장한다. plus=check+1
4 : iload_1 local variables array(지역 변수 배열) 1번 인덱스의 값을 읽는다. plus
5 : ireturn 더한 값을 리턴한다. return plus
4 : pop 호출한 메서드의 반환 값(test)을 스택에서 제거한다.  
5 : return 메서드가 완료되고 반환된다.  

 

여기서 0 : iload_0에 집중하자 원래 Local Variables array에 첫 번째 배열은 this가 온다. 하지만 여기서는 check가 왔다 어떻게 된 걸까?

이전글과 위에 있는 Metaspace의 관리영역을 보면 알 수 있다. 일단 위 코드를 보면 test는 static 메서드이다. 앞글에서 static 메서드는 Native memory에서 관리하기에 빌드하면서 미리 선언된다. 그렇기에 stack에 local variables array가 아니라 Metaspace 영역에서 관리된다. 그래서 Local Variables array에 0번째 배열은 this가 아닌 매개변수인 check가 들어간다. 만약 메서드가 static 메서드가 아니었다면 0번에는 this가 들어갔다.

 

Local Variables array

메서드에서 사용되는 매개변수나 지역변수가 저장되어 있다.

Operand Stack

위에 코드와 같이 stack의 자료구조로 메서드 내 계산을 위한 작업 공간이다.

Constant Pool Reference

Run-time Constant Pool의 참조를 갖는다

 

위 바이트코드에서 1 : invokestatic #7을 기억하는가? 뒤에 #7이 붙어서 약간 어색했을것이다. 

위사진은 Main코드파일을 디스어셈블하고 pool부분만 잘라낸 사진이다.  여기서 보듯이 #7을 보면 #8과 #9를 가리킨다. #8은 Class로 #10을 가리킨다. #10은 Main을 뜻한다. #9는 #11:#12로 가리킨다. #11은 test 메서드고 #12는 (I) I는 매개변수가 int이고 반환형도 int라는 것을 의미한다. 이렇게 값들을 참조한다.

 

디스어셈블 : 기계어를 어셈블리 언어로 변환하는 과정이다.

 

출처

https://johngrib.github.io/wiki/jvm-stack

https://sanghoonly.tistory.com/62

https://velog.io/@ddangle/Java-%EB%9F%B0%ED%83%80%EC%9E%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%98%81%EC%97%ADRuntime-Data-Area%EC%97%90-%EB%8C%80%ED%95%B4

https://velog.io/@agugu95/%EC%9E%90%EB%B0%94-%EB%9F%B0%ED%83%80%EC%9E%84-%ED%99%98%EA%B2%BD%EA%B3%BC-%EB%A9%94%EB%AA%A8%EB%A6%AC-Java-Heap-Permgen-and-MetaSpace

https://sharplee7.tistory.com/54

https://jgrammer.tistory.com/entry/JAVA-Java8%EB%B6%80%ED%84%B0%EB%8A%94-static%EC%9D%B4-heap%EC%98%81%EC%97%AD%EC%97%90-%EC%A0%80%EC%9E%A5%EB%90%9C%EB%8B%A4

https://www.youtube.com/watch?v=GU254H0N93Y