본문 바로가기

자바

[Java] JVM의 이해(2) - 주요 메모리와 static

지난번 포스트에서 JVM의 구조에 대해 살펴봤다.

이번에는 JVM이 메모리를 어떻게 관리하는지 들여다보려고 한다. Runtime Data Area 중 주요 부분을 자세히 살펴보자.

  1. Method Area
    • 클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간
    • Runtime Constant Pool 영역을 통해 상수 자료형을 저장하여 참조하고 중복을 방지한다.
  2. Heap Area
    • 자바로 구성된 객체 및 JRE 클래스가 탑재되는 영역
    • String Pool, 실제 데이터를 가진 인스턴스, 배열 등 저장
  3. JVM Stack Area
    • 메소드 안에서 사용되는 값, 메소드의 매개변수, 지역변수, 리턴값 및 연산 시 일어나는 값 저장
  4. PC Register
    • 현재 수행중인 JVM 명령의 주소를 갖는다.
  5. Native method stack
    • 다른 프로그래밍 언어로 작성된 메서드 호출 코드를 수행하는 스택

JVM 주요 메모리

Method Area

이 부분을 처음 알게 되면 힙 영역과 저장하는 데이터가 유사하다고 생각할 수 있다. 모든 코드는 결국 어떤 인스턴스 위에서 동작하게 되는 것 아닌가.

메서드 영역은 주로 정적인 데이터를 저장한다. 프로그램 실행 코드, 클래스가 구성된 양식, 스태틱 변수, Constant Pool 상수 풀… 변경될 수 없는, 변경되어서는 안되는 값들이 이 영역에 해당된다.

Heap Area

그렇다면 힙 영역은? 힙 영역에도 클래스가 저장이 되는데? 메서드 영역과는 다른 게 뭐지?

메서드 영역에는 클래스를 저장한다면 힙은 클래스라는 틀로 찍어낸 반죽들이 몸을 뉘이는 장소다. 즉, 그 클래스의 인스턴스를 저장한다.

그 외에도 객체와 배열 등 런타임에 생성된 인스턴스들이 바로 이곳에 저장되는 것이다.

더 자세한 구조는 나중에 가비지 컬렉터를 설명하면서 함께 이야기하도록 하자.

Stack Area

위의 내용을 이해하고 나면 이 부분은 좀 쉽다. 스택 영역은 마지막에 들어온 값이 먼저 나가는 LIFO 형태로 구현되어 있다. 메서드를 호출할 때마다 메서드만을 위한 공간인 스택 프레임이 생성되고 그 안에서 사용되는 값을 저장한다. 매개변수, 지역변수, 리턴 값 등이 저장되는 장소이다. 메서드의 수행이 마무리되면 해당 프레임은 삭제된다.

이들은 또한 힙 영역을 참조하고 있다. main 클래스에서 사용하는 객체의 인스턴스는 heap 영역에 저장되어 있기 때문에 이들을 참조하는 것은 당연한 일이다.

 

Class Loader의 메모리 접근

앞선 내용들로 모두 알고 있다시피 클래스 로더는 실행하는 클래스들을 링킹하고 로드하는 작업을 거친다. 그렇다면 static 변수는 언제 최초로 메모리에 올라가게 되는 것일까.

곰곰히 생각해보면 static 변수는 프로그램이 종료할 때까지 살아있는 것으로 알려져있다. 그러다보니 프로그램을 처음 시작할 때 한번에 올라가지 않을까, 오해를 한 적이 있다.

실제로 찾아보면 금방 답은 나온다. 클래스 로더는 호출을 하냐, 안하냐를 기준으로 동적으로 클래스와 변수들을 메모리에 올린다. 이 내용은 static 으로 선언한 것도 마찬가지다. 실제로 static 메서드를 가진 클래스를 선언하고 호출하지 않더라도 static 메서드를 호출하면 사용 가능하다.

당연한 이야기지만 실제로 클래스를 언제 사용할지도 모르는데 일단 로드하는 것은 비효율적이다.

 

 

💡 자주 있는 일은 아니지만 임의로 클래스 로더를 만들어서 사용하는 경우가 있다.

라이브러리 패키지 명과 본인이 만든 라이브러리의 패키지명이 겹치는데 모두 필요한 경우 두개를 구분해서 로드할 수 있는 클래스 로더가 필요하다.

  • 어떤 네임 스페이스에서 클래스를 읽어서 쓸지
  • 클래스 이름은 같지만 라이브러리에서 가져온 것을 쓸지…

등을 취사선택하여 로드하도록 클래스 로더를 커스텀 하는 경우다.

 

💡 static 때문에 memory leak 메모리 누수가 발생하는 경우

static 변수는 생성되고 프로그램이 종료할 때까지 살아있다. 그러다보니 static이 큰 용량의 메모리를 홀드하고 있다면 방치되고 메모리 누수가 발생하기 쉽다. 예를 들어 ArrayList 를 static 으로 잡는다고 하자. 그러면 코드 상황에 따라 ArrayList에는 수많은 데이터가 add 되며 늘어나는 게 흔한 경우다. 때문에 언제든 늘어날 수 있는 자료구조를 static 형태로 선언하는 것은 지양하는 게 좋다.

 

만약 서비스 상에서 정말 필요하다고 하면 구아바 같은 곳에서 제공하는 immutable 형태의 자료구조 라이브러리를 활용하도록 하자.