나는 요즘 Github에 기술 스크립트를 정리하고 있다. 알고 있다고 생각하는 것도 실제 말로 내뱉는 게 어렵기 때문에 글로써 정리할 필요가 있겠다고 생각했고, 나중에 기술 면접에도 도움이 되지 않을까 싶었다. Java의 구조부터 차근차근 정리하려고 계획 중이고 너무 기초적인 것은 넘어가려고 한다.
Java의 큰 장점 중 하나는 플랫폼(Operating System)에 종속되지 않고 독립적이라는 것인데 이런 장점을 제공하는 것이 JVM이다. JVM의 아키텍처는 핵심 기능을 하는 여러 개의 컴포넌트들로 이루어져 있지만, 첫 번째로 Runtime Data Area에 대한 탐구를 해보고자 한다. 상대적인 네이티브 언어와는 다르게 Java는 개발자가 Storage를 관리할 필요가 없는 Garbage Collector(GC)의 기능을 가지고 있기 때문에 오히려 메모리에 대해 간과하지 않고 구조에 대한 이해가 더 필요하다 생각했다. 그리고 오히려 폐쇄적인 GC 때문에 메모리에 대한 이슈가 발생할 수도 있을 것이다.
Java의 메모리 구조에 대한 탐구를 계속하다 보니 StackOverFlow에 실제로 메모리들이 각자 정해진 구역에 저장되는 것이 맞는지 혼동의 질문들이 많은 것을 발견할 수 있다. Java 8 버전에서 메모리 영역에 변화가 있었는데 Java Heap에 자리를 차지하고 있던 Permgen 메모리 영역이 사라지고 MetaSpace 영역이 새로 생겼다는 것이다. MetaSpace는 Java Heap 영역이 아닌 Native Memory 영역에 위치하게 되었다. 상대적으로 메모리 크기가 작았던 Permgen에 비해 MetaSpace는 상당히 큰 메모리를 제공하고 JVM을 운영할 때 Native Memory에 사용할 크기를 지정해 줄 수도 있다. 사실 이 두 가지 영역에 대해서 탐구를 하게 된 것은 정적 변수의 저장 위치 때문이었다. 결론은 Heap Area에 저장된다는 것이었지만, 그냥 그렇다는 것만 알게 되었고 완벽히 이해는 하지 못하였다. 더 깊게 들어가면 오히려 정리할 주제에서 많이 벗어날 것 같아 삽질은 줄이고 정리를 했다.
- PC Register: JVM은 여러 개의 Thread를 실행할 수 있는데, 생성된 Thread는 PC Register를 갖게 됩니다. Thread가 생성되면 호출된 메소드를 실행하고, 실행된 메소드가 *네이티브가 아닐 경우 PC Register는 현재 실행 중인 JVM 명령의 주소를 저장합니다. 만약 현재 실행 중인 메소드가 *네이티브 메소드라면 PC Register 값은 정의되지 않습니다. 즉, JVM의 PC Register는 현재 실행 중인 JVM의 명령 주소 값을 저장하는 역할을 수행합니다.
* 네이티브 메소드: 자바 언어가 아닌 다른 언어로 프로그래밍된 코드, 상대적인 개념이다.
- Stack area: JVM Thread가 생성되면 동시에 Stack 영역이 각각의 Thread에 활성화됩니다. Thread의 Stack 영역은 프레임을 저장합니다. 프레임에 관한 내용은 나중에 더 자세히 다루기로 하겠고 지금은 한 메소드가 실행될 때 필요한 값들을 저장하는 공간이라고 정의하겠습니다. 이 프레임은 Push, Pop을 통해 제어되고 Stacks-Base(쌓는다는 의미를 가지고 있다)이기 때문에 프레임이 추가로 생성될 수 있습니다. 단어의 의미에서 알 수 있듯이 메소드가 호출되면 프레임이 Push 되고 종료가 되면 프레임이 Pop이 되어 프레임에 할당된 메모리를 모두 삭제합니다. Stack의 크기는 런타임시 동적으로 결정되고 개발자가 임의로 그 크기를 지정할 수도 있습니다.
* Local Variable: 메소드 내에 선언된 지역 변수들
* Operand Stack: 계산을 위한 공간, 프레임 생성 시 생성된다.
* Constant pool reference: Method Area 안에 존재하는 Runtime Constant Pool의 인덱스 값을 가지고 있다. 참조 값으로 가지고 있고 해당 값이 필요하면 Runtime Constant Pool에 찾아가 인덱스에 해당하는 값을 찾아올 수 있다.
- Heap area: 모든 JVM Thread 간에 공유되는 메모리 공간입니다. Heap에는 모든 클래스의 인스턴스, 배열에 대한 값들이 저장됩니다. 객체에 대한 Heap Storage는 개발자가 명시적으로 관리하지 않고 가비지 컬렉터(GC, Garbage Collector)이라는 기능을 통해 사용하지 않는 Storage는 자동으로 회수됩니다. Heap의 크기는 런타임시 동적으로 결정되고 개발자가 임의로 그 크기를 지정할 수도 있습니다.
* String pool: String은 선언 방식에 따라 메모리 영역의 차이가 생긴다. new 연산자를 이용한다면 기존 Heap Storage 안에 생성되지만 문자열을 통해 생성할 경우 Heap Area의 String pool에서 관리하게 된다.
- Method area: 모든 JVM Thread 간에 공유되는 메모리 공간입니다. *런타임 상수 풀(Runtime Constant Pool), 필드, 메소드 로컬 데이터, 메소드 코드, 생성자 코드 등 클래스의 구조에 대한 정보를 저장합니다.
* Runtime Constant Pool: 클래스 정보가 로드될 때 생성되고, 런타임 시 반드시 확인이 필요한 상수들을 담아 놓는 공간입니다.
- Native Method area: 자바 프로그래밍 언어가 아닌 다른 언어로 작성된 네이티브 코드를 저장하는 영역입니다. 보통 C 계열 언어의 기능을 수행합니다. JNI(Java Native Interface)를 통해서 네이티브 메소드와 자바 메소드를 연결할 수 있습니다.
Reference
Oracle Java Document
http://java8.in/java-virtual-machine-run-time-data-areas/
https://johngrib.github.io/wiki/jvm-stack/
https://www.geeksforgeeks.org/how-to-initialize-and-compare-strings-in-java/
'Java' 카테고리의 다른 글
[java] HttpURLConnection을 이용한 API 통신 방법 (0) | 2023.02.24 |
---|---|
[java] Stream API Basic (0) | 2023.02.20 |