Optimizing Java (자바최적화): JVM, 운영체제, 하드웨어
JVM이란?
스택 기반의 해석 머신이다. 물리적 CPU 하드웨어 레지스터는 없지만,일부 결과를 스택에 보관하며, 스택의 위에 쌓인 값을 가져와 계산한다.
JVM 인터프리터의 기본 로직
평가 스택을 이용해 중간값을 담아두고, 가장 마지막에 실행된 명령어와 독립적으로 프로그램을 구성하는 명령 코드를 순서대로 처리한다. while 루프 안의 switch 문 같은 방식이다.
실행 순서
- java helloWorld
- OS가 가상 머신 프로세스 (자바 바이너리)를 구동한다.
- 자바 가상환경이 구성되고, 스택 머신이 초기화 된다.
- 자바 프로세스가 새로 초기화되면, 클래스로더가 차례로 동작한다.
- 부트스트랩 클래스로더 실행
- 자바 런타임 코어 클래스 로드
- 다른 클래스 로더가 시스템에 필요한 클래스를 로드할 수 있는 최소한의 클래스를 로드한다.
- 예시) Object, Class
- 자바 런타임 코어 클래스 로드
- 확장 클래스 로더 생성
- 부트스트랩 클래스로더를 부모로 설정하고, 필요할 때 클래스 로딩 작업을 부모에게 넘긴다.
- 부트스트랩 클래스로더 실행
- 자바는 처음보는 클래스를 dependency에 로드한다. 클래스를 찾지 못한 클래스는 부모 클래스로더에게 대신 룩업을 넘긴다.
- 만약 못찾는 경우, ClassNotFoundException 오류가 발생한다.
- 자바 프로세스가 새로 초기화되면, 클래스로더가 차례로 동작한다.
- 이후, 실제로 작성한 클래스 파일이 실행된다.
바이트코드 실행
- javac의 일
- 자바 파일을 바이트코드 형태인 .class 파일로 바꾸는 작업
- 바이트코드는 중간표현형 (IR: Intermidiate Representation) 이다.
- 컴퓨터 아키텍처에 의존하지 않아 이식성이 좋다.
- JVM 지원 OS 어디에서든 실행할 수 있다.
클래스파일 구성정보
- 매직넘버: 0xCAFEBABE
- 클래스 파일임을 나타내는 용도
- 클래스 파일 포맷 버전
- 컴파일한 JVM 버전과 실행하는 JVM 버전이 호환되지 않는 문제를 방지하는 용도
- 상수 풀
- 액세스 플래그
- 클래스 수정자를 반영
- 추상 클래스, 정적 클래스 등 클래스 종류 표시
- this 클래스
- 현재 클래스명
- 슈퍼 클래스
- 인터페이스
- 필드
- 메서드
- 속성
제로-오버헤드 원칙
- "사용하지 않는 것에 비용을 지불하지 말라"는 것을 말한다.
- 기계에 가까운 언어로 개발한다. 저수준으로 기계를 제어한다.
- 그러나 개발자의 생산성은 낮다.
- 컴퓨터와 OS가 어떻게 동작하는지 개발자가 세세하게 알려줘야한다.
- 이 경우, 소스 코드를 빌드하면 특정한 기계어로 컴파일된다.
자바는 제로-오버헤드 원칙에 동조하지 않는다.
AOT (Ahead of time) 컴파일
- 실행 전에 바이트코드를 기계어로 바꾸는 컴파일러를 말한다.
- 제로 오버헤드 원칙에 따른 컴파일 방식을 AOT 컴파일로 볼 수 있다.
핫스팟 JVM과 JIT (just in time) 컴파일
- 자바프로그램 실행 방법: 바이트코드 인터프리터가 가상 스택 머신에서 명령을 실행한다.
- 필요한 배경
- 자바는 CPU를 추상화하여, 다른 플랫폼에서도 클래스 파일을 실행할 수 있다.
- 그러나 프로그램이 성능을 최대한 내려면
네이티브 기능
을 활용해 CPU에서 직접 프로그램을 실행해야 한다.
핫스팟
은 프로그램 단위 (메서드와 루프)를 인터프리티드 바이트코드에서 네이티브 코드로 컴파일한다.핫스팟
은 자주 실행되는 코드 파트를 발견해, JIT 컴파일을 실행한다.- 이렇게 분석을 하는 동안 미리 프로그래밍한 추적 정보가 취합된다.
- 특정 메서드가 어느 임계치를 넘어가면, 프로파일러가 특정 코드 섹션을 컴파일/최적화한다.
JIT 장점
해석단계에서 수집한 추적 정보를 근거로 최적화를 결정한다.
자바처럼 프로파일 기반 최적화 (profile-guided optimization, PGO)를 응용하는 환경에서는 AOT 플랫폼에서는 불가능한 방식으로 런타임 정보를 활용할 여지가 있다.
그래서 dynamic inlining(동적 인라이닝) 혹은 virtual call (가상 호출) 등으로 성능을 개선할 수 있다.
핫스팟 VM은 CPU 타입을 정확히 감지해 특정 프로세서의 기능에 맞게 최적화를 적용할 수 있다.
Garbage Collection
힙 메모리를 자동으로 관리해주는 프로세스다. 불필요한 메모리를 회수하거나 재사용한다.
3장. 하드웨어와 운영체제
무어의 법칙
대량 생산한 칩상의 트랜지스터 수는 약 18개월마다 2배씩 증가한다.
메모리
무어의 법칙에 따라 개가 증가한 트랜지스터 수는 초기에는 Clock Speed (클록 속도)를 높이는 데 쓰였다. 클록 속도가 증가하면 초당 더 많은 명령어를 처리할 수 있다. 프로세서 속도도 빨라졌다.
그러나 프로세스 코어의 수요를 메인 메모리가 처리하지 못하는 현상이 발생했다. 칩이 빨라질 수록 메모리도 빨리 움직여야하는데 그렇지 못했다.
메모리캐시 (CPU 캐시)
CPU에 있는 메모리이다. 레지스터보다는 느리지만 메인 메모리보다는 빠르다.
자주 접근하는 메모리는 CPU가 메인 메모리를 재참조하지 않게 사본을 떠서 CPU 캐시에 보관한다.
요즘 CPU는 액세스 빈도가 높은 캐시일수록 프로세서 코어와 더 가까이 위치시킨다.
CPU와 가장 가까운 캐시는 L1 (레벨 1캐시), 그 다음은 L2 식으로 부른다.
이러한 캐시 아키텍처를 통해 액세스 시간을 줄이고, 코어가 처리할 데이터를 계속 채워넣는다. 클록 속도와 액세스 시간 차이 때문에 최신 CPU는 캐시에 더 많은 예산을 투자한다.
노스브리지란? CPU, RAM, 바이오스 롬, PCI 익스프레스 그래픽 카드 사이의 통신을 담당한다.
이러한 캐시 아키텍처 덕분에 프로세스 처리율은 개선되었다. 그러나 새로운 고민이 발생하게 되었다. 메모리에 있는 데이터를 어떻게 캐시로 가져오고, 캐시한 데이터를 어떻게 메모리에 다시 써야 할지 결정해야했다.
이 문제는 보통 캐시 일관성 프로토콜
(cache consistency protocol)이라는 방법으로 해결한다.
자바 성능을 논할 때는 객체 할당률에 대한 애플리케이션 민감도가 아주 중요하다.
데이터를 메인 메모리에서 캐시로 퍼나르는 과정에서 메모리 버스를 예열시키는 구간에서 지배적인 영향을 미치기도 한다.
페이지 테이블
가상 메모리 주소를 물리 메모리 주소로 매핑한다.
변환 색인 버퍼 (Translation Lookaside Buffer, TLB)
페이지 테이블의 캐시 역할을 한다. 가상 주소를 참조해 물리 주소에 접근하는 작업 속도가 빨라진다.
분기 예측 (Branch Prediction)
프로세서가 조건 분기하는 기준값을 평가하느라 대기하는 현상을 방지한다.
다단계 명령 파이프라인을 이용해 CPU 1사이클도 여러 개별 단계로 나누어 실행해 여려 명령이 동시 실행중인 경우가 있다.
조건문을 다 평가하기 전까지, 다음 명령을 알 수 없는 문제가 있는 경우가 있다. 그 결과, 분기문 뒤에 오는 다단계 파이프라인을 비우는 동안 프로세서는 여러 사이클 (최대 20회) 동안 멎게 된다.
이런 일이 없도록 하기 위해 프로세서는 트랜지스터를 활용해, 가장 발생 가능성이 큰 브랜치를 미리 결정하는 휴리스틱을 만든다. 미리 추측 결과로 파이프라인을 채운다. 운이 맞으면 CPU는 다음 작업을 실행하고, 아니면 명령을 모두 폐기하고 파이프라인을 비운다.
하드웨어 메모리 모델
JMM은 프로세서 타입별로 다른 메모리 액세스 일관성을 고려하여, 명시적으로 약한 모델 (weak model)으로 설계되었다.
기계 공감(mechanical sympathy)
더 나은 성능을 얻기 위해 하드웨어 작동 원리를 깊게 이해하려는 것
저지연, 고성능을 필요로하는 분야에 많이 적용되었다.
운영체제
OS의 주 임무는 여러 실행 프로세스가 공유하는 리소스 액세스를 관장하는 일이다.
제한된 리소스를 여러 프로세스에 골고루 나누어줄 수 있는 중앙시스템이 필요하다.
이 리소스 중 메모리와 CPU 시간은 가장 중요하다.
메모리 액세스 제어
한 프로세스가 소유한 메모리 영역을 다른 프로세스가 함부로 훼손할 수 없게 해야한다.
메모리 액세스 제어에 필요한 핵심 기술
- 가상 주소 방식 (virtual addressing)
- 메모리 관리 유닛 (MMU, Memory Management Unit)을 활용한 방식
- 페이지 테이블
TLB는 물리 메모리 주소 룩업 시간을 줄이는 하드웨어 기능이다. 버퍼를 사용하면 소프트웨어가 메모리에 접근하는 성능이 향상된다. 그러나 MMU는 개발자가 세부를 파악하기에는 너무 저수준 영역이다.
OS 액세스 스케줄러
CPU 액세스를 제어하는 OS 커널 요소이다.