Skip to content

Commit

Permalink
chapter 1-3 (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
meengi07 authored Jan 10, 2024
1 parent dcf9d42 commit bd4dd49
Show file tree
Hide file tree
Showing 15 changed files with 456 additions and 0 deletions.
87 changes: 87 additions & 0 deletions ch01/김민기.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Chapter 1. 성능과 최적화

### 1. 자바 성능 : 잘못된 방법

자바 초창기에는 메서드 디스패치 (dispatch : 메서드 호출에 대한 처리 방식) 성능은 최악이었지만, 최근에는 가상 디스패치 ( 동적 바인딩 ) 성능이 좋아졌고, JVM에서 자동 인라이닝(automatic managed inlining : 메서드 호출에서 직접 코드를 호출해 오버헤드를 줄이는 방법)을 통해 가상 디스패치조차 대부분의 호출부에서 사라지게 됨

우수한 성능 목표를 달성하기 위한 방법들

- 전체 소프트웨어 수명주기의 성능 방법론
- 성능과 연관된 테스트 이론
- 측정, 통계 도구 선정
- (시스템+데이터) 분석 스킬
- 하부 기술과 메커니즘

중요한 원칙

- JVM을 더 빨리 작동시키는 ‘마법의 스위치’ 는 없다.
- 자바를 더 빨리 실행하게 하는 ‘팁, 트릭’은 없다.
- 못 보구 꼭꼭 숨겨둔 ‘비밀 알고리즘’ 같은 것도 없다.

### 2. 자바 성능 개요

자바는 실용적인 언어로 개발 환경이 ‘충분히 빠르다면’ 생산성을 대가로 성능을 희생하는 입장이었으나 최근 핫스팟같은 정교한 JVM의 성숙으로 고성능 컴퓨팅 애플리케이션에도 적합한 수준에 이르렀다.

실용성을 추구하는 자바 플랫폼의 성격 중 서브시스템(managed subsystem)이 대표적인데, 개발자가 일일히 용량을 관리하지 않고 시스템이 관리하도록 하는 것으로 단적으로 메모리 관리가 있다. JVM이 탈착형 GC 서브시스템 형태로 메모리를 자동 관리하는 덕분에 수동으로 메모리를 관리하며 개발할 필요가 없다.

성능 측정은 정규 분포를 따르지 않는 경우가 많아서 기초 통계 기법만 갖고는 결과를 제대로 처리하기 힘들다. 측정 행위 자체도 오버헤드가 일어나며 자주 샘플링하거나 기록하는 것도 성능 결과 수치에 영향을 끼친다.

### 3. 성능은 실험과학이다

JVM 성능 튜닝은 기술, 방법론, 정량적 측정값, 툴을 망라한 개념으로 시스템 소유자/유저가 추구하는 측정 결과를 얻는 것으로 일종의 실험과학이라고 할 수 있다.

- 원하는 결과를 정의한다
- 기존 시스템을 측정한다
- 요건을 충족시키려면 무슨 일을 해야 할지 정한다.
- 개선 활동을 추진한다
- 다시 테스트한다
- 목표가 달성됐는지 판단한다

바람직한 성능 결과를 정의하고 판단하는 과정에서 정량적인 일련의 목표가 수립되며, 무엇을 측정할지 대상을 확정하고 목표를 기록하는 행위가 중요하며 이런 행위들이 프로젝트 아티팩트(결과물)와 제품 일부를 형성한다. 성능 분석은 비기능 요건을 정의하고 달성하는 활동이다.

### 4. 성능 분류

먼저 성능 지표는 통해 성능 분석의 어휘집, 튜닝 프로젝트의 목표를 정량적인 단위로 표현한 기준이다.

- 처리율

서브시스템이 수행 가능한 작업 비율을 나타난 지표, 일정 시간동안 완료한 작업 단위 수로 표시(예: 초당 처리 가능한 트랜잭션 수) 처리율이 실제 성능을 반영하는 의미있는 지표가 되려면 하드웨어 스펙, OS, 소프트웨어 스펙, 서버환경, 트랜잭션이 동일한지 등, 처리율을 테스트할때 실행 간 워크로드가 일정해야함

- 지연

하나의 트랜잭션을 처리하고 그 결과를 응답할때까지 걸리는 시간, 종단 시간이라고도 하며, 대개 그래프에서 워크로드에 비례하는 함수로 표시

- 용량

시스템이 보유한 작업 병렬성의 총량, 즉 시스템이 동시 처리 가능한 작업 단위 개수를 말하며 처리율과 밀접한 연관이 있고 시스템 동시 부하가 증가할수록 처리율도 영향을 받음

- 사용률

가장 흔한 태스크는 시스템 리소스를 효율적으로 활용하는 것, 사용률은 워크로드에 따라 리소스별로 다를 수 있다.

- 효율

처리율을 리소스 사용률로 나눈 값으로 측정하며 같은 처리율을 적은 리소스로 달성한다면 효율이 좋은것이다.

- 확장성

처리율이나 시스템 용량은 처리하는 데 쓸 수 있는 리소스에 달려있으며, 리소스 추가에 따른 처리율 변화는 시스템/애플리케이션의 확장성을 가늠하는 척도로 시스템 확장성은 궁극적으로 정확히 리소스를 투입한 만큼 처리율이 변경되는 형태를 지향한다.

- 저하

시스템이 더 많은 부하를 받으면 지연, 처리율 측정값에 변화가 생긴다. 이 변화는 사용률에 따라 달라지는데 시스템을 덜 사용한다면 측정값이 느슨하게 변하지만 시스템이 풀 가동중이면 처리율이 더 늘어나지 않는, 지연이 증가하는 양상을 띄며 이런 현상을 저하(degradation)라고 한다.

- 측정값 사이의 연관 관계

다양한 성능 측정값은 어떤 식으로든 서로 연결돼 있다. 시스템의 부하가 증가하면 사용률도 증가하지만 시스템이 많이 사용되지 않을때는 적은 사용률을 보인다. JIT컴파일러가 좋은 예로, JIT 컴파일 대상이 되는 메서드는 ‘충분히 빈번하게’ 인터프리티드 모드로 실행돼야 하며, 부하에 따라 메서드 호출 빈도가 변하면 컴파일 대상이 변경될 수 있다.


### 5. 성능 그래프 읽기

성능 테스트에서 자주 등장하는 패턴들이 있다.

- 부하가 증가하면서 예기치 않게 저하(지연)가 발생한 그래프를 성능 엘보(performance elbow) 라고 한다.
- 반대로 클러스터에 장비를 추가함에 따라 거의 선형적으로 처리율이 확장되는 운이 아주 좋은 케이스로 이렇게 환경이 극단적으로 순조로울 때 가능하다.
- 암달의 법칙으로 태스크를 처리할 때 프로세서 개수를 늘려도 실행 속도를 최대 어느 정도까지 높일 수 있는지를 나타낸 그래프다.
- JVM의 GC 서브시스템의 메모리 사용 패턴은 보통 ‘톱니’ 모양을 나타낸다.
- 시스템 리소스가 누수될 때 흔히 나타나는 징후의 그래프
152 changes: 152 additions & 0 deletions ch02/김민기.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Chapter2. JVM 이야기



# 1. 인터프리팅과 클래스로딩

자바 가상 머신을 규정한 명세서(VM 스펙)에 따르면 JVM은 스택 기반의 해석 머신이다.레지스터(물리적 CPU)는 없지만 일부 결과를 스택에 보관하며 이 스택의 맨 위에 쌓인 값을 가져와 계산한다.

JVM 인터프리터(해석기)의 기본 로직은 평가 스택을 이용해 중간값들을 담아두고 가장 마지막에 실행된 명령어와 독립적으로 프로그램을 구성하는 옵코드(opcode)를 하나씩 순서대로 처리하는 ‘`while 루프 안의 switch문`’이다.

1. `java HelloWorld` 명령을 내려 자바 애플리케이션을 실행하면 OS는 가상 머신 프로세스(자바 바이너리)를 구동합니다. 자바 가상 환경이 구성되고, 스택 머신이 초기화된 다음 실제 클래스 파일이 실행된다.
2. 애플리케이션의 진입점(entry point)은 main() 메서드다. 제어권을 이 클래스로 넘기려면 가상 머신이 실행이 시작되기 전에 이 클래스를 로드해야 한다.
3. 이때 자바 클래스로딩(class loading) 메커니즘이 관여하여 자바 프로세스가 새로 초기화되면 사슬처럼 연결된 클래스로더가 차례차례 작동한다. 제일 먼저 부트스트랩 클래스가 실행되며 자바 런타임 코어 클래스를 로드한다.
4. 부트스트랩 클래스로더는 다른 클래스로더가 나머지 시스템에 필요한 클래스를 로드할 수 있게 최소한의 필수 클래스(java.lang.Object, Class, Classloader)만 로드한다.
5. 그다음 확장 클래스로더가 생긴다. 부트스트랩 클래스로더를 자기 부모로 설정하고 필요할때 클래스로딩 작업을 부모에게 넘긴다. (확장 클래스로더를 이용하면 OS나 플랫폼에 네이티브 코드를 제공하고 기본 환경을 오버라이드할 수 있다. js 런타임 내시혼(hashorn)을 바로 확장 클래스로더가 로드한다.)
6. 끝으로 애플리케이션 클래스로더가 생성되고 지정된 클래스패스에 위치한 유저 클래스를 로드합니다.

![스크린샷 2024-01-06 오후 5.07.52.png](./images/스크린샷 2024-01-06 오후 5.07.52.png)


자바는 프로그램 실행 중 처음 보는 새 클래스를 디펜던시(dependency)에 로드한다. 클래스를 찾지 못한 클래스로더는 기본적으로 자신의 부모 클래스로더에게 대신 룩업을 넘긴다.

# 2. 바이트코드 실행

자바 소스 코드는 실행되기까지 꽤 많은 변환 과정을 거친다.

1. javac를 이용해 컴파일하며 보통 전체 빌드 프로세스의 한 부분으로 수행한다.

![스크린샷 2024-01-06 오후 5.14.02.png](./images/스크린샷 2024-01-06 오후 5.14.02.png)

바이트코드는 특정 컴퓨터 아키텍처에 특정하지 않은 중간 표현형(Imemciae Repcsenation) 이다.

| 컴포넌트 | 설명 |
| --- | --- |
| 매직 넘버 magic number | 0xCAFEBABE |
| 클래스 파일 포맷 버전 | 클래스 파일의 메이저/마이너 버전 |
| 상수 풀 constant pool | 클래스 상수들이 모여 있는 위치 |
| 액세스 플래그 access flag | 추상 클래스, 정적 클래스 등 클래스 종류 표시 |
| this 클래스 | 현재 클래스명 |
| 슈퍼클래스 | 슈퍼(부모)클래스명 |
| 인터페이스 | 클래스가 구현한 모든 인터페이스 |
| 필드 | 클래스에 들어 있는 모든 필드 |
| 메서드 | 클래스에 들어 있는 모든 메서드 |
| 속성 | 클래스가 지닌 모든 속성 |

```java
public class HelloWorld {
public static void main(String[] args) {
for(int i=0; i<10; i++) {
System.out.println("Hello World");
}
}
}

// Class file //
public class HelloWorld {
public HelloWorld();
Code:
0: aload_0 // this 레퍼런스를 스택 상단에 올려놓는다
1: invokespecial #1 // 슈퍼생성자들을 호출하고 객체를 생성하는등
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0 // 정수형 상수 0을 평가 스택에 push
1: istore_1 // 상숫값을 오프셋1에 위치한 지역변수(루프i)에 저장
2: iload_1 // 오프셋 1의 변수를 스택으로 다시 로드
3: bipush // 상수 10을 push
5: if_icmpge // 오프셋과 상수 10을 비교한다. if(i < 10)
8: getstatic // System.out의 정적 메서드를 해석
11: ldc // println의 문자열을 로드
13: invokevirtual // 이 클래스에 속한 인스턴스 메서드를 실행
16: iinc // 오프셋 정수 증가
19: goto // 2번 명령으로 되돌아감
22: return
```


# 3. 핫스팟 입문

![스크린샷 2024-01-06 오후 6.09.40.png](./images/스크린샷 2024-01-06 오후 6.09.40.png)

자바의 핫스팟 가상 머신의 등장으로 C/C++ 같은 언어와 비교할 정도의 성능이 개선됐다.

C++언어는 제로-오버헤드 원칙을 준수하지만 이는 곧 컴퓨터와 OS에 대해 실제 동작에 대해 세세한 수준까지 명령해야 합니다. 또한 이런 코드를 빌드할 땐 플랫폼에 특정한 기계어로 컴파일해야 한다. (**AOT** Ahead-of-Time 사전 컴파일)

자바는 이런 제로-오버헤드 추상화 철학을 따르기 보다 핫스팟은 프로그램의 런타임 동작을 분석하고 성능에 가장 유리한 방향으로 영리한 최적화를 적용하는 가상 머신이다.

## 3.1 JIT컴파일이란?

자바 프로그램은 바이트코드 인터프리터가 가상화한 스택 머신에서 명령어를 실행하며 CPU를 추상화한 구조라서 다른 플랫폼에서도 클래스 파일을 문제없이 실행할 수 있지만, 프로그램이 성능을 최대로 내려면 네이티브 기능을 활용해서 CPU에 직접 프로그램을 실행해야 한다. 이를 위해 핫스팟은 프로그램 단위(메서드 & 루프)를 인터프리티드 바이트코드에서 네이티브 코드로 컴파일 한다. 이것이 바로 **JIT(Just-In-Time) 컴파일**이라고 한다.

핫스팟은 인터프리티드 모드로 실행하는 동안 애플리케이션을 모니터링하면서 가장 자주 실행되는 코드 파트를 찾아 JIT컴파일을 수행한다. 이렇게 분석을 하는 동안 미리 프로그래밍한 추적 정보가 취합되면서 더 정교하게 최적화를 할 수 있다. 특정 메서드가 한계치를 넘어가면 프로파일러가 특정 코드 섹션을 컴파일/최적화 한다. JIT 방식으로 컴파일하면 이점이 많은데, 컴파일러가 해석 단계에서 수집한 추적 정보를 근거로 최적화를 결정한다는 게 가장 큰 장점이다.

AOT 컴파일러는 여러 기종의 프로세서에서 실행 가능한 코드를 만들지만, 프로세서에 특정한 기능은 방법이 없다. 자바처럼 프로필 기반 최적화(PGO 프로그램의 실행 프로파일(실제 실행 시점에서의 동작 패턴)을 활용하여 코드 최적화를 수행하는 기술)를 응용하는 환경에서는 대부분의 AOT 플랫폼에서 불가능한 방식으로 런타임 정보를 활용할 여지가 있으므로 동적 인라이닝(dynamic inlining 동적으로 함수 호출을 인라인하는 기술) 또는 가상 호출(virual call)등으로 성능을 개선할 수 있다. 또한 핫스팟 VM은 시동 시 CPU타입을 감지해 특정 프로세스의 기능에 맞게 최적화를 적용할 수 있다.

### 단어 정리

<aside>
💡 **AOT(Ahead of Time) 컴파일** :
프로그램을 실행하기 전에 미리 소스 코드를 기계어로 컴파일 해 둔다.

**JIT(Just In Time)컴파일** :
인터프리터를 통해 바이트 코드를 실행하면서 프로그램을 분석하고 런타임 중 자바 바이트 코드를 네이티브 코드로 컴파일하며, 수집한 정보를 바탕으로 최적화를 통해 성능을 향상시킨다.

**PGO(Profile Guided Optimization) 프로필 기반 최적화** :
컴파일러가 프로그램의 실행 프로파일(실행 시 동작하는 패턴)을 기반으로 코드를 최적화 하는 기술
(예→ JIT컴파일러)

**Dynamic Inlining 동적 인라이닝** :
인라이닝은 함수 호출 지점에 해당 함수의 코드를 직접 삽입함으로써 함수 호출의 오버헤드를 줄이는 최적화 기법, 런타임 중에 최적화를 수행하는 것으로 실제 실행 중에 함수 호출이 반복되고, 그 함수가 인라인 가능한 조건을 충족할 때 동작한다.

</aside>

# 4. JVM 메모리 관리

JVM에서 메모리 관리는 GC(Garbage Collection)이라는 프로세스를 이용해 힙 메모리를 자동 관리하는 방식으로 해결한다. JVM이 더 많은 메모리를 할당해야 할 때 불필요한 메모리를 회수하거나 재사용하는 불확정적 프로세스다. 다양한 GC 알고리즘이 개발됐지만 GC가 실행되면 모든 애플리케이션 스레드가 일시정지 되는 STW(Stop the World)가 발생한다.

# 5. 스레딩과 자바 메모리 모델(JMM)

자바는 멀디스레드 프로그래밍을 지원한다. 주류 JVM 구현체에서 자바 애플리케이션 스레드는 각각 정확히 하나의 전용 OS 스레드에 대응한다. 멀티스레드 방식은 세 가지 기본 설계 원칙을에 기반한다.

- 자바 프로세스의 모든 스레드는 가비지가 수집되는 하나의 공용 힙을 가진다.
- 한 스레드가 생선한 객체는 그 객체를 참조하는 다른 스레드가 액세스할 수 있다.
- 기본적으로 객체는 변경 가능(mutable)하다 즉, 객체 필드에 할당된 값은 프로그래머가 애써 final 키워드로 불변 immutable 표시하지 않는 한 바뀔 수 있다.

# 6. JVM 구현체 종류

- OpenJDK : 자바 기준 구현체를 제공하는 특별한 오픈 소스 프로젝트로 오라클이 직접 프로젝트를 주관/지원하며 자바 릴리즈 기준을 발표한다.
- 오라클(Oracle) : 가장 널리 알려진 구현체로 OpenJDK 기반이지만 오라클 상용 라이센스다.
- 줄루(Zulu) : 아줄 시스템이 제작한 자바 풀 인증을 받은 무료 OpenJDK 구현체다. 상용 라이센스 문제의 제약없이 OpenJDK 유료 지원 서비스를 제공한다.
- 아이스티(IcedTea) : 레드햇의 제품으로 풀 인증을 받았고 재배포가 가능하다.
- 징(Zing) : 아줄 시스템이 제작한 고성능 상용 JVM이다. 64비트 리눅스에서만 작동하며 대용량 힙 메모리와 멀티 CPU 서버급 시스템을 위해 설계됐다.
- J9 : IBM이 만든 상용 JVM으로 출발했다가 오픈소스로 변경됐다. 이클립스 OMR 프로젝트 기반으로 제작되며 IBM 상용 제품의 근간을 이루고 있다.
- 애비안(Avian) : 100% 자바 인증을 받은 구현체가 아니라 제품으로 쓰기에 완전한 솔루션은 아니지만 JVM 세부 작동 원리가 궁금한 개발자에게 훌룡한 학습 도구 역할을 하는 흥미로운 오픈 소스 프로젝트다.
- 안드로이드 (Android) : 구글 안드로이드가 ‘자바에 기반한’ 프로젝트가 아닌 변형된 JVM을 사용한다.

## JVM 라이센스

JVM 구현체는 거의 다 오픈소스이고 IBM J9와 상용 제품인 아줄 징을 제외하면 대부분 핫스팟(GPL 라이센스)에서 비롯된 제품이다. 오라클 JDK와 OpenJDK는 라이센스 외에 아무 차이가 없어졌다.

# 7. JVM 모니터링과 툴링

JVM은 성숙한 실행 플랫폼으로 다양한 인스트루먼테이션 (instrumentation), 모니터링, 관측 기술을 제공한다.

- 자바 관리 확장 (JMX, Java Management Extensions) : JVM과 그 위에서 동작하는 애플리케이션을 제어하고 모니터링하는 범용 툴
- 자바 에이전트 (Java agent) : 자바 언어로 작성된 툴 컴포넌트로 instrument 인터페이스로 메서드 바이트 코드를 조작한다.
- JVM 툴 인터페이스 (JVMTI, JVM Tool Interface) : JVM의 네이티브 인터페이스다.
- 서비서빌리티 에이전트 (SA, Serviceability Agent) : 자바 객체, 핫스팟 자료 구조 모두 표출 가능한 API와 툴을 모아놓았다. SA를 이용하면 대상 JVM에서 코드를 실행할 필요가 없다.

## VisualVM

NetBeans 플랫폼 기반의 시각화 툴인 VisualVM은 UI를 통해 직관적으로 실시간 모니터링을 할 수 있다.
Loading

0 comments on commit bd4dd49

Please sign in to comment.