0 + 프로그래밍/0 + Java

[10분 테코톡] 나는 JVM를 모르고 개발했다.

힘들면힘을내는쿼카 2023. 3. 3. 21:41
728x90
반응형

[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이 필요함
      • JavaOS에 독립적이지만, JVMOS에 종속적이다! 🙂
  • 자바는 객체지향 프로그래밍 언어
  • 기본 자료형을 제외한 모든 요소들이 객체로 표현되고, 객체 지향 개념의 특징인 캡슐화, 상속, 다형성이 잘 적용된 언어 입니다.
  • 네트워크와 분산처리를 지원
    • 소켓 프로그래밍, 서블릿 등등
  • 멀티스레드를 지원
  • 동적 로딩 지원
    • 실행시 모든 클래스가 로딩되지 않고 필요한 시점에 클래스를 로딩해서 사용

장점

  • 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 codeOS에 맞게 해석 해주는 역할을 하고 GC를 통해 자동적인 메모리 관리를 해줍니다.
    • 레지스터는 하드웨어마다 다르기 때문에, 자바의 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 애플리케이션의 동작 과정

  1. Java 애플리케이션이 실행되면 JVMOS로 부터 메모리 할당
  2. 자바 컴파일러(javac.exe)가 자바 소스코드(.java)를 읽어 바이트 코드(.class)로 변환
  3. JVM 에서 class loader를 통해 class 파일을 로딩
  4. 로딩된 .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 AreaJVM 벤더마다 다양한 형태로 구현 가능
      • 오라클 핫스팟 JVM(Oracle Hotspot JVM)에서는 흔히 Permanent Area 또는 Permanent Generation(PermGen)이라고 부름
      • GCJVM 벤더의 선택사항

 

  • Runtime Constant Pool
    • Method Area에 포함(JVM 동작에서 가장 핵심적인 역할 수행)
    • class 파일의 constant pool 테이블에 해당하는 영역
      • 클래스 내에서 사용되는 상수들을 담고 있는 테이블
      • 각 클래스와 인터페이스 상수, 메소드와 필드에 대한 모든 레퍼런스를 저장하는 테이블
    • JVM은 어떤 메소드나 필드를 참조할 때 Runtime Constant Pool을 통해 해당 메소드나 필드의 실제 메모리 상 주소를 찾아 참조함
    • 스레드 공유 생명주기
      • JVM 시작시 생성
      • 프로그램 종료 시 사라짐
      • 명시적으로 null 선언시 사라짐

 

  • Heap Area
    • Runtime에 생성되는 객체를 저장하기 위한 메모리의 영역
      • 대부분의 메모리 이슈 발생 근원지…
    • new 연산자로 생성된 모든 ObjectInstance 변수, 그리고 배열을 저장
      • 모든 스레드가 Heap Area를 공유함
      • 멀티스레드 프로그래밍을 할 때 동기화에 주의해야 하는 영역
    • 힙 구성 방식이나 GC 방법은 JVM 벤더의 재량
    • 두개의 영역으로 구분
      • Young Generation
        • 생명 주기가 짧은 객체를 GC 대상으로 하는 영역
        • 오래 사용되는 객체를 Old Generation으로 이동 시킴
      • Old Generation
        • 생명 주기가 긴 객체를 GC 대상으로 하는 영역

 

  • Stack Area(JVM Stack)
    • 각 스레드를 위한 분리된 Runtime Stack 영역(각 스레드 마다 존재)
    • 스택 영역(JVM Stack)에 스택 프레임(Stack Frame)을 추가(push)하고, 제거(pop)하는 동작만 수행
      • 메소드가 호출할 때 마다 Stack Frame으로 불리는 EntryStack 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가 생성될 때마다 하나씩 존재
    • 어떤 명령을 실행해야 할지에 대한 기록(현재 수행중인 부분의 주소를 갖고있음)

 

  • Native Method Stack
    • 바이트 코드가 아닌 실제 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행 시키는 영역
    • JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택
      • C/C++ 스택이 생성
    • JNI(Java Native Interface)를 통해 바이트 코드로 전환하여 저장
      • Java가 다른 언어로 만들어진 애플리케이션과 상호 작용할 수 있는 인터페이스를 제공
      • JVMNative 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

먼저, 모든 오브젝트(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에 대해서는 다음시간에 더 상세하게 포스팅 하겠습니다. 🏃‍♀️

 

 

728x90
반응형