Java 런타임 데이터 영역
C/C++ 개발자는 메모리 관리 측면에서 보면 각 객체의 소유권과 라이프타임 모두 관리할 책임을 지기 때문에 잡다한 것들을 다 하는 노가다꾼이라고 할 수 있다.
반대로 자바 개발자는 가상 머신이 제공하는 가비지 컬렉터 덕분에 메모리 할당과 해제를 직접 하지 않아도 메모리 누수나 오버플로 문제를 거의 겪지 않는다. 하지만 통제권을 위임 했기에 문제가 생기면 가상머신의 메모리 관리 방식을 이해하지 못하는 한 해결하기가 상당히 어렵다.
런타임 데이터 영역
자바 가상 머신은 런타임 환경에 필요한 메모리를 몇 개의 데이터 영역으로 나누어서 관리한다. 각 영역은 목적과 생성/삭제 시점이 존재한다. 어떤 영역은 가상 머신 프로세스의 시작과 동시에 만들어지며, 어떤 영역은 사용자 스레드의 시작/ 종료에 맞춰 생성/삭제 된다.
자바 가상 머신 명세에 따르면 자바 가상 머신이 관리하는 메모리는 다음과 같은 런타임 데이터 영역들로 구성된다.

프로그램 카운터
프로그램 카운터 레지스터는 작은 메모리 영역으로 현재 실행중인 스레드의 바이트코드 줄 번호 식별자라고 생각하면 쉽다. 바이트코드 인터프리터는 카운터 값을 바꿔 다음에 실행할 바이트코드 명령어를 선택한다. 프로그램의 제어 흐름, 분기, 순환, 점프 등을 표현하며 예외 처리나 스레드 복원 같은 모든 기본 기능이 해당 표시기를 사용해서 이루어진다.
자바의 가상 머신에서 멀티 스레딩은 CPU 코어를 여러 스레드가 교대로 사용하는 방식으로 구현되기 때문에 특정 시점에서 각 코어는 한 스레드의 명령어만 처리하게 된다. 따라서 스레드 전환 후 이전에서 실행하다 멈춘 지점을 정확하게 복원하려면 스레드 각각에 고유한 프로그램 카운터가 필요하다. 따라서 각 스레드의 카운터는 서로 영향을 주지 않는 독립된 영역에 저장되고 이 메모리 영역을 스레드 프라이빗 메모리라고 한다.
번외로 스레드가 자바 메서드를 실행 중일 때는 실행 중인 바이트 코드 명령어의 주소가 프로그램 카운터 값에 기록되는데 native 메서드를 실행 할 때의 카운터 값은 undefined로 책정되고 이는 OutOfMemoryError 조건이 명시되지 않은 유일한 영역이다.
자바 가상 머신 스택
자바 가상 머신 스택도 스레드 프라이빗 하며, 연결된 스레드와 lifetime이 동일하다. 가상 머신 스택은 자바 메서드를 실행하는 스레드의 메모리 모델을 설명해준다. 각 메서드가 호출될 때마다 자바 가상 머신은 스택 프레임을 만들어 지역 변수 테이블, 피연산자 스택, 동적 링크, 메서드 반환값 등의 정보를 저장한다. 그런 다음 스택 프레임을 가상 머신 스택에 푸시/팝 하는 일을 반복한다. 사람들이 주로 말하는 자바의 스택 영역은 자바 가상 머신 스택에서 주로 지역 변수 테이블을 가리킨다.
지역 변수 테이블에는 자바 가상 머신이 컴파일 타임에 알 수 있는 다양한 기본 데이터 타입, 객체 참조, 반환 주소 타입, 반환 주소 타입을 저장한다. 지역 변수 테이블에서 이 데이터 타입들을 저장하는 공간을 지역 변수 슬롯이라고 하며 일반 적으로 슬롯 하나의 크기는 32비트로 double과 같은 64비트는 2개를 차지하고 나머지 타입은 1개에 저장된다. 지역변수 테이블을 구성하는데 필요한 데이터 공간은 컴파일 과정에서 할당된다.
네이티브 메서드 스택
자바 가상 머신 스택과 매우 비슷한 역할을 한다. 차이점이라고 하면 가상 머신 스택은 자바 메서드를 실행할 때 사용하고, 네이티브 메서드 스택은 네이티브 메서드를 실행할 때 사용한다.
자바 힙
자바 힙은 애플리케이션이 사용할 수 있는 가장 큰 메모리이다. 자바 힙은 모든 스레드가 공유하며 가상 머신이 구동 될 때 만들어진다. 이 메모리 영역의 유일한 목적은 객체 인스턴스를 저장하는 것이고 거의 모든 객체 인스턴스가 이 영역에 할당된다. 힙은 가비지 컬렉터가 관리하는 메모리 영역이기 때문에 GC 힙이라고도 한다.
힙은 물리적으로 떨어진 메모리에 위치해도 상관 없으나 논리적으로는 연속되어야 한다. 하지만 대다수 가상 머신이 저장 효율을 높이고 구현 로직을 단순하게 유지하기 위해 큰 객체(주로 배열 객체)는 물리적으로도 연속된 메모리 공간을 사용하도록 구현된다.
메모리 할당 관점에서 자바 힙은 모든 스레드가 공유한다. 따라서 객체 할당 효율을 높이고자 스레드 로컬 할당 버퍼 여러 개로 나뉜다. 이는 자바 힙을 다시 작게 구분해 메모리 회수와 할당을 더 빠르게 도와준다.
메서드 영역
모든 스레드가 공유하는 영역으로 가상 머신이 읽어 들인 타입 정보, 상수, 정적 변수 그리고 JIT 컴파일러가 컴파일한 코드 캐시 등을 저장하는데 이용된다.
메서드 영역도 논리적으로는 힙의 일부로 기술되지만 간단한 구현에 있어서는 가비지 컬렉터나 압축을 따로 진행하지 않기에 논힙 이라고 불리기도 한다.
영역에 제약을 거의 두지 않아서 힙과 마찬가지로 데이터가 연속될 필요가 없고, 크기를 동적으로 선언해도 되고 심지어 가비지 컬렉션을 사용하지 않아도 된다.
런타임 상수 풀
런타임 상수 풀은 메서드 영역의 일부이다. 상수 풀 테이블에는 클래스 버전, 필드, 메서드, 인터페이스 등 클래스 파일에 포함된 설명 정보에 더해 컴파일 타임에 생성된 다양한 리터럴과 심볼 참조가 저장된다. 가상머신이 클래스를 로드할 때 이러한 정보를 메서드 영역의 런타임 상수 풀에 저장한다.
클래스 파일의 상수 풀과 비교해 보았을 때 런타임 상수 풀은 동적이라는 특징을 가지고 있다. 자바 언어에서 상수가 꼭 컴파일 타임에 생성되어야 한다는 규칙이 없다. 다시 말해 상수 풀의 내용 전부가 클래스 파일에 미리 완벽하게 기술되어 있는것이 아니라 런타임에도 메서드 영역의 런타임 상수 풀에 새로운 상수가 추가될 수 있다는 뜻이고 이를 잘 반영한 것이 String클래스의 intern() 메서드이다.
다이렉트 메모리
다이렉트 메모리는 가상 머신 런타임에 속하지 않지만 자주 쓰이는 메모리이며 OutOfMemorry의 원인이 될 수 있어 설명하기 위해 가져왔다.
JDK1.4에서 추가된 NIO는 힙이 아닌 메모리를 직접 할당할 수 있는 네이티브 함수 라이브러리를 이용하고 해당 메모리에 저장 되어있는 DirectByteBuffer개체를 통해 작업을 수행하는데 이때 직접 할당할 수 있는 메모리가 다이렉트 메모리이다.
물리 메모리를 직접 할당하기 대문에 자바 힙 크기의 제약과는 무관하지만, 이 메모리 역시 메모리이기에 기기의 총 메모리 용량, 프로세서가 다룰 수 있는 주소공간을 넘어설 수는 없다. 하지만 서버 관리자들이 -Xmx 등의 매개 변수 를 설정할 때 가상 머신의 메모리 크기만 고려할 뿐 다이렉트 메모리는 간과하는 경우가 제법 있다.