[10분 테코톡] 나는 JVM를 모르고 개발했다.
부끄러운 일이다.
Java
개발자면서 Java
의 핵심인 JVM
에 대해서 잘 모르고 있었다…
그냥 대충 JVM이 OS와 JAVA 사이에 동작하는 프로그램이야~
정도로만 이해하고 대충 넘겼다.
늦었지만, Java 개발자면서 Java의 핵심인 JVM에 대해서 잘 모르는 건 말이 안된다고 생각한다.
그래서 이번 기회에 공부하고 정리해보려고 합니다.😄
그중 10분 테코톡에서 어썸오님의 발표 영상이 많은 도움이 됐습니다.
여러분도 꼭 들어보시는 것을 강추 드립니다. 👍
참고:
YouTube
NAVER D2
Hotspot JVM Heap 메모리 구조: Java7, Java8 비교하여
Java의 특징
JVM
에 대해서 설명하기에 앞서Java
의 특징에 대해서 간략하게 설명 하겠습니다.
- Write once, run anywhere!
- 자바는 OS에 독립적인 특징을 가지고 있습니다.
- OS 별 JVM이 필요함
Java
는OS
에 독립적이지만,JVM
은OS
에 종속적이다! 🙂
- 자바는 객체지향 프로그래밍 언어
- 기본 자료형을 제외한 모든 요소들이 객체로 표현되고, 객체 지향 개념의 특징인 캡슐화, 상속, 다형성이 잘 적용된 언어 입니다.
- 네트워크와 분산처리를 지원
- 소켓 프로그래밍, 서블릿 등등
- 멀티스레드를 지원
- 동적 로딩 지원
- 실행시 모든 클래스가 로딩되지 않고 필요한 시점에 클래스를 로딩해서 사용
장점
JVM
위에서 동작하기 때문에 운영체제에 독립적 입니다.- 다양한
OS
가 공존하는 인터넷 환경에 적합한 언어 입니다.
- 다양한
GC
를 통한 자동적인 메모리 관리가 가능합니다.- 지금도 오라클에서 꾸준하게 자바의 성능을 개선하여 새로운 버전을 지원합니다.
단점
JVM
위에서 동작하기 때문에 실행 속도가 느립니다.java byte code
를 하드웨어 기계어로 바로 변환해주는JIT
컴파일러 도입으로JVM
기능이 향상됨- 자바9 부터는 런타임 라이브러리를 모듈화 하여 실행 속도를 단축 시켰습니다.
- 다중 상속이나 타입에 엄격하며, 제약이 많습니다.
맨날 헷갈리는 JDK와 JRE 🤔
JDK
는 자바개발 도구를 의미합니다.JRE
+javac.exe
(개발에 필요한 실행파일)JVM
+Java API
(클래스 라이브러리) +javac.exe
(개발에 필요한 실행파일)
JRE
는 자바 실행 환경을 의미합니다.(자바로 작성된 응용프로그램이 실행되기 위한 최소환경)JVM
+Java API
(클래스 라이브러리)
JVM 역할
Java
소스만 튜닝해서는 최적의 성능을 보장할 수 없습니다.Java compiler
를 통해 변환된 class
파일(byte code
)을 최적의 상태로 수행할 수 있도록 JVM
옵션을 튜닝하는 과정까지 거쳐야 최적의 성능을 발휘 할 수 있습니다.
JVM
옵션을 튜닝하기 위해서는 반드시 JVM Heap 메모리 구조
에 대해 알아야 정확한 진단이 가능합니다.
이 이야기는 나중에 하고 먼저 JVM
의 구조에 대해서 알아봅시다.
참고
JVM은 Java 애플리케이션 1개당 1개가 생성됩니다.
JVM 특징
- 자바로 작성된 프로그램은 모두
JVM
에서만 실행됩니다.
(Java 애플리케이션
<—>JVM
<—>OS
<—>컴퓨터
)- 자바 바이트 코드를 실행하고자 하는 모든 하드웨어에
JVM
을 동작시킴으로써 자바 실행 코드를 변경하지 않고도 모든 종류의 하드웨어에서 동작함
- 자바 바이트 코드를 실행하고자 하는 모든 하드웨어에
- 대표적인 컴퓨터 아키텍처인 인텔 x86 아키텍처나 ARM 아키텍처와 같은 하드웨어가 레지스터 기반으로 동작하는데 비해
JVM
은 스택 기반으로 동작하며,Java byte code
를OS
에 맞게 해석 해주는 역할을 하고GC
를 통해 자동적인 메모리 관리를 해줍니다.- 레지스터는 하드웨어마다 다르기 때문에, 자바의 WORA(Write Once Run Anywhere) 목표를 달성할 수는 없었다.
이러한 이유로 스택 기반으로 동작하도록 했다.
- 레지스터는 하드웨어마다 다르기 때문에, 자바의 WORA(Write Once Run Anywhere) 목표를 달성할 수는 없었다.
- 기본 자료형(
primitive data type
)을 제외한 모든 타입(클래스와 인터페이스)을 명시적인 메모리 주소 기반의 레퍼런스가 아니라 심볼릭 레퍼런스(Symbolic Reference
)를 통해 참조 합니다. - 클래스 인스턴스는 사용자 코드에 의해 명시적으로 생성되고
GC
에 의해 자동적으로 소멸 됩니다. JVM
은 기본 자료형을 명확하게 정의하여 호환성을 유지하고 플랫폼 독립성을 보장합니다.C/C++
등의 전통적 언어는 플랫폼에 따라int
형의 크기가 변합니다.
- 인텔 x86 아키텍처가 사용하는 리틀 엔디안이나, RISC 계열 아키텍처가 주로 사용하는 빅 엔디안 사이에서 플랫폼 독립성을 유지하려면 고정된 바이트 오더를 유지해야 합니다. 자바 클래스 파일은 네트워크 전송 시에 사용하는 바이트 오더인 네트워크 바이트 오더를 사용합니다.(네트워크 바이트 오더는 빅 엔디안)
JVM
은 다음과 같이 구성됩니다.
- 자바 인터프리터(interpreter)
- 자바 컴파일러에 의해 변환된
java byte code
를 읽고 해석하는 역할을 하는 것java byte code
는 자바와 기계어 사이의 중간 언어이며 자바 코드를 배포하는 가장 작은 단위
- 자바 컴파일러에 의해 변환된
- 클래스 로더(class loader)
- 동적으로 클래스를 로딩해주는 역할
- 프로그램이 실행 중인 런타임에서 모든 코드가
JVM
과 연결(자바는 동적 로딩을 지원)
- 프로그램이 실행 중인 런타임에서 모든 코드가
- 동적으로 클래스를 로딩해주는 역할
- JIT 컴파일러(just in time compiler)
- 프로그램이 실행 중인 런타임에
java byte code
를 실제 네이티브 코드로 변환해 주는 컴파일러를 의미 - 프로그램 실행속도 향상을 목적으로 개발(인터프리터만 사용하면 너무 느림)
- 프로그램이 실행 중인 런타임에
- 가비지 컬렉터(garabge collector)
- 사용하지 않은 메모리를 자동으로 회수
- 개발자는 개발에만 집중 🧑💻
JIT과 AOT 컴파일러 비교
- JIT 컴파일러(
just in time
)- 런타임(실행 시점)에 바이트 코드를 기계어로 번역
AOT
보다 상대적으로 느림
- AOT 컴파일러(
ahead of time
)- 빌드 시점에 기계어로 번역
두 컴파일러는 아래 글에서 더 자세하게 이야기 하겠습니다...!
Java 애플리케이션의 동작 과정
Java
애플리케이션이 실행되면JVM
이OS
로 부터 메모리 할당- 자바 컴파일러(
javac.exe
)가 자바 소스코드(.java
)를 읽어 바이트 코드(.class
)로 변환 JVM
에서class loader
를 통해class
파일을 로딩- 로딩된
.class
파일을Execution engine
을 통해 실행
클래스 로더
자바는 컴파일 타임이 아니라 런타임에 클래스를 처음으로 참조할 때 해당 클래스를 로드하고 링크하는 특징이 있습니다.
이러한 것을 동적 로드라고 하는데, 이 동적 로드를 담당하는 부분이 바로 바로 바로 JVM
클래스 로더 입니다.
클래스 로더의 특징
- 계층 구조
- 클래스 로더 끼리 부모-자식 관계를 이루어 계층 구조로 생성
- 최상위 클래스 로더는 부트스트랩 클래스 로더(
Bootstrap Class Loader
)
- 위임 모델
- 계층 구조(부모-자식)를 바탕으로 클래스 로더끼리 로드를 위임하는 구조로 동작
- 클래스를 로드할 때 먼저 상위 클래스 로더를 확인하여 상위 클래스 로더에 있다면 해당 클래스를 사용하고, 없다면 로드를 요청 받은 클래스 로더가 클래스를 로드 함
- 쉽게 이야기 하면 내가(클래스 로드를 요청 받은 클래스) 아이스크림을 살때(클래스 로드를 할 때) 먼저 부모님(상위 클래스 로더)에게 아이스크림(클래스)이 있는 지 확인하여 부모님(상위 클래스 로더)이 아이스크림(클래스)이 있으면 부모님(상위 클래스)에게 아이스크림(클래스)을 받고, 없다면 내가 아이스 크림을 산다.(클래스 로드를 요청받은 클래스 로더가 클래스를 로드함)
- 가시성 제한
- 하위 클래스 로더는 상위 클래스 로더의 클래스를 찾을 수 있지만, 상위 클래스 로더는 하위 클래스 로더의 클래스를 찾을 수 없음
- 자식은 부모의 재산을 물려 받으나, 부모는 자식의 재산을 물려받을 수 없음
- 하위 클래스 로더는 상위 클래스 로더의 클래스를 찾을 수 있지만, 상위 클래스 로더는 하위 클래스 로더의 클래스를 찾을 수 없음
- 언로드 불가
- 클래스 로더는 클래스를 로드할 수 있디만 언로드할 수는 없다.
- 들어올 땐 마음대로지만 🙆♂️, 나갈땐 아니란다..! 🙅♂️
- 언로드 대신, 현재 클래스 로더를 삭제하고 새로운 클래스 로더를 생성하는 방법을 사용
- 클래스 로더는 클래스를 로드할 수 있디만 언로드할 수는 없다.
클래스 로더의 종류(클래스 로더의 위임 모델)
- 부트스트랩 클래스 로더(Bootstrap Class Loader)
JVM
을 기동할 때 생성되며,Object 클래스
들을 비롯하여자바 API
들을 로드- 네이티브 코드로 구현(다른 클래스 로더는 자바로 구현)
- 익스텐션 클래스 로더(Extension Class Loader)
- 기본
자바 API
를 제외한 확장 클래스들을 로드 - 다양한 보안 확장 기능 등을 여기에서 로드
- 기본
- 시스템 클래스 로더(System Class Loader)
- 애플리케이션 클래스들을 로드(사용자가 지정한
$CLASSPATH
내의 클래스들을 로드)- 부트스트랩 클래스 로더와 익스텐션 클래스 로더가
JVM
자체의 구성 요소들을 로드
- 부트스트랩 클래스 로더와 익스텐션 클래스 로더가
- 애플리케이션 클래스들을 로드(사용자가 지정한
- 사용자 정의 클래스 로더(User-defined Class Loader)
- 애플리케이션 사용자가 직접 코드 상에서 생성해서 사용하는 클래스 로더
WAS
같은 프레임워크는 웹 애플리케이션, 엔터프라이즈 애플리케이션들이 서로 독립적으로 동작하게 하기 위해서 사용자 정의 클래스 로더를 사용- 클래스 로더의 위임 모델을 통해 애플리케이션의 독립성을 보장
- https://hbase.tistory.com/174
클래스 로드 단계
- 로드(Loading)
- 클래스를 파일에서 가져와서 JVM 메모리에 로드
- 검증(Verifying)
- 읽어온 클래스가 자바 언어 명세(
Java Language Specification
) 및JVM
명세에 맞게 잘 구성되었는지 검사 - 클래스 로드의 전 과정 중에서 제일 까다로운 검사를 수행하는 과정
- 가장 시간이 많이 걸림 ⏰
JVM TCK
의 테스트 케이스 중에서 가장 많은 부분이 😈 잘못된 클래스를 로드하여 정상적으로 검증 오류를 발생시키는지 테스트 하는 부분
- 읽어온 클래스가 자바 언어 명세(
- 준비(Preparing)
- 클래스가 필요로 하는 메모리를 할당하고, 클래스에서 정의된 필드, 메소드, 인터페이스들을 나태나는 데이터 구조 준비
- 분석(Resolving)
- 클래스의 Constant Pool 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경
- 초기화(Initializing)
- 클래스 변수들을 적절한 값으로 초기화
- static initializer 들을 수행
- static 필드들을 설정된 값으로 초기화
JVM
명세는 이들 작업들에 대해 명시하고 있으나 작업에 따라서 수행 시점은 유연하게 적용할 수 있도록 하고 있다.
Execution engine
Runtime Data Area
에 할당된 바이트 코드를 실행시키는 주체Runtime Data Area
는 애플리케이션이 동작하기 위해 OS
에서 할당받은 메모리 공간을 의미합니다.
실행엔진(Execution engine
)이 어떻게 동작하는 지는 JVM
명세에 없습니다.
따라서 JVM
벤더들은 다양한 기법으로 실행 엔진을 향상시키고 다양한 방식의 JIT 컴파일러를 도입하고 있습니다.
- Interpreter
- 바이트 코드 명령을 하나씩 읽어서 해석하고 실행
- 하나하나의 해석을 빠르지만 실행은 느림
- JIT(Just In Time) 컴파일러
interpreter
방식으로 실행하다가 반복되는 코드를 발견하여 바이트 코드 전체를 컴파일하여 네이티브 코드(자바의 부모가 되는 C, C++, 어셈블리 언어)로 변환- 이후에는 메소드를 더 이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식
- 네이티브 코드를 실행하는 것이 인터프링 방식보다 빠르고, 네이티브 코드는 캐시에 보관하기 때문에 더 빠르게 수행 됨
interpreter
의 단점 해소
JIT 컴파일러가 컴파일 하는 과정은 바이트 코드를 하나씩 인터피리팅 하는 것보다 훨씬 오래 걸립니다.
만약에 한 번만 실행되는 코드라면 컴파일 하지 않고 인터프리팅 하는 것이 훨씬 유리합니다.
따라서, JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 확인하고, 일정 기준을 넘을 때만 컴파일을 수행(네이티브 코드로 변환) 합니다.
Hotspot Compiler
JIT 컴파일러는 바이트 코드를 일단 중간 단계의 표현인 IR(Intermediate Representation
)로 변환하여 최적화를 수행합니다. IR(Intermediate Representation
)로 변환한 다음에 네이티브 코드를 생성 합니다.
오라클 핫스팟 VM은 핫스팟 컴파일러(Hotspot Compiler
)라고 불리는 JIT 컴파일러를 사용 합니다.
(핫스팟 이라고 불리는 이유는 내부적으로 프로파일링을 통해 가장 컴파일이 필요한 부분(핫스팟)을 찾아낸 다음에, 이 핫스팟을 네이티브 코드로 컴파일 하기 때문 입니다.)
핫스팟 VM은 한번 컴파일된 바이트 코드 라도 해당 메서드가 더 이상 자주 불리지 않는다면(핫스팟이 아니게 된다면), 캐시에서 네이티브 코드를 덜어내고 다시 인터프리터 모드로 동작 합니다.
핫스팟 VM은 서버 VM과 클라이언트 VM으로 나뉘어 있고, 동일한 런타임을 사용하지만, 각각 다른 JIT 컴파일러를 사용 합니다.
- Hotspot Client VM
- Hotspot Server VM
서버 VM에서 사용하는 Advanced Dynamic Optimizing Compiler
가 더 복잡하고 다양한 성능 최적화 기법을 사용하고 있다.
AOT(Ahead-Of-Time) 컴파일러
IBM JVM은 JIT 컴파일러뿐만 아니라 IBM JDK 6부터 AOT(Ahead-Of-Time
) 컴파일러라는 기능을 도입했습니다.
이는 한번 컴파일된 네이티브 코드를 여러 JVM이 공유 캐시를 통해 공유해서 사용하는 것을 의미합니다.
쉽게 이야기해서 AOT 컴파일러를 통해 이미 컴파일된 코드는 다른 JVM에서도 컴파일하지 않고 사용할 수 있게 하는 것입니다.
또한, 아예 AOT 컴파일러를 이용하여 JXE(Java EXecutable
)라는 파일 포맷으로 프리 컴파일(pre-compile
)된 코드를 작성하여 빠르게 실행하는 방법도 제공하고 있습니다.
자바 성능 개선의 많은 부분은 바로 이 실행 엔진(Execution engine)을 개선하여 이뤄지고 있습니다.^^
JIT 컴파일러는 물론 다양한 최적화 기법을 도입하여 JVM의 성능은 계속해서 향상되고 있습니다.
초창기 JVM과 지금의 JVM은 이 실행 엔진에서 큰 차이가 있습니다.
오라클 핫스팟 VM은 1.3부터 핫스팟 컴파일러를 내장하기 시작하였고, 안드로이드 Dalvik VM은 안드로이드 2.2부터 JIT 컴파일러를 도입하였다.
Runtime Data Area
애플리케이션이 동작하기 위해 OS
에서 할당받은 메모리 공간을 의미합니다.
쉽게 말하면, Class Loader
에서 준비한 데이터를 보관하는 장소 입니다.
런타임 데이터 영역은 6개의 영역으로 나눌 수 있습니다.
이중 Method Area
, Heap Area
, Runtime Constant Pool
는 모든 스레드가 공유하는 영역이며Stack Area
, PC Register
, Native Method Stack
은 스레드 마다 하나씩 생성 합니다.
- Method Area
JVM
이 시작될때 생성static
으로 선언된 변수들을 포함하여class
레벨의 모든 데이터가 이곳에 저장JVM
마다 단 하나의 Method Area 존재- 모든 스레드가 Method Area를 공유함
- 멀티스레드 프로그래밍을 할 때 동기화에 주의해야 하는 영역
- 저장되는 정보 종류
Field Info
: 멤버 변수의 이름, 데이터 타입, 접근 제어자의 정보Method Info
: 메소드 이름, 반환 타입, 매개변수, 접근 제어자의 정보Type Info
:class
인지interface
인지 여부 저장,Type
속성, 이름,super class
의 이름
Method Area
는JVM
벤더마다 다양한 형태로 구현 가능- 오라클 핫스팟 JVM(
Oracle Hotspot JVM
)에서는 흔히Permanent Area
또는Permanent Generation
(PermGen
)이라고 부름 GC
는JVM
벤더의 선택사항
- 오라클 핫스팟 JVM(
- Runtime Constant Pool
Method Area
에 포함(JVM
동작에서 가장 핵심적인 역할 수행)class
파일의constant pool
테이블에 해당하는 영역- 클래스 내에서 사용되는 상수들을 담고 있는 테이블
- 각 클래스와 인터페이스 상수, 메소드와 필드에 대한 모든 레퍼런스를 저장하는 테이블
JVM
은 어떤 메소드나 필드를 참조할 때Runtime Constant Pool
을 통해 해당 메소드나 필드의 실제 메모리 상 주소를 찾아 참조함- 스레드 공유 생명주기
- JVM 시작시 생성
- 프로그램 종료 시 사라짐
- 명시적으로 null 선언시 사라짐
- Heap Area
Runtime
에 생성되는 객체를 저장하기 위한 메모리의 영역- 대부분의 메모리 이슈 발생 근원지…
new
연산자로 생성된 모든Object
와Instance 변수
, 그리고배열
을 저장- 모든 스레드가 Heap Area를 공유함
- 멀티스레드 프로그래밍을 할 때 동기화에 주의해야 하는 영역
- 힙 구성 방식이나
GC
방법은JVM
벤더의 재량 - 두개의 영역으로 구분
- Young Generation
- 생명 주기가 짧은 객체를 GC 대상으로 하는 영역
- 오래 사용되는 객체를
Old Generation
으로 이동 시킴
- Old Generation
- 생명 주기가 긴 객체를 GC 대상으로 하는 영역
- Young Generation
- Stack Area(JVM Stack)
- 각 스레드를 위한 분리된
Runtime Stack
영역(각 스레드 마다 존재) - 스택 영역(
JVM Stack
)에 스택 프레임(Stack Frame
)을 추가(push
)하고, 제거(pop
)하는 동작만 수행- 메소드가 호출할 때 마다
Stack Frame
으로 불리는Entry
가Stack Area
에 생성- 쉽게 말해서 메소드를 실행하기 위한 정보가 저장되는 공간
- 스레드 역할 종료시 바로 소멸되는 특성의 데이터를 저장
- 메소드 호출이 끝나거나 예외가 발생하면 사라짐
- 예외 발생시
printStackTrace()
등의 메서드로 보여주는Stack Trace
의 각 라인은 하나의 스택 프레임을 표현!
- 메소드가 호출할 때 마다
- 각종 형태의 변수, 임시 데이터, 스레드 또는 메소드의 정보 저장
- 재귀를 사용할때 종단점(
base case
)에 오류가 있으면StackOverflowError
발생
- 각 스레드를 위한 분리된
e.printStackTrace
try {
//..비지니스 로직..//
} catch (Exception e) {
// Stack Trace의 각 라인은 하나의 스택 프레임을 표현
e.printStackTrace();
}
Stack Area(JVM Stack) 구성
스택 프레임(Stack Frame
)
- 스택 프레임(
Stack Frame
)JVM
내에서 메서드가 수행될 때마다 하나의 스택 프레임이 생성되어 해당 스레드의 JVM 스택에 추가되고 메서드가 종료되면 스택 프레임에서 제거- 각 스택 프레임은 지역 변수 배열(
Local Variable Array
), 피연산자 스택(Operand Stack
), 현재 실행 중인 메서드가 속한 클래스의 런타임 상수 풀(Reference to Constan Pool
)에 대한 레퍼런스를 갖고 있습니다. - 지역 변수 배열(
Local Variable Array
), 피연산자 스택(Operand Stack
)의 크기는 컴파일 시에 결정되기 때문에 스택 프레임 크기도 메서드에 따라 크기가 고정
지역 변수 배열(Local Variable Array
)
- 지역 변수 배열(
Local Variable Array
)- 0 부터 시작하는 인덱스를 가진 배열
- 0은 메서드가 속한 클래스 인스턴스의 this 레퍼런스
- 1부터는 메서드에 전달된 파라미터들이 저장
- 메서드 파라미터 이후에는 메서드의 지역 변수들이 저장
피연산자 스택(Operand Stack
)
- 피연산자 스택(Operand Stack)
- 메서드의 실제 작업 공간 🔨👷🏻
- 각 메서드는 피연산자 스택과 지역 변수 배열 사이에서 데이터를 교환하고, 다른 메서드 호출 결과를 추가(
push
)하거나 꺼냄(pop
) - 피연산자 스택 공간이 얼마나 필요한지는 컴파일할 때 결정할 수 있음
- 피연산자 스택의 크기도 컴파일 시에 결정
- PC Register
- PC는
Program Counter
를 의미 - 각
Thread
가 시작될 때 생성되며, 현재 실행중인JVM
명령의 주소를 저장하는 영역- 멀티스레드 프로그래밍에서
ThreadA
가 작업을 하다가CPU
점유를ThreadB
에 넘겨주고 다시ThreadA
로 돌아왔을 때, 이전에 어떤 명령을 수행했는지 기억하고 있어야TheradA
에 이전 작업이 어떤 명령을 수행했는지 알수 있음! 😁
- 멀티스레드 프로그래밍에서
Thread
가 로직을 처리하면서 지속적으로 갱신Thread
가 생성될 때마다 하나씩 존재- 어떤 명령을 실행해야 할지에 대한 기록(현재 수행중인 부분의 주소를 갖고있음)
- PC는
- Native Method Stack
- 바이트 코드가 아닌 실제 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행 시키는 영역
- JNI(
Java Native Interface
)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택- C/C++ 스택이 생성
- JNI(
Java Native Interface
)를 통해 바이트 코드로 전환하여 저장Java
가 다른 언어로 만들어진 애플리케이션과 상호 작용할 수 있는 인터페이스를 제공JVM
이Native Method
를 적재하고 수행할 수 있도록함(실질적으로 C, C++만 100% 호환)
- 각 스레드 별로 생성
Runtime Constant Pool
클래스 내에서 사용되는 상수들을 담고 있는 테이블 입니다.
예제를 통해서 동작 방법에 대해서 알아봅시다!
CodingStudy
public class CodingStudy {
public static void main(String[] args) {
Crew crew = new Crew();
crew.study();
}
}
Crew
public class Crew {
private int level = 2;
public void study() {}
}
Class 파일 경로
Class
파일이 저장되는 경로 입니다.(없다면 CodingStudy
를 한번 실행해 주세요.)
터미널 창에서 해당 경로로 이동하고, javap
명령어를 통해 class 파일
을 볼 수 있습니다.
$ cd /Users/study/java-basic/build/classes/java/main/hello
$ javap -v CodingStudy
클래스 파일 자체는 바이너리 파일이기 때문에 사람이 이해하기 쉽지 않습니다.
이러한 점을 보완하기 위해 JVM
벤더들은 javap
라는 역어셈블러(disassembler
)를 제공합니다.javap
를 이용한 결과물을 흔히 자바 어셈블리라고 부릅니다.
결과(자바 어셈블리)
public class hello.CodingStudy
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // hello/CodingStudy
super_class: #6 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #6.#23 // java/lang/Object."<init>":()V
#2 = Class #24 // hello/Crew
#3 = Methodref #2.#23 // hello/Crew."<init>":()V
#4 = Methodref #2.#25 // hello/Crew.study:()V
#5 = Class #26 // hello/CodingStudy
#6 = Class #27 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lhello/CodingStudy;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 crew
#19 = Utf8 Lhello/Crew;
#20 = Utf8 MethodParameters
#21 = Utf8 SourceFile
#22 = Utf8 CodingStudy.java
#23 = NameAndType #7:#8 // "<init>":()V
#24 = Utf8 hello/Crew
#25 = NameAndType #28:#8 // study:()V
#26 = Utf8 hello/CodingStudy
#27 = Utf8 java/lang/Object
#28 = Utf8 study
{
public hello.CodingStudy();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lhello/CodingStudy;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class hello/Crew
3: dup
4: invokespecial #3 // Method hello/Crew."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method hello/Crew.study:()V
12: return
LineNumberTable:
line 5: 0
line 6: 8
line 7: 12
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 args [Ljava/lang/String;
8 5 1 crew Lhello/Crew;
MethodParameters:
Name Flags
args
}
SourceFile: "CodingStudy.java"
자바 바이트 코드에서 메소드를 호출하는 명령어(OpCode)
- invokeinterface: 인터페이스 메서드 호출
- invokespecial: 생성자,
private
메서드, 슈퍼 클래스의 메서드 호출 - invokestatic:
static
메서드 호출 - invokevirtual: 인스턴스 메서드 호출
Constant pool
분석
동작 과정
Constant Pool Resolution
Hotspot JVM Heap 구조
Hotspot JVM Heap
메모리 구조는 크게 Young Generation
, Old Generation
영역으로 구분 됩니다.Young Generation
는 다시 Eden
, Survivor
영역으로 구분 됩니다.
- Hotspot JVM Heap
- Young Generation
- Eden
- Survivor
- Old Generation
- Young Generation
먼저, 모든 오브젝트(object
)는 힙 메모리 영역을 관리할 목적으로
일정한 기준을 가지고 각 영역을 소유 하고 있습니다…!
Eden
영역은 오브젝트가 힙 영역에 최초로 할당되는 공간 입니다.Eden
영역의 공간이 부족하게 되면 오브젝트의 참조 여부를 확인한 뒤,
참조되고 있다면 Survivor
영역으로 이동 하고
만약 더 이상 참조되지 않는다면 Eden
영역에서 제거 됩니다.
Minor GC 🧹
Survivor
영역은 0, 1 두개로 구분되는데,Eden
영역에서 Survivor
영역으로 오브젝트가 이동될 때는(오브젝트가 참조되고 있는 상황 이군요!)
한개의 Survivor
영역만 사용 됩니다.
Survivor0
으로 이동 할때는 Survivor1
이 초기화가 되고, Survivor1
으로 이동하면 Survivor0
이 초기화 됩니다.
이를 Minor GC
라고 합니다.
Full GC 🧹
특정 기준치 이상 Survivor
영역에서 살아남은 오브젝트는Old Generation
영역인 Tenured
영역으로 이동 합니다.
(특정 기준치는 오브젝트의 참조 횟수 및 생존 시간 등을 의미합니다.)
Tenured
영역에 있는 오브젝트는 비교적 오랫동안 참조 되었기 때문에
앞으로도 사용될 가능성이 높은 오브젝트들 입니다.😎
그런데 Tenured
영역도 공간이 부족하게 되면
우선순위 알고리즘에 따라 메모리 공간을 확보하는 과정이 발생하게 되는데…
이러한 과정을 Full GC
라고 합니다.
Permanent, Metaspace 영역의 비교
Permanent
영역은 클래스나 메소드의 메타(Meta
)정보를 저장하거나
정적(static
) 변수 또는 상수(Constant
)가 저장되는 공간으로
메타데이터 영역으로 불렸습니다.
하지만 Java 8
버전 부터 Permanent
영역이 Metaspace
영역으로 이름을 바꾸면서Native
영역으로 편입 되었습니다.
기존 힙 영역일 때는 JVM
에 의해 관리되었지만, 네이티브 영역으로 변경되었기 때문에
운영체제 레벨에서 관리하는 영역으로 변경되었습니다.
하지만, 기존 Permanent
영역에 속해있던
정적 변수와 상수는 힙 영역을 벗어나지 못하고 그대로 머물게 되었습니다.
다른 의미로는 힙 영역에서 관리되기 때문에 GC
의 대상이 되도록 현행을 유지한다는 의미다.
이 모든 개념을 한번에 머리속에 담기는 어렵겠지만,
꾸준히 반복해서 읽다보면 자연스럽게 머리속에 개념이 자리 잡을 것이라고 생각 합니다....😇
GC에 대해서는 다음시간에 더 상세하게 포스팅 하겠습니다. 🏃♀️
'0 + 프로그래밍 > 0 + Java' 카테고리의 다른 글
제어할 수 없는 코드가 포함된 로직을 메소드 변경 없이 테스트 코드를 작성하는 방법(오버라이딩) (0) | 2023.11.20 |
---|---|
[10분 테코톡] 나는 제너릭을 모르고 개발했다. (0) | 2023.04.19 |
[10분 테코톡] 나는 GC를 모르고 개발했다. (0) | 2023.03.07 |