본문 바로가기
Programming/Java

[Optimizing Java] 2. JVM 이야기

by 읽고 쓰는 개발자 2023. 6. 11.

성능에 관심있는 개발자라면 기본적으로 JVM 기슬 스택의 구조를 이해해야 한다. 

이 장에서는 뒷부분에 나오는 고급 주제의 기본 지식을 제공하기 위해 JVM이 자바 코드를 실행하는 방법을 소개한다. 

바이트코드에 대하여 9장에서 자세히 다루지만, 먼저 가볍게 읽은 후 9장을 읽을 때 다시 읽어보면 좋음

 

JVM : 자바를 실행하기 위한 가상 기계

JVM구성요소

  1. 자바 인터프리터(interpreter)
  2. 클래스 로더(class loader)
  3. JIT 컴파일러(Just-In Time compiler)
  4. 가비지 컬렉터(garbage collector)

2.1 인터프리팅과 클래스로딩

  • JVM : 스택 기반의 해석머신, 레지스터(물리적 하드웨어) 없지만 일부 결과를 실행 스택에 보관하며 이 스택의 맨 위 쌓인 값(들)을 가져와 계산
  • JVM 인터프리터(해석기)의 기본 로직 : while 루프 안의 switch문
    • 평가 스택을 이용해 중간값들을 담아두고 가장 마지막에 실행된 명령어와 독립적으로 프로그램을 구성하는 옵코드(명령코드, 수행할 명령어를 나타내는 부호)를 순서대로 처리
    • java HelloWorld 명령을 통해 자바 애플리케이션 실행하면 
      • OS(운영체제) -> 가상 머신 프로세스 (자바 바이너리) 구동 
      • 자바 가상 환경 구성 -> 스택 머신 초기화 -> 유저 작성한 실제 HelloWorld 클래스 파일 실행 
      • application entry point(진입점) : helloworld.class의 main() 메서드. 제어권이 해당 클래스로 넘기려면 가상 머신이 실행 시작 전 해당 클래스 로드해야 함(자바 클래스로딩 메커니즘 관여) 
      • 자바 클래스로딩 메커니즘 : 자바 프로세스 초기화 -> 사슬처럼 줄지어 연결된 클래스로더가 차례로 작동.
        1.  부트스트랩 클래스 : 자바 런타임 코어 클래스 로드, 다른 클래스로더가 나머지 시스템에 필요한 클래스를 로드할 수 있게 최소한의 필수 클래스(ex. java.lang.Object, Class, Classloader)만 로드 
        2. 확장 클래스로더 : 부트스트랩 클래스로더를 자기 부모로 설정, 필요할 때 클래스로딩 작업을 부모에게 넘김 ->
          1. 특정한 OS나 플랫폼에 네이티브 코드 제공
          2. 기본 환경을 오버라이드(재정의)  
          3. 많이 사용되지는 않는 클래스로더
        3. 애플리케이션 클래스로더 : 지정된 클래스패스에 위치한 유저 클래스를 로드 
      • 자바는 새 클래스를 디펜던시(의존체)에 로드. 클래스를 찾지 못한 클래스로더는 기본적으로 자신의 부모 클래스로더에게 대신 룩업을 넘김. 부트스트랩로더까지 찾지 못하면 ClassNotFoundException 예외 발생. 방지를 위해서는 빌드 프로세스 수립시 운영 환경과 동일한 클래스패스로 컴파일 하기. 
      • cf)
        • 자바 클래스 로더는 하나의 객체에 불과함. 따라서 클래스 초기 세트를 존재하게 할 방법이 필요한 것 
        • 클래스는 (패키지명 포함) 풀 클래스명과 자신이 로드한 클래스로더, 두 가지 정보로 식별됨. ( 상이한 로더로 두 번 로드될 수 있으니 주의 필요 )

 

2.2 바이트코드 실행

  • 소스 코드 실행 전까지 변환과정
    • 자바 컴파일러(javac)를 이용해 컴파일(전체 빌드 프로세스의 한 부분으로 수행됨)
      • 자바 소스코드 -> 바이트코드(.class)파일로 변환 (변환 과정에서 최적화 거의 없기 때문에 해독 쉬움)
      • javap(역어셈블러) : 자바 SDK에 포함. 클래스파일 내부 확인 가능 
      • 클래스파일 구조 (해부도)
        1. magic_number : JVM이 valid한 compiler로부터 .class 파일이 생성되었는지 검증하는 값
        2. major_version & minor_version : .class 파일의 컴파일 버전을 의미한다. 낮은 버전의 컴파일러에서 생성된 .class 파일은 상위 버전에 JVM에서 구동할 수 있으나 그 반대는 runtime exception을 출력하여 구동이 불가하다.
          • .class 파일을 생성한 javac 컴파일러 버전 <= JVM 버전이어야 구동이 가능
        3. constant_pool_count : .java 파일 컴파일 시 모든 변수와 메소드 참조 주소는 constant pool에 symbolic reference로 저장된다.
        4. constant_pool[] : 변수, 메소드 정보를 담은 symbolic reference 배열
          • symbolic reference : 클래스의 특정 메모리 주소를 참조 관계가 아니라 참조하는 대상의 이름만을 지칭
          • 이름에 맞는 객체의 주소를 찾아서 연결하므로 실제 메모리 주소가 아니라 이름만을 갖게되는 것
        5. access_flag : class file에 선언된 제어자 정보
        6. this_class : class file의 정보
        7. super_class : 현재 클래스의 상위 클래스(extend한 클래스)를 의미 (없으면 Object가 상위)
        8. interface_count : 현재 class file이 implements한 interface 개수
        9. interface[] : implements한 interface들의 정보를 담은 배열
        10. fields_count : 현재 class file에 선언된 static 변수(클래스 변수) 개수
        11. fields[] : 클래스 변수 정보를 담은 배열
        12. method_count : 현재 class file에 선언된 메소드 개수
        13. method[] : 선언된 메소드 정보를 담은 배열
        14. attributes_count : 현재 class file에 선언된 변수(인스턴스 변수) 개수
        15. attributes[] : 인스턴스 변수 정보를 담은 배열
 

2.3 핫스팟 입문 

핫스팟 JVM

  • 자바는 C,C++과 다르게 제로-오버헤드 추상화(기계에 가까워 저수준 엄격히 제어)에 동조하지 않음
  • 핫스팟은 런타임 동작 분석, 성능 유리한 방향으로 최적화 적용하는 가상머신. 핫스팟 등장 이후 제로-오버헤드 언어에 필적할만한 성능 진화 
  • 자바의 생산성 지켜줌  

2.3.1 JIT 컴파일이란?

  •  자바 프로그램 : 바이트코드 인터프리터가 가상화한 스택 머신에서 명령어를 실행하며 시작됨
  • 핫스팟은 이를 위해 프로그램 단위(메서드, 루프)를 인터프리티드 바이트코드 -> 네이티브 코드 컴파일 : JIT(Just In-time) 컴파일 기술 
  • 인터프리티드 모드에서 애플리케이션 모니터링 하며 자주 실행되는 코드 파트 미리 JIT 컴파일 수행/최적화
  • 컴파일러가 해석단계에서 수집한 추적 정보 근거로 최적화. 핫스팟 신규 버전에서 실행시 어플리케이션 최신 최적화 가능 
  • JIT 컴파일러 실행 후에는 유저 작성 코드와 전혀 다를 수 있음 -> 애플리케이션 동작 방식을 상식적인 추론으로 넘겨짚지않도록 조심하기
  • 자바(프로필 기반 최적화) 환경에서 동적 인라이닝, 가상 호출(virtual call) 등으로 성능 개선 가능
  • 자바의 특징 : 핫스팟 컴파일 서브시스템 + JMM 기능 

2.4 JVM 메모리 관리

  • 자바는 메모리 할당/해제 유저가 직접하지 않으며, 가비지 수집 프로세스를 이용해 힙 메모리 자동 관리 방식으로 해결
  • 가비지 수집 : JVM이 더 많은 메모리를 할당해야 할 때 불필요한 메모리를 회수하거나 재사용하는 불확정적 프로세스 
  • 가비지 수집 성능 최적화 주제 : 6,7,8장 

2.5 스레딩과 자바 메모리 모델(JMM)

  • 자바의 멀티스레드 방식의 세가지 설계 원칙
    • 자바 프로세스의 모든 스레드는 가비지가 수집되는 하나의 공용 힙 가짐
    • 한 스레드가 생성한 객체는 그 객체를 참조하는 다른 스레드가 액세스 가능
    • 기본적으로 객체는 변경 가능(Mutable), Final 키워드 의도적으로 붙이지 않으면 바뀔 수 있음
  • JMM : 서로 다른 스레드가 객체 안에 변경되는 값을 어떻게 바라보는지를 기술한 공식 메모리 모델 
    • 스레드 A,B가 같은 객체 참조할 때 A가 객체의 값을 바꾸면 무슨일이 일어날까? 
      -> OS 스케줄러가 스레드 강제 방출할 경우, B 스레드가 무효 상태의 객체 바라보게 될수도 
    • 상호베타적 락은 코드 동시 실행시 객체 손상을 막는 유일한 방어 장치이지만 실제로 사용시 상당히 복잡
    • 12장에 JMM과 스레드/락 다루는 방법 담김 

 


2.6 JVM 구현체 종류

  • 핫스팟(오라클 제작) 이외의 자바 구현체 다수 존재 
  • OpenJDK
    • 자바 기준 구현체(소프트웨어/하드웨어 구현할 때 참조 가능한 샘플 프로그램) 제공하는 GPL 프로젝트 
    • 오라클이 직접 주관/지원하고 자바 릴리즈 기준 발표
  • 오라클 자바(Oracle)
    • OpenJDK 기반. 보안 패치 제외 OpenJDK 공개 저장소 커밋됨
  • 줄루(Zulu)
    • 아줄 시스템 제작한 OpneJDK 구현체 
    • 재배포 가능 
    • OpenJDK 유료 지원 서비스 제공
  • 아이스티(IcedTea)
    • 레드햇 제품.
    • 재배포 가능 
  • 징(Zing)
    • 아줄 시스템 제작 
    • 고성능 상용 JVM 
    • 대용량 힙 메모리와 멀티 CPU 서버급 시스템을 위해 설계된 프로덕트 
  • J9
    • IBM제작
  • 애비안(Avian)
    • 자바 인증 받은 완벽 구현체 아니지만, JVM 세부 작동 원리 학습에 도움 
  • 안드로이드(Android)
    • 여러가지 자바 클래스 라이브러리 구현체와 교차 컴파일러 사용 

2.6.1 JVM 라이센스

  • JVM 구현체는 거의 다 오픈소스이며, 대부분 핫스팟(GPL 라이선스)에서 비롯한 제품
  • 오라클 자바(자바 9 이후) 라이선스 체계 
    • 오라클 자바 :  OpenJDK 코드 베이스 + 상용 제품
    • 오라클 라이선스 사용 시 조심해야 함 ( 회사 밖으로 오라클 바이너리 재배포 불가, 동의 없이 오라클 바이너리 패치 불가 )

 

2.7 JVM 모니터링과 툴링 

  • JVM은 성숙한 실행 플랫폼,실행 중인 애플리케이션에 인스트루먼테이션,모니터링, 관측하는 다양한 기술 제공 
    • instrumentation : 오류 진단이나 성능개선을 위해 애플리케이션에 특정 코드 삽입하는 것 
    • 인스트루먼테이션,모니터링,관측 툴에 쓰이는 중요한 기술 
      • 자바 관리 확장(JMX) : JVM과 그 위에서 동작하는 애플리케이션 제어,모니터링하는 강력한 범용 툴 
        • 클라이언트 애플리케이션처럼 메서드 호출, 매개변수 변경 가능 
      • 자바 에이전트 : java.lang.instrument 인터페이스로 메서드 바이트코드 조작 
      • JVM 툴 인터페이스
      • 서버서빌 에이전트

2.7.1 VisualVM 

  • javac, java 등 잘 알려진 바이너리 이외에도 JDK에 유용한 툴이 많음
  • VisualVM도 그 중 하나 : 넷빈즈 플랫폼 기반의 시각화 툴
  • 플러그인 아키텍처를 가져 다른 툴을 쉽게 추가할 수 있음 ex)VisualGC라는 유용한 가비지 수집 플러그인

2.8 마치며

  • 이번 장에서는 JVM 내부 모델 전체를 개략적으로 훑어보았으며, 자세한 내용은 뒷장에서 자세히 다룰 예정
  • 다음 장에서는 성능 분석을 위한 배경지식으로 운영체제와 하드웨어의 동작 세부에 대해 공부하며 타이밍 서브시스템에 대해서도 자세히 살펴본다.

 

출처 : 자바 최적화