JVM 메모리 구조

Kyu·2021년 1월 10일
1

Java 공부기록

목록 보기
6/40

출처: https://slidesplayer.org/slide/15121827/

위 링크의 내용을 보기좋게 재편집한 포스트임. 내가 작성한 내용은 아무것도 없음.


3 Why? 메모리구조를 알아야 하는가!

메모리란?

메모리는 말 그대로 데이터를 저장하는 공간입니다. 데이터 뿐만 아니라 어떤 프로그램을 실행하기 위한 명령어도 저장됩니다. 우리가 컴퓨터를 살 때 따지는 것이 대표적으로 그래픽카드나 CPU말고도 RAM이나 HARD DISK용량을 따집니다. RAM은 1기가가 좋다느니, HARD DISK는 1TB면 충분하다느니, RAM과 HARD DISK가 바로 메모리의 대표적인 예가 되겠습니다.

컴퓨터를 켜면, 인터넷이든 게임이든 뭐든 프로그램을 실행시킬 겁니다. 자동으로 실행되는 윈도우(운영체제)도 마찬가지입니다. 컴퓨터를 실행한다는 것은 메모리를 계속 사용한다는 의미입니다.

프로그램을 사용하기 위해서는 어떤 절차가 필요한지 메모리에 다 저장되어 있기 때문에 그것을 참고하면서(메모리에 저장되어 있는 명령어를 참조한다는 의미) 그 프로그램이 실행되기 위한 준비물을 메모리에서 꺼내(메모리에 저장되어 있는 데이터를 참조한다는 의미) 프로그램을 실행하는 것입니다.

정리하자면, 메모리는 데이터와 명령어를 저장하는 공간입니다. 이 공간이 필요한 이유는 컴퓨터가 어떤 일을 할 때(프로그램 실행 등등) 그 일을 어떻게 하는지를 참고하기 위한 자료가 메모리에 있는 것입니다.

메모리 구조를 배우는 이유

코드의 가장 중요한 것은 메모리 관리입니다. 아무리 훌륭한 프로그램이라도 메모리 관리가 형편없으면 그 코드는 형편없는 코드라고 합니다.
컴퓨터의 메모리 공간은 한정되어 있습니다. 그런데 메모리 관리가 형편없는 프로그램을 실행시킨다고 가정한다면, 불필요한 메모리를 사용하고 그것이 누적되다 보면 시스템 속도 저하는 물론이고 심할 경우 컴퓨터의 메모리 공간을 초과하여 강제적으로 프로그램을 종료(튕김)하게 되는 것입니다.

4 JAVA 프로그램 실행 구조

일반적인 프로그램은 windows나 Linex 같은 운영체제(os) 위에서 실행하게 됩니다.JAVA프로그램의 실행은 운영체제가 JVM을 실행시키고, JVM이 자바 프로그램을 실행시키는 구조를 가지고 있습니다.

5 JVM이란? Java Virtual Machine 자바 가상 머신 JAVA 와 OS간 중계자 역할

JVM이란?

JVM은 Java Virtual Machine의 약자로, 영어에서도 알 수 있듯이, 가상의 하드웨어 역할을 해주는 software입니다.

Java : Java언어를 사용한다.
Virtual : 가상화, 즉 Hardware 적인 형태가 아닌 Software 적인 형태이다.
Machine : 독자적으로 작동할 수 있는 기계적인 구조를 가지고 있어 하나의 축약된 컴퓨터이다.

JVM은 JAVA 프로그램간의 중계자 역할을 하며, JAVA는 OS가 무엇이든 코드가 변하지 않고 JVM이 설치되어있는 곳이라면 어디든지 재사용이 가능하다는 특징이 있습니다.

이것이 가능한 이유는 JVM은 OS 버전별로 바뀔 수 있지만(JVM은 OS에 종속적) JAVA와 JVM간 통신은 항상 같은 규격에서 이루어지기 때문입니다.(JAVA프로그램은 OS에 독립적)

그러므로 실제 JAVA프로그램이 실행되는 곳은 OS에서 메모리를 할당받은 JVM이며, JVM내에서 데이터가 메모리 공간의 특성에 따라 적재됩니다. 즉 JVM의 메모리 구조가 우리가 알고자 하는 메모리 구조입니다.

6 Runtime Data Area Native

JVM 메모리 구조와 과정

JVM 메모리 구조자바소스가 메모리에 적재되는 과정을 통해서 JVM의 메모리 구조를 살펴보도록 하겠습니다. 임의의 ‘Hello.java’라는 파일을 만들었다고 가정해봅시다. 이 파일은 사용자가 알아볼 수 있는 코드로 작성한 것입니다. 이 코드를 JVM이 알아 볼 수 있게 Bytecode로 변경해주어야 합니다. 이것을 ‘컴파일’이라고 합니다. commend창에서 javac.exe라는 컴파일러를 통해 ‘javac Hello.java’라는 명령어를 입력하면 ‘bytecode로된 Hello.class’파일이 생성 됩니다. (이클립스에서는 자바파일을 실행하게 되면 자동으로 생성해주게 됩니다.)

원래 C언어 같은 기본적인 다른 프로그램 언어로 작성된 소스를 컴파일 하면 CPU에 의해 동작하는 .exe의 바이너리 코드가 생성됩니다. 그런데 자바의 경우는 컴파일 시 .class 의 바이너리 코드가 생성됩니다. .exe파일 처럼 바로 실행되지 파일이 아니라 자바 가상머신(VM)에 올라가야 실행이 되는 바이너리 코드입니다. 이것을 자바 바이트 코드라 합니다. JVM의 class loader는 class파일에 있는 수행할 클래스를 찾아서 메모리에 적재합니다. 이 class 파일을 수행하는 시점을 ‘RunTime’이라고 합니다.

Java의 Class 파일은 JVM 위에서 다시 실행 가능한 형태로 변형되는데 이 때 Linking 작업이 발생하게 됩니다. 그래서 Class 파일은 실행 시 Link를 할 수 있도록 Symbolic Reference 만을 가지고 있습니다. 이 Symbolic Reference는 Runtime 시점에서 메모리상에서 실제로 존재하는 물리적인 주소로 대체되는 작업인 Linking이 일어나게 되는 것입니다. 이러한 Link 작업은 필요할 때마다 이루어지기 때문에(동적행위) 이를 가리켜 Dynamic Linking이라고 합니다.적재된 클래스들은 execution engine(실행엔진)에 의해서 binary code로 해석됩니다. 해석된 프로그램은 Runtime Data Areas에 배치되어 실질적인 수행이 이루어지게 됩니다. ‘Runtime Data Areas’ 는 JVM이 JAVA프로그램을 수행하기 위해서 OS로부터 할당받은 메모리 영역입니다. 이 과정 속에서 JVM은 필요에 따라 Garbage Collection 작업을 수행하게 됩니다.Runtime data areas는 다음 장에서 좀 더 자세하게 알아보도록 하겠습니다.

7 메소드 영역

메소드 영역은 클래스 영역, code영역, static 영역의 다양한 이름을 가진 영역입니다. 이 영역에 클래스와 method, 자바 코드가 적재되어서 붙어진 영역이름입니다. 이 영역은 프로그램이 수행되는 동안 클래스의 정보를 참조하는 곳입니다. 말 그대로 이 영역에는 필드 정보, 메소드 정보, 타입 정보, 상수풀, 클래스 변수가 저장되는 영역입니다.

Field Information

  • 필드란? 멤버변수 (= 클래스변수)
  • Type에서 선언된 모든 필드가 저장되는 장소
  • 필드이름
  • 필드 타입
  • 필드 접근 지정자 (public, private, protected, static, final, volatile, transient)
  • transient :
  • IO에서 어떤 객체를 객체직렬화(Serializable)하여 처리할시 모든 인스턴스 변수는 기본적으로 Serializable 상태입니다. 만약 인스턴스변수중 직렬화를 원하지 않는 변수가 있다라고 가정할때 해당 변수를 transient로 선언해 주시면 됩니다.
  • volatile : 어떤 데이터값이 특정 객체에서만 사용되는것이 아니라 다른 객체에서도 변경 및 참조할 수 있는 값으로 사용되고자 할때 쓰는 키워드입니다.(하나의 변수를 여러 쓰레드에서 사용할 때)
  • 자바에선 static 키워드를 씁니다.

Method Information

  • Type에서 선언된 모든 메소드 저장소, 선언된 순서대로 저장
  • method 이름
  • method 리턴타입(or void)
  • method 매개변수(parameter)와 Type
  • method 접근지정자(public, private, protected, static, final, transient, synchronized, native)

Type Information

Type란, java class, interface를 말함

  • Type의 전체이름(패키지명 + 클래스명)
  • Type의 Super클래스의 전체 이름
    (Type이 Interface이거나 Object class 이거나 Super class가 없으면 제외)
  • Type이 class인지, Interface인지의 여부
  • Type의 접근지정자 (public, abstract, final)
  • 직접 연관된 Interface 전체이름 리스트(참조순)

상수풀 = constant pool

  • Type에서 사용된 상수의 ordered Set (순서x, 중복x 순서로 정렬된다는 뜻)
  • 문자상수, 타입, 필드,method의 symbolic reference(객체 이름으로 참조하는 것)가 포함

Class variable

  • static 변수
  • 모든 객체에 공유, 객체 생성 없이도 접근 가능
  • 이 변수는 객체에 속하는게 아니라 클래스에 속함
  • class를 사용하기 이전에 메모리 할당을 받아야 함
  • final class변수 -> 상수로 치환 -> constant pool에 값 복사

9 Stack area

스택공간을 설명하기에 앞서 스택의 개념을 설명해야 이해하기 편할 것 같습니다.

스택이란 영어로 “쌓아올리다.” 라는 의미로 사용되는데, 접시들을 하나하나 쌓아 올리는걸 생각하면 쉽습니다. 접시들을 하나하나 쌓아 올리면 꺼내 쓸 땐 가장 늦게 들어온 맨 위에 접시부터 사용하게 됩니다. 결국 맨 처음 깔린 접시는 맨 마지막에 쓰이게 되는데 이게 스택입니다.(Last In First Out, LIFO)
컴퓨터에서도 똑같이 적용되는데, 접시대신에 메서드가 쌓인다고 생각하면 됩니다.

즉 메서드가 새로운 메서드를 호출하면 현재 실행 중이던 메서드는 대기 상태가 되고 그 위에 호출된 메서드가 실행되고, 또 다른 새로운 메서드가 호출되면 현재 실행 중이던 메서드도 대기상태가 되고 다시 그 위에 새로운 메서드가 실행되는 구조입니다.

스택에는 메서드가 호출 될 때마다 스택프레임이라는 데이터 영역이 생성됩니다.

이 스택프레임에는 매개변수나 지역변수 , 리턴 값 및 연산 시 일어나는 값들을 임시로 저장할 수 있는 공간이 할당됩니다. 이후 메서드의 수행이 끝나게 되면 메모리가 반환됩니다.

스택프레임 공간에는 지역변수 공간, 연산스택 공간, 프레임데이터공간이 있습니다.

실제 데이터의 연산이 이루어지는 공간은 연산스택 공간이고, 프레임데이터 공간은 상수풀, 리턴값 저장 공간(method를 호출한 스택프레임으로 돌아가기 위해서 반환값이 있는 경우 pc레지스터에 operand stack에 push) , 예외구문을 처리하는 공간이 있습니다.

10 Heap area

heap 영역은 프로그램 상에서 데이터를 저장하기 위해서 동적(실행시간)으로 할당하여 사용하는 영역입니다. new 연산자로 생성된 객체와 배열을 저장하는 공간입니다. 단, 그 객체와 배열은 메서드 영역에서 로드된 클래스만 생성 가능합니다.

주로 실행시간에 생성되는 객체를 저장합니다. heap영역은 개발자가 메모리해제를 따로 해주지 않아도 Garbage Collector가 알아서 메모리해제를 하는 영역입니다. 이 말은, 개발자가 메모리해제를 할 수 없다는 뜻이기도 합니다. Garbage Collection에 대한 내용은 뒤에서 따로 설명해 드리도록 하겠습니다.

heap 영역은 크게 Permanent Generation 영역, new 영역, Old영역으로 나뉩니다.
객체를 생성할 때 필요한 정보를 저장하는 공간인 permanent 영역, 객체가 생성되는 new영역, 생성된 객체를 오랫동안 참조할 가능성이 있는 객체들을 저장하는 공간이 old영역입니다.
각 영역에 대한 설명은 Runtime data area에 대한 설명을 마친 후, 자세하게 보도록 하겠습니다.

11 Runtime Data Area

Native method stack area

native method란 자바 외의 다른 언어(C, C++)에서 제공되는 메서드를 의미합니다. 이 영역은 native method의 변수를 저장하는 공간입니다. 이를 수행하기 위해서 JVM은 JNI(Java Native Interface)라는 표준규약을 통해서 다른 언어에서 제공하는 메서드를 사용합니다.

native method

JRE는 Java 실행 환경인데 여기에는 Java Virtual Machine과 Java API, 그리고 Native Method 등이 포함되어 있다. 이중 Java Application interface, 즉 Java API는 한마디로 Runtime Library의 집합이다. Java API는 OS 시스템과 Java 프로그램의 사이를 이어주는 역할(Interface)을 한다. Java API는 Native Method를 통해 OS 자원과 연계되어 있고 다른 한 편으로는 Java 프로그램과 맞닥뜨리고 있다.

만약 java.io.InputStream 이라는 클래스를 사용하여 특정 파일 시스템의 정보를 읽어 온다고 가정하면 Java는 Class 파일 내에 있는 java.io.InputStream의 Symbolic Reference를 이용하여 Runtime 시 해당 Instance에 접근하게 된다. 그러면 이 Instance에 대한 내용, 즉 실제 File에 대한 접근은 Native Method를 통해 OS에 명령을 전달하게 된다. 이후 OS는 실제 File IO를 일으키게 되고, 이 File IO의 결과는 다시 Native Method를 통해 Java API로 전달되는 과정을 거쳐 프로그램이 실행된다. 이러한 Java API를 포함하는 JRE는 OS나 머신에 따라 적절한 것을 설치하면 java 프로그램에게는 플렛폼의 제약을 떠나 동일한 실행환경을 제공하게 된다.

12 PC Register

PC Register는 스레드(하나의 프로그램 내에서 실행되는 메서드)가 생성될 때마다 생성되는 공간이며, 현재 수행중인 JVM의 명령어 주소를 가지고 있습니다. JVM은 CPU에 직접 명령을 수행하지 않습니다. 스택프레임에 있는 Operand 스택 영역에서 명령어를 뽑아내어 pc register라는 별도의 메모리 공간에 저장한 후 CPU에 명령을 전달합니다. JVM이 스택 기반의 PC register를 사용하는 목적은 JVM은 플랫폼에 독립적으로 동작하기 위함입니다.

13 Garbage Collection

Garbage Collector는 더 이상 참조가 되지 않는 객체를 제거하여 heap memory를 재사용 할 수 있도록 하는 일을 합니다. 이런 일을 Garbage Collection이라고 합니다. Java에서는 개발자가 프로그램 코드로 메모리를 해제하지 않기 때문에 Garbage Collector가 더 이상 참조되지 않는 객체를 찾아 지우는 작업을 합니다.

14 Permanent 영역

Permanent영역은 JVM이 시작될 때 생성됩니다. 프로그램이 수행되는 동안 클래스의 정보를 참조하는 곳입니다. method area와 permanent 영역의 차이점은 method area는 스레드가 공유하는 공간이고, permanent 영역은 jvm이 사용하는 공간입니다.

  • class에 대한 meta정보를 저장하는 공간
    메타정보의 주 내용은 보통 jsp와 같은 파일들이 클래스로 변환되기에 필요한 정보
    만약 이 공간이 부족하게 된다면 로드 자체가 안되는 일을 겪게 됨
    ex) 이클립스가 아웃오브 메모리를 뱉고 실행조차 안되는 경우
  • 클래스의 메서드
  • 클래스의 이름
  • 상수 풀 정보(constant pool information) - 프로그램을 실행하는데 필요한 상수를 위한 메모리

new영역

new 영역은 Young 영역이라고도 합니다.
자바객체는 eden영역에서 생성됩니다. 이를 메모리 할당이라고 합니다. eden영역은 모든 자바객체의 고향이라고 할 수 있습니다. eden영역에서 객체 생성이 되고, 메모리가 차게 되면 GC가 발생하게 됩니다. 이때, 계속 참조되어 있는 객체들은 survivor1에 복사가 되고 참조되지 않는 객체들은 GC가 메모리 해제를 하게 됩니다. 이때 eden 영역에 GC가 일어날 때 survivor2에 객체가 없더라도 eden 영역과 survivor2영역은 같이 메모리해제가 됩니다. eden영역에 객체가 생성이 되고 또 메모리가 차게 되면 메모리해제를 위해서 GC가 발생합니다. 이때, eden영역과 survivor1영역에 있는 객체들 중 참조되는 객체들은 survivor2로 복사되고, eden영역과 survivor1영역은 메모리해제가 일어납니다. 이렇게 계속 메모리가 차게되고 survivor1과 survivor2영역으로 값 복사가 계속 일어난 후, 앞으로도 계속 참조가 될 것이라고 예상 되는 객체들은 Old영역으로 객체의 주소값이 복사됩니다.

Old영역

Young영역에서 저장되어 있던 객체 중 오래 살아남은 객체들이 저장되는 영역입니다.
Sun JVM의 Heap
JVM중 가장 흔하게 사용되고 있다고 생각되는 Sun JVM의 Heap특성을 한마디로 표현하면 Generation Heap이라고 할 수 있다. 이것은 Short-Lived object에 대한 빠른 생성과 제거를 보장한다는 성격도 함께 지니고 있다.
Sun JVM의 Heap은 Old Generation(Tunured space라고도 불림)과 Young Generation으로 구성되어 있다. Young Generation은 다시 Eden과 두개의 Survivor Space(From Space, To Space)로 나누어 볼 수 있다.
이 Heap영역의 동작 과정을 간략하게 살펴 본다면 보통 Object는 Eden에서 생성되며 Eden이 Full이 될 때 Live Object는 From Space로 카피된다. 다시 Eden이 full이 되면 From Space에서 To space로 이동된다. 그리고 Eden이 다시 Full일 때 Object가 사용되거나 참조되고 있는 상태라면 obejct는 old generation으로 이동하게 된다. 이 과정을 그림으로 표현하면 아래와 같다.

이 내용은 다시 정리해야함

Object는 Eden에서 생성이 되고 Eden이 꽉 차게 되면 Memory copy가 발생한다고 하였다. 이러한 작업을 Minor Garbage collection이라 하고 Copy collection이라고도 한다. Minor Collection시 Garbage Collector는 Eden의 Object를 검사하여 reference가 없는 것 들은 제거하고 나머지 live object는 From space가 꽉 찰 때까지 Eden에서 From Space로 이동하게 된다. 이러한 작업은 짧은 생애를 가질 수 밖에 없는 object는 최대한 짧게 가지고 있겠다는 Sun JVM의 특징을 그대로 나타내어 준다.
Eden이 다시 차오르면 From Space의 Live Object는 To Space로 이동한다. 그러나 이것은 논리적으로 그렇다는 것이고 실제로 JVM은 각 Minor collection이 발생할 때 마다 Survivor Space의 포인터를 유지하고 있다가 단순히 From Space에서 To Space로 변경해 주는 것이다. Eden이 다시 full이 되면 To Space에 있는 Live Object는 Old Generation으로 카피된다.
Minor Garbage Collection은 매우 빠르고 효율적이다. 소요시간은 Young Generation의 크기에 따라 다르지만 1초 미만이다. 또한 JVM Thread Processing을 멈추는 등의 부작용도 발생하지 않는다.
Young Generation이 모두 꽉 차게 되면 Garbage Collector는 가용 메모리를 확보하기 위해 Major Garbage Collection을 수행한다. Major Garbage Collection은 Object들이 live상태로 있는지 여부를 파악하기 위해 모든 Thread의 수행을 잠시 동결시켜 살아있는 object를 표시하고 dead object는 제거하여 heap 을 정리한다. 이러한 작업 때문에 Major Garbage Collection을 mark and sweep collection이라고도 한다. 이 Major Garbage Collection은 Thread를 잠시 멈추게 되고 Mark and Sweep작업을 위해 CPU에 부하를 가하게 되며 이러한 작업은 보통 Minor Garbage Collection에 비해 10배 이상의 시간을 사용하기 때문에 성능에 악영향을 주게 된다.
Sun JVM은 앞서 언급한 대로 짧은 운명을 가지고 태어난 Object는 짧게, 그리고 장수할 운명을 지닌 Object는 오래도록 유지시키겠다는 의도를 지니고 있다고 하였다. Sun JVM을 사용할 때는 이러한 의도를 잘 살려 주는 것이 결국 좋은 성능을 내는 것과 밀접한 관계가 있다할 수 있다. 즉 Short Live Object는 Old Generation으로 올라가기 전에 Young Generation에서 제거되게끔 하고 Long Lived Object의 경우 Old Generation에 상주시켜 상대적으로 아주 저렴한 Minor Garbage Collection만으로 heap의 유지가 가능하게 유도하는 것이 좋다.
이를 위해서는 JVM의 Memory구성이 중요한데 Young Generation은 전체 Heap의 1/2보다 약간 적게 Survivor Space는 Young Generation의 1/8정도의 크기가 적당하다. 이렇게 Heap을 구성하기 위해서는 별도의 Option을 주어야 한다. JVM의 Default의 경우는 Young Generation이 작게 잡혀있기 때문에 Default를 사용하는 것은 권장하지 않는다. (이에 대한 Memory Option과 실제 설정의 예는 다음의 글에서 다루기로 한다.) 다시 얘기하지만 Young Generation이 작으면 short Live Object가 Old Generation으로 넘어갈 확률이 커지고 이는 결국 Major Garbage Collection의 발생확률이 높아지는 것이다.
혹시 이 글을 읽으면서 그렇다면 골치 아프게 생각하지 말고 Young Generation으로 heap을 꽉 채우면 되지 않겠느냐하고 생각하는 사람이 있을 지도 모르겠다. 그렇게 되면 Major Garbage Collection은 발생하지 않을 테니 말이다. 그러나 Sun JVM은 Young Generation이 너무 크게 잡혀 있으면 Minor Garbage Collection때 Copy Collection을 하는 것이 아니라 Mark and Sweep Collection을 수행한다고 한다. 그러니 그 생각은 포기하는 것이 좋을 것이다.

profile
TIL 남기는 공간입니다

0개의 댓글