diff --git "a/ch06/\353\260\225\354\213\234\354\260\275.md" "b/ch06/\353\260\225\354\213\234\354\260\275.md" new file mode 100644 index 0000000..ab67425 --- /dev/null +++ "b/ch06/\353\260\225\354\213\234\354\260\275.md" @@ -0,0 +1,126 @@ +# 6. 가비지 수집 기초 +- 시스템에 있는 모든 객체의 수명을 몰라도 런타임이 객체를 추적해서 쓸모없는 객체를 제거한다. +- GC 구현 원칙 + - 모든 가비지 수집 + - 살아있는 객체 제거 X (SEG FAULT) +## 6.1 마크 앤 스윕 +- 마크 앤 스윕 + - 할당 리스트를 순회 하면서 마크 비트 지운다. + - GC 루트부터 살아 있는 객체 식별 + - 객체마다 마크 비트 셋업 + - 마크 비트 없는 객체는 + - 힙에서 메모리 회수 + - 할당 리스트에서 객체 삭제 + - 라이브 객체 그래프 (dfs 로 객체들 식별) + - `jmap -histo` 하면 객체별 heap 상태를 볼 수 있습니다. +### 6.1.1 가비지 수집 용어 +- STW (STOP THE WORLD) +- 동시 +- 병렬 +- 정확 +- 보수 +- 이동 +- 압착(COMPACT) +- 방출 + +## 6.2 핫스팟 런타임 개요 +- JAVA는 기본형, 레퍼런스로 CALL BY VALUE 한다.(힙에 있는 객체의 주소) + +### 6.2.1 객체를 런타임에 표현하는 방법 +- OOP라는 구조체를 사용해서 객체를 표현합니다. + - Mark 워드 + - Klass 워드 + - LENGTH + - 32비트 여백 +- OOP 상속 구조 + - instanceOop + - methodOop + - arrayOop + - symbolOop + - klassOop + - markOop +### 6.2.2 GC 루트 및 아레나 +- GC 루트는 메모리의 고정점으로 메모리 풀 외부에서 내부를 가리키는 포인터입니다. +- 내부 포인터와 외부 포인터가 있습니다. + - GC 루트의 종류 + - 스택 프래임 + - JNI + - 레지스터 + - 전역 객체 + - 클래스의 메타데이터 +- 핫스팟 GC는 아레나라는 메모리 영역에서 일어납니다. +- 핫스팟은 자바 힙을 관리할 떄 시스템 콜을 하지 않습니다. +- 핫스팟은 유저공간 코드에서 힙크기를 관리해서 측정값을 확인해서 성능 문제를 확인 할 수 있습니다. +## 6.3 할당과 수명 +- 할당률 + - 일정 기간 새로 생성된 객체가 사용한 메모리 량입니다. (JVM이 기록X 쉽게 측정가능) +- 객체 수명 + - 제대로 측정하기 어렵다. +### 6.3.1 약한 세대별 가설 +- 객체 수명은 BINOMIAL DIST 를 따릅니다. +- 대부분의 객체는 짧은 시간을 살아 있고 다른 객체들은 수명이 훨씬길다. +- 핫스팟은 지표를 모읍니다. + - GENERATION COUNT(GC 통과 횟수) + - EDEN (YOUNG) + - OLD, TENURED(OLD) +- 늙은 객체가 젊은 객체를 참조하는일은 거의 없다. +- 카드 테이블이라는 구조에 늙은 객체가 젊은 객체를 참조하는 정보를 기록합니다. + - 늙은 객체의 o 에 있는 참조 형 필드값이 바뀌면 해당 엔트리를 더티 마킹합니다. + +## 6.4 핫스팟 JVM 의 가비지 수집 +- 자바는 OS를 이용해 동적으로 메모리를 관리하지 않습니다. +- 프로세스 시작 -> JVM 메모리 할당 -> 단일 메모리풀 관리 +- 방출 -> 수집기가 객체들을 에덴 -> 에덴 -> 테뉴어로 이동시킴 +- +### 6.4.1 스레드 로컬 할당 +- JVM은 성능을 강화하여 에덴을 관리 +- 여러 버퍼로 나눠서 각 애플리케이션 스레드가 새객체를 할당하는 구역으로 배포 +- 각 스레드가 스레드 로컬 할당 버퍼(TLAB)을 받게됨 (동적 할당) +- 스레드가 버퍼를 다 채우면 JVM은 새 에덴 영역을 가르키는 포인터를 내줍니다. + +### 6.4.2 방출형 수집 +- 장수하지 못한 객체를 임시 수용소에 담자 +- 테뉴어드 세대를 어지립히지 않고 풀 GC 발생 빈도 줄이기 + - 수집기가 라이브 반구를 수집할 때, 객체들은 다른 반구로 압착시켜 옮기고 수집된 반구는 비워서 재사용 + - 절반의 공간은 항상 완전히 비운다. +- 핫스팟에서 영 힙의 반구부를 서바이버 공간이라고 합니다. +- VISUAL gc 플러긴을 사용하면 gc 로그를 확인할 수 있습니다. + +## 6.5 병렬 수집기 +- JVM 디폴트 가비지 수집기는 병렬 수집기입니다. +- 처리율에 최적화 되어 있습니다. +- 영 GC, 풀 GC 모두 풀 STW를 일으팁니다. + +- 병렬 수집기 종류 + - Parallel GC + - ParallelOld GC +- 영세대 수집은 가장 흔한 가비지 수집 형태입니다. +- 스레드가 에덴에 객체를 할당하려는데 자신이 할당받은 TLAB 공간이 부족하면 영세대 수집을 발생시킵니다. +- JVM은 그떄 할수없이 STW 합니다. +- 스레드가 중단되면 핫스팟은 영세대를 뒤져서 가비지 아닌 개게를 골라냅니다. +- Parallel GC는 살아남은 객체를 현재 비어있는 서바이버 공간으로 모두 방출한후, 세대 카운트를 늘려 한차례 이동했음을 기록합니다. + - 에덴과 객체를 방출 시킨 서바이버 공간을 재사용가능한 빈 공간으로 표시하고 TLAB을 스레드에 배포합니다. + - 살아있는 객체만 건드려서 약한 세대별 가설을 활용 + - STW 중단시간을 조금이라도 단축해서 효율적으로 수집하겠다는 의도 +### 6.5.2 올드 세대 병렬 수집 +- 하나의 연속된 메모리 공간에서 압착하는 수집기 +- 아주 효율적이고 메모리 단편화가 일어날 일도 없다. +- 올드 공간은 크게 눈에 띄는 변화가 없습니다. +- ParallelOld GC는 하나의 연속된 메모리 공간에서 압착하는 수집기이비다. +- 메모리 단편화 안일어난다. 효율적이다. + +### 6.5.3 병렬 수집기의 한계 +- 풀 STW 유발한다. +- 영 수집은 살아있는 객체에 STW 시간이 비례한다 10ms +- 올드 수집은 stw 시간이 힙크기에 비례 +- 손쉬운 스레드 생성이 자바의 강력한 장점 하지만 새로운 실행 스택들이 gc 루트의 원천이라서 gc가 복잡해진다. + +## 6.6 할당의 역할 +- 유입된 메모리 할당 요청을 수용하기에 메모리가 부족할 때 작동해서 필요한 만큼 메모리 공급 +- 힙 메모리 공간이 꽉채워져 더이상 객체를 생성할 공간이 없을 대 일어난다. (불규칙함) +- gc 가 발생하면 모든 애플리케이션 멈춤 +- visual vm으로 메모리 사용패턴을 잘 보면 힙사용률이 톱니바퀴로 나타나게 된다. +- 할당률이 너무 높음 -> 조기승격하게됨 +- 튜닝 포인트가 된다. + + diff --git a/ch07/README.md b/ch07/README.md index 0ea2762..0ac3da0 100644 --- a/ch07/README.md +++ b/ch07/README.md @@ -1 +1,157 @@ -# Ch7. 가비지 수집 고급 +# 7. 가비지 수집 고급 +- 현대 자바의 가비지 수집 이론 +- 다양한 수집기들의 선정하는 배경 +- 각종 수집기들의 특성 + + +## 7.1 트레이드오프와 탈착형 수집기 +- 코드 변경없이 탈착가능하다. +- 고려 항목들 + - 중단시간 (제일큰 관심사) + - 처리율 (런타임 대비 GC 시간 %) + - 중단 빈도 (수집기 때문에 얼마나 자주 멈추냐) + - 회수 효율 (GC 사이클당 얼마나 많은 가비지 수집되는가?) + - 중단 일관성(중단시간이 고르냐?) +- 예 + - 배치처리 시스템은 중단 기간보다 처리율이 더 큰 영향을 미친다. + - CPU 효율 및 처리율 우수한 GC를 사용해야한다. +## 7.2 동시 GC 이론 +- 문제: 메모리 할당이 불확정성을 유발하므로 언제 끼어들지 확정적이지 않다. +- 최신 GC는 해당 문제를 해결하려고 시도한다. +- 동시 수집기를 써서 스레드 실행 도중 수집에 필요한 작업 일부를 수행한다. +### 7.2.1 JVM 세이프 포인트 +- STW 가비지 수집을 하려면 애플리케이션 스레드를 모두 중단시켜야합니다. +- JVM은 스레드마다 SAFE POINT를 둡니다. +- GC 스레드가 OS에게 무조건 중단시켜달라고 요청할 수 없기 때문에, 애플리케이션 스레드는 반드시 서로 공조해야합니다. + - JVM은 강제로 스레드를 세이프포인트 상태로 바꿀 수 없다. + - JVM은 스레드가 세이프 포인트 상태에서 벗어나지 못하게 할 수 있다. +- JVM이 중단 하는 방법 +- 1. JVM이 전역 세이프포인트 시간 플래그를 세팅한다. +- 2. 각 애플리케이션 스레드는 폴링 하면서 이 플래그가 세팅됐는지 확인한다. +- 3. 애플리케이션 스레드는 일단 멈췄다가 다시 깨어날때까지 대기한다. +- 일반 애플리케이션 스레드는 이런 식으로 바이트 코드 2개 실행할때마다 체크하게 된다. +### 7.2.2 삼색 마킹 +- 알고리즘 동작원리 + - GC루트를 회색 표시한다. + - 다른 객체는 모두 흰색 표시한다. + - 마킹 스레드가 임의의 회색 노드로 이동한다. + - 마킹 스레드가 흰색 표시된 자식 노드가 있는 노드를 만나면, 먼저 회색 표시한후 해당 노드를 검은색으로 표시한다. + - 회색 노드가 하나도 남지 않을때까지 위 과정을 되풀이한다. + - 검은색 객체는 접근 가능한것으로 살아남는다. + - 흰색객체는 수집대상이된다. +- 동시 수집 (스냅샷뜨기) + - 수집 사이클 시작할때, 할당된 객체는 라이브 객체로 간주한다. +- 단점 + - 변경자 스레드가 수집하는 도중에 검은색 상태, 수집을 안하는 동안 흰색 상태로 객체 생성 + - 라이브 객체가 수집되는 현상이 생길 수 도 있다. + - 업데이트시에 쓰기 배리어를 이용해서 마킹 사이클 동안 삼색을 그대로 유지합니다. + - `동시 마킹 도중에 검은색 객체 노드가 흰색 객체노드를 가리킬 수 없다.` + - 모든 변경사항을 미리 큐에 넣어두고 MAIN 이 끝나면 FIXUP에 바로잡는 경우도 있습니다. + +## 7.3 CMS +- CMS 수집기는 중단시간을 아주 짧게하려고 설계된 TENURED 공간 전용 수집기입니다. +- 중단 시간을 최소화 하기 위해 스레드 실행중에 가급적 많은 일을 합니다. +- 마킹은 삼색 마킹 +- 레코드 FIXUP + - 초기 마킹 (STW) GC루트 얻음 + - 동시 마킹 | 삼색 마킹 진행 + - 동시 사전 정리 + - 재마킹 (STW) + - 동시 스윕 + - 동시 리셋 +- 효과 + - 애플리케이션이 오래 멈추지 않는다. + - 단일 풀 GC 사이클 시간이 더길다. + - CMS GC 사이클동안 처리율 감소한다. + - GC가 객체를 추적하므로 메모리 사용 증가한다. + - GC 수행에 훨씬 더 많은 CPU 시간이 필요하다. + - CMS는 힙을 압착하지 않아서 테뉴어드 영역은 단편화된다. + - 느린 GC로 조기승격이 많이일어나 TENURED 공간 부족이 생긴다 + - 동시 모드 실패로 불린다. + - 75 % 점유 이상시에 CMS 가 수집을 하는게 옵션으로 잡혀있다. + - 힙 단편화가 발생해서 그럴수도있다. + - 스위퍼 스레드로 여유 공간을 뭄치는 역할을 한다. +## 7.4 G1 +- CMS와 스타일이 다른 수집기입니다. +- 특징 + - CMS 보다 튜닝이 쉽다. + - 조기 승격에 덜 취약하다. + - 대용량 힙에서 확장성이 우수하다. + - 풀 STW 수집을 없앨수 있다. +- G1 힙 레이아웃 및 영역 + - G1 힙은 영역으로 구성됩니다 + - 영역은 디폴트 크기가 1MB + - 영역을 이용하면 세대를 연속적으로 배치할 필요 없습니다. + - 수집기가 매번 전체 가비지 수집 할 필요 없다. +- G1 알고리즘 설계 + - 동시 마킹 단계를 이용한다. + - 방출 수집기 + - 통계적으로 압착한다. +- TLAB 할당, 서바이버 공간 방출, 테뉴어드 승격 은 개념 같다. +- 차이점 + - 영 세대의 크기는 전체 중단 시간 목표에 따라 조정된다. + - `올드 객체가 영객체를 참조하는일은 거의 없다.` + - G1 에서는 RSET이라는 장치로 영역을 추적합니다. + - RSET은 영역별로 하나씩 영역 내부를 참조하는 레퍼런스를 관리하기 위한 장치입니다. + - 전체 힙을 다 뒤질 필요없이 RSET만 꺼내보면됩니다. + - RSET은 부유 가비지 라는 GC 문제를 해결하는데 유용합니다. + - 전역적으로 LIVE 이지만 사용한 루트 세트에 따라 범위가 제한적인 로컬 마킹상에서 살아있는 객체로 잘못 인식된다. +- G1 단계 + - 초기 마킹 (STW) 서바이버 영역에서 올드세대 레퍼런스 찾기 그다음 영 GC 탐색 + - 동시 루트 탐색 + - 동시 마킹 + - 재마킹(STW) + - 정리(STW) 어카운팅, RSET의 씻기 + +## 7.5 셰난도아 +- 주목표는 중단시간 단축 +- 동시 압착을한다. +- 단계 + - 초기마킹(STW) + - 동시마킹 + - 최종마킹(STW) + - 동시압착 +- 브룩스 포인터라고 객체가 재배치 됐는지 표기하는 포인터가 생깁니다. +- 해당 포인터가 있으면 새 OOP 위치를 참조하도록 수정합니다. +- 동시 압착 메커니즘 + - 객체를 TLAB로 복사한다. + - CAS로 브룩스 포인터가 추측성 사본을 가리키도록 수정한다. + - 이 작업이 성공하면 압착스레드가 승리 이후 모든 버전은 브룩스 포인터를 경유한다. + - 실패하면 압착스레드가 실패한것으로 추측성 사본을 원상복구하고 승리한 스레드가 남긴 브룩스 포인터를 따라간다. +- 해당 수집기는 레드햇 페도라 같은 배포판에 실려있다. +- 중단시간 개짧네;; +## 7.6 C4 +- 오라클 상용 솔루션 +- 동시 압착 알고리즘 사용하는데 객체 헤더를 쓴다. +- 로드값을 자가치유 베리어로 로딩이 끝나면 객체 위치를 직접 가리키게 만들자. +- 단계 + - 마킹 + - 재배치 + - 재매핑 +- 영객체 + 올드 객체 +- 동시 수집기 튜닝시 수집기가 백투백모드로 실행된다. +## 7.7 밸런스드 +- 4GB 이상의 힙에 맞게 설계됨 +- 대용량 힙에서 중단시간 길어지는 현상 개선 +- 중단 시간이 최악일 경우 최소화 +- IBM 에서 부분 가비지 수집으로 에덴이 꽉차면 수집진행함 +- 나이가 많은 영역은 필요하면 수집하게됩니다. (G1 도 MIXED COLLECTION 함) +- 전역 가비지 수집도 있다. +- 객체 헤더는 클래스 슬롯이라고 불린다. +- 어레이릿이라고 여러 영역에 걸쳐서 할당할 수 있따. + - 전체 GC 시간은 더걸리지만 평균 중단시간은 줄어든다. + - 압착 혹은 stw 수집의 경우의 수가 줄어든다. + +## 7.8 레거시 핫스팟 수집기 +- Serial && Serial Old + - Parallel Old GC와 동작원리 동일 +- 증분 CMS + - 동시 수집 + - G1에 영향 +- 엡실론 + - 아무일도 안하는 수집기 + - 테스트 및 벤치마킹 + - 회귀 테스트 + - 할당률이 0인 애플리케이션 혹은 라이브러리 코드의 테스트 + - JMH테스트 용 +## 7.9 마치며 diff --git "a/ch07/\353\260\225\354\213\234\354\260\275.md" "b/ch07/\353\260\225\354\213\234\354\260\275.md" new file mode 100644 index 0000000..8b13789 --- /dev/null +++ "b/ch07/\353\260\225\354\213\234\354\260\275.md" @@ -0,0 +1 @@ + diff --git "a/ch08/\353\260\225\354\213\234\354\260\275.md" "b/ch08/\353\260\225\354\213\234\354\260\275.md" new file mode 100644 index 0000000..8977255 --- /dev/null +++ "b/ch08/\353\260\225\354\213\234\354\260\275.md" @@ -0,0 +1,165 @@ +# 8. gc로깅, 모니터링, 튜닝, 툴 +- 자바 성능 튜닝에서 gc 로깅과 모니터링을 다뤄보자 + +## 8.1 GC 로깅 개요 +- GC 로그는 훌륭한 원천정보입니다. +- 콜드 케이스 분석(시스템 원인 분석을 할때도 유용), 후 분석 가능 +- 모든 애플리케이션 두가지 설정해야함 + - GC 로그 생성 + - 특정 파일에 GC 로그 보관 +- GC 로깅은 오버헤드가 없다. +- JVM 프로세스는 로깅을 켜놓아야한다. +### 8.1.1 GC 로깅 켜기 +jvm을 사용하면 gc로깅은 무조건 켜야된다. +`-Xloggc:gc.log -XX:+PrintGCDetails -XX:PrintTenuringDistribution` +`-XX: +PrintGCTimeStamps -XX:+PrintGCDateStamps` +-Xloggc:gc.log -> gc 이벤트에 로깅할 파일을 정한다. +-XX:+PrintGCDetails -> gc 이벤트 세부 정보를 로깅한다. +-XX:PrintTenuringDistribution -> 툴링에 꼭 필요한 부가적인 GC이벤트 세부 정보를 추가한다. + -> 메모리 할당 압박 효과, 조기승격 이벤트 계산시 필요한 기초 데이터 제공 +-XX: +PrintGCTimeStamps -> GC 이벤트 발생 시간을 출력한다. (RUNTIME) + -> GC 이벤트와 애플리케이션 이벤트 연관 관계 도출 +-XX:+PrintGCDateStamps -> GC 이벤트 발생 시간을 출력한다. (시계시간) + -> GC 이벤트와 JVM 이벤트를 연관 관계 도출 + +`-XX:+UseGCLogFileRotation -XX:+NumberOfGCLogFiles= -XX:+GCLogFileSize=` +-XX:+UseGCLogFileRotation -> 로그 순환 기능을 켠다. +-XX:+NumberOfGCLogFiles= -> 보관 가능한 최대 로그 파일 개수를 설정한다. +-XX:+GCLogFileSize= -> 순환 직전 각 파일의 최대 크기를 설정한다. + + +## 8.1.2 GC 로그 VS JMX +VisualGC는 JVM 힙 상태를 실시간으로 표시하는 툴입니다. JMX 인터페이스를 통해서 JVM 데이터를 수집합니다. +JMX는 GC에 영향을 줍니다. +1. GC 로그데이터는 실제로 가비지 수집 이벤트가 발생해서 쌓이는 반면 JMX는 데이터를 샘플링하여 얻습니다. +2. GC 로그 데이터는 캡처 영향도가 없지만, JMX는 프록시 및 원격 메서드 호출 관정에서도 암묵적인 비용이 듭니다. +3. GC 로그 데이터에는 자바 메모리 성능 데이터가 50가지 있지만 JMX는 10가지도 안됩니다. + + +### 8.1.3 JMX의 단점 +1. 런타임을 샘플링하여 현재 상태를 업데이트 받음 +2. 런타임에 있는 JMX 빈을 폴링한다. +-> +- 문제는 가비지 수집 -> 각 수집 사이클 전후의 메모리 상태를 알 수 없어서 GC 데이터를 깊이 있게 정확하게 분석할 수 없다. +- 메모리압(할당률) 분석해야하는데 JMX는 데이터 수집 방식이 불가능하게한다. +- 추가적 단점들 + - 방화벽에 포트를 열어야한다. -> 부차 소켓 접속이 맺어질 수 있다. + - 프록시 객체를 이용해 remove() 메서드 호출을 대행한다. + - 자바 종료화(finalization) 에 의존한다 -> 가비지 수집기를 돌려서 객체 회수해야됨 -> 1시간마다 풀gc 발생 -> 부하 발생 + +### 8.1.4 GC 로그 데이터의 장점 +- 처음 자바 gc를 개발한 분들은 GC 로깅을 JVM 구현체 디버깅 용도로 추가했습니다. +- 60개 가까운 GC플래그로 생성된 데이터 상당수가 성능 디버깅 목적으로 쓰이게 됐죠 +- GC 로그 수집 및 분석은 튜닝 활동에서 절대 빠질 수 없다. +- GC 로그는 핫스팜 JVM 내부에서 논블로킹쓰기 메커니즘을 이용해 남깁니다. -> 성능에 영향 0 + + +## 8.2 로그 파싱 툴 +- 반드시 툴을 사용하세요 + +### 8.2.1 센섬 +- 센섬은 jClarity 사가 제작한 상용 메모리 분석기 입니다. 마소 -> gc툴킷으로 오픈소스로 풀어버림 +- gc로그 파싱, 정보 추출, 자동 분석 기능 제공 + - 정확한 할당률 + - 조기 승격 + - 공격적인 할당 + - 유저 이탈 + - 메모리 누수 감지 + - 힙 크기 조정 및 용량 계획 + - vm에 대한 OS 간섭 + - 크기를 잘못잡은 메모리 풀 + +### 8.2.2 GCViewer +- 상용툴에 비해 빈약한 기능 + +## 8.3 GC 기본 튜닝 +- GC는 언제 튜닝해야 할까? +- GC가 성능 문제를 일으키는 근원이라고 확인하거나 배제하는 해위는 저렴하다. +- UAT에서 GC 플래그를 켜는 것도 저렴하다. +- 메모리 프로파일러, 실행 프로파일러를 설정하는 작업은 저렴하지 않다. +- GC를 모니터링할때, 해당 사항들을 주시해야합니다. + - 할당 (젤 중요) + - 중단 민감도 + - 처리율 추이 + - 객체 수명 + +방법론 +1. vmstat 같은 툴로 고수준의 머신 지표를 체크 +2. CPU 사용률이 100%에 가까운가? +3. 시간(90%) 이상이 유저 공간에서 소비되는가? +4. GC로그가 쌓이고 있다면 현재 GC가 실행 중이라는 증거다. + +세가지 조건이 다맞으면 GC일 확률이 높다. + + +### 8.3.1 할당이란? + +- 할당률 분석은 가비지 수집기를 튜닝하면 성능이 개선될지 여부를 판단할 수있따. +- 이벤트 데이터를 활용하면 할당된 데이터양, 단위 수집 시간을 계산 +- 평균 할당률을 산출 할 수 있다. + +- 할당률 수치가 1GB/s 이상으로 지속한다면 십중팔구 가비지 수집기 튜닝만으로는 해결할 수 없는 문제가 있다. +- 애플리케이션 핵심부 할당 로직을 제거하는 리팩터링을 수행하여 메모리 효율을 개선해야한다. + +효율 개선 방법 (Visual VM, jmap) +- 굳이 없어도 그만인, 사소한 객체 할당 (로그 디버깅 메시지) + json 직렬화, 역직렬화 용 자동 생성 코드 + ORM 코드 +- 도메인 객체의 메모리 과도할당 문제 + - 단순 힙 히스토그램 그려보면 도메인 객체가 히스토그램 상위권을 점유하는 것을 볼 수 있습니다. +- 덩치 큰 배열만 곧바로 테뉴어드에 할당될 가능성이 큽니다. +- 핫스팟은 TLA 및 큰 객체의 조기 승격에 관한 튜닝 플래그를 제공합니다. +- 하지만 할당률은 테뉴어드로 승격되는 객체 수에 영향을 끼칩니다. +- 단명 객체는 장례를 치를 시간도 없이 테뉴어드로 잘못 승격될 가능성이 큽니다. +- +- 조기 승격 관련 스위치 `-XX:MaxTenuringThreshold=` + - 한계치가 높을수록 진짜 장수한 객체를 더 많이 복사합니다. + - 너무 낮으면 단명 객체가 승격되어 테뉴어드 메모리압을 가중 시킵니다. +- 테뉴어드로 승격되는 객체가 증가하고 풀 수집이 더 자주 발생합니다. -> 스위치를 함부러 변경하지 ㅏㅁ세요 + + +### 8.3.2 중단 시간이란? +- 애플리케이션에서 100ms 정도의 중단 시간은 무시할만합니다. +- 풀스탑의 민감도에 따른 수집기 선택 + - 1 초 이상 걸려도 괜찮다 + - PARALLEL/G1 + - 1초 100 ms초 + - PARALLEL/G1 + - 무조건 100ms 이하 + - CMS 돌려야함 + - +### 8.3.3 수집기 스레드와 GC 루트 +- GC 스레드 처럼 생각하려고 생각해봐라 +- 루트 탐색 시간은 세가지 영향 요소가 있다. + - 애플리케이션 스레드 개수 + - 코드 캐시에 쌓인 컴파일드 코드량 + - 힙 크기 +- 객체 그래프가 복잡할 수록 마킹이 오래 걸릴 확률이 높다. +- 애플리케이션 스레드가 많아져도 스택 프레임을 더 많이 탐색해야한다. +- 세이프 포인트에 도달하는 시ㅏㄴ도 길어지는 등 gc 시간에 영향을 끼친다. + +## 8.4 Parallel GC 튜닝 +- 풀 STW +- GC 처리율이 높고 계산 비용이 싸다 +- 부분 수집 X +- 중단 시간은 힙크기에 비례 +- 가장 단순해서 튜닝이 젤 쉬움 + +## 8.5 CMS 튜닝 +- CMS는 플래그의 가짓수가 방대하다. +- 튜닝을 하기 위해선 위험을 무릅써야한다. +- 처리율 + - GC 생기면 코어 절반이 GC에 할당됨 + - CMF가 생기기 직전에는 백투백 CMS 수집이 생김 + - 전체 애플리케이션 처리율 50%로 떨어짐 + - GC 할당 코어 줄이는 방법이 있지만 결국 CMF에 취약해짐 +### 8.5.1 단편화로 인한 CMF +- 튜닝 분석에 필요한 데이터가 GC로그에만 존재하는 사례 +- 프리리스트 통계치를 바탕으로 CMF를 예측할 수 있다. +- `-XX:PrintFLSStatistics=1` +- -> 메모리 청크의 크기 분포 짐작 가능 하고 로그를 분석하거나 센섬같은 툴을 쓰면 cmf 근접사실을 알아 낼 수 있습니다. + +## 8.6 G1 튜닝 +- 최대 힙크기와 최대 GC 중단 시간을 간간히 설정하면 나머지는 수집기가 하는것이 G1 튜닝의 최종 목표입니다. +- 하지만 변화가 많아서 베스트프랙티스를 가이드하긴 어렵다. + - 영세대 설정 + - 테뉴어드 한계치 추가 + - 수용가능한 최장 중단시간 목표 정해두기 diff --git "a/ch09/\353\260\225\354\213\234\354\260\275.md" "b/ch09/\353\260\225\354\213\234\354\260\275.md" new file mode 100644 index 0000000..64ff3ef --- /dev/null +++ "b/ch09/\353\260\225\354\213\234\354\260\275.md" @@ -0,0 +1,152 @@ +# 9. JVM 코드실행 +- JVM의 코어 기능은 메모리 관리와 코드 실행 컨테이너다. +- 인터프리터로 자바 바이트코드를 실행하는환경은 기계어 직접 실행보다 성능이 떨어진다. +- 동적 컴파일 기능, JIT을 이용해서 이 문제를 해결한다. +- JIT 컴파일 기법은 실행 중인 메서드를 지켜보고 있다가 직접 실행 가능한 코드로 컴파일할 메서드를 분별하는 메커니즘이다. + - 바이트 코드 해석 + - 프로파일 기반 최적화의 기초 개념 + - 코드 캐시 및 핫스팟 컴파일 서브시스템 + - 최적화를 어떻게 진행할지 + - 컴파일드 메서드 생성원리 + - 튜닝 방식 +## 9.1 바이트 코드 해석 +- JVM 인터프리터는 스택머신처럼 작동, 물리적 CPU와는 달리 레지스터는 없다. +- 작업할 값을 평가 스택에 놓고 스택 머신 명령어로 스택 최상단에 위치한 값을 변환 +- 데이터 공간 + - 평가 스택: 메서드별 하나씩 생성 + - 로컬 변수: 결과 임시 저장 + - 객체 힙: 메서드 끼리 스레드끼리 공유 +### 9.1.1 JVM 바이트 코드 개요 +- 작업 코드는 1바이트로 나타냄 +- 스택 상단에 위치한 두값의 기본형을 구분할 수 있게 표기 +- IADD OR DADD (int 형 vs DOUBLE형) +- store 패밀리는 dstorem astore 로 double 형, 참조형이 있습니다. +- jvm은 빅엔디언 리틀엔디언 하드웨어 아키텍쳐 모두 바이트 코드 변경없이 실행 가능하다. +- load같은 옵코드 군에는 단축형이 있어서 인수 생략하고 클래스 파일의 인수 바이트 공간을 절약가능하다. +- this(현객체) alod_0 같은 명령어는 자주쓰여서 클래스 파일 크기 줄어든다. +- 자바 플랫폼 초기에는 클래스파일을 14.4kbps 모뎀을 통해 내려받아야되서 압축이 중요 이슈였다. + +### 9.2 로드/스토어 +- 스택에 데이터를 넣고 빼는 옵코드 + - 상수 풀에서 데이터를 로드하거나 스택 상단을 힙에 있는 객체 필드에 저장하는 작업 + - load store +- ldc는 클래스의 상수풀에 있는 상수 로드 +- const는 매개변수 없이 aconst_null 형태로 진짜 상수만 로드하는 옵코드 + +### 9.2 산술 +- add +- sub +- div +- mul +- (cast) +- 기본형에 적용되며 스태 기반 연산 수행 + +### 9.3 흐름 제어 카테고리 +- if +- goto +- 흐름을 제어하는 바이트코드 + +### 9.4 메서드 호출 바이트 코드 +- 프로그램에서 새 메서드로 제어권을 넘기는 유일한 장치 +- 지역 흐름 제어와 다른 메서드로 제어권을 넘기는 행위를 구분 +- invokevirtual -> 메서드를 가상 디스패치를 통해 호출 +- invokespecial -> 메서드를 특별한 디스패치를 통해 호출 +- invokeinterface -> 인퍼에시ㅡ 오프셋 룩업을 이용해 인터페이스 메서드 호출 +- invokestatic -> 정적 메서드 호출 +- invokedynamic -> 호출해서 실행할 메서드를 동젖ㄱ으로 찾기 + + +- call site (호출부) 는 메서드 내부에서 다른 메서드(callee)를 호출한 지점 +- 비정적 메서드 호출의 경우, 어느 객체의 메서드인지 찾아야하는데 이런 객체를 수신자(receiver obj)라고 합니다. +- 인스턴스 메서드 호출은 invokevirtual +- 자바 인터페이스 메서드 호출 invokeinterface +- 컴파일 타임에 디스패치할 메서드를 특정할 수 있는 경우 (private or superclass ) invokespecial로 컴파일 +- invokedynamic은 람다 같은 친구를 지원한다. +- invokedynamic 명령어에 의해 호출 되는 플랫폼 팩토리 메서드가 생성합니다. +- 클래스의 상수 풀에서 확장된 엔트리를 참조하여 런타임에 동적 호출 가능 +- 플랫폼 옵코드 + - 객체별로 힙 저장공간을 새로 할당 + - 고유락을 다루는 명령어 + - monitorenter + - monitorexit + - newarray + - 할당할 배열길이가 스택 상단에 놓여진다. + - new + - anewarray +- 바이트코드는 구현 복잡도에 따라 대단위 바이트 코드와 소단위 바이트코드로 구분됨 + - 메서드 디스패치가 필요한 작업은 대단위 연산이 핫스팟 vm을 다시 호출한다. +- JVM애플리케이션 스레드 하나하나가 진짜 OS 스레드 + - JVM이 어떤 관리 작업을 수행하고 내부상태를 일관되게 유지하는 지점을 세이프포인트라고함 + - 객체 그래프가 들어 있음 + - 바이트 코드 사이사이가 단순한 세이프 포인트로 사용될 수 있음 + + +### 9.2 AOT와 JIT 컴파일 + +### 9.2.1 AOT 컴파일 +- 소스 코드를 컴파일러에 넣고 실행가능한 기계어로 뽑아내는것 +- AOT는 프로세서 아키텍쳐와 실행한 플랫폼에 맞는 실행코드를 얻는것 +- AOT 컴파일하게되면 사용가능한 프로세서 기능에 대해서 가장 보수적인 선택하게 되고 성능 향상 힘듬 + + +### 9.2.2 JIT 컴파일 +- JIT 컴파일은 런타임에 프로그램을 고도로 최저고하한 기계어로 변환하는 기법 +- 프로그램의 런타임 실행 저보를 수집해서 어느부분을 최적화해야할지 프로파일을 만들어 결정하게됨 +- 바이트 코드를 네이티브 코드로 컴파일하는 비용은 런타임에 지불됩니다. +- 프로파일링 서브시스템은 어느 메서드가 실행 중인지 추적합니다. +- 컴파일 하기 적정 한계치를 넘어선 메서드가 발견되면 컴파일 스레드를 가동해 바이트코드를 기계어로 변환 +- 핫스팟은 프로파일링 정보를 보관하지않고 실행마다 다시 만듭니다. + +### 9.2.3 AOT 컴파일 VS JIT 컴파일 +- 핫스팟은 새로운 프로세서기능을 활용할 수 있고 다시 컴파일 하지 않아도 된다. +- JIT 컴파일러도 향상되고 핫스팟 VM도 향상된다. + + +### 9.3 핫스팟 JIT 기초 +- 핫스팟의 기본 컴파일 단위는 전체 메서드 +- 한 메서드에 해당하는 바이트 코드는 네이티브 코드로 컴파일됩니다. +- 핫스팟은 주로 KLASSS 메타 데이터 구조체에 있는 vtable을 이용해 JIT 컴파일을 구현합니다. +- 핫스팟은 C++ 애플리케이션 입니다. +- JIT 컴파일 서브시스템을 구성하는 스레드는 핫스팟 내부에서 가장 중요한 스레드들입니다. +- 낙점된 메서드는 컴파일 스레드에 올려놓고 백그라운드에서 컴파일합니다. +- 최적화된 기계어가 생성되면 VTABLE은 새로 컴파일된 코드를 가리키게 됩니다. (포인터 스위즐링) + +### 9.3.2 JIT 컴파일 로깅 +- 컴파일 이벤트 로그가 표준 출력 스트림에 생성되므로 성능 엔지니어는 이 로그를 보고 어떤 메서드가 컴파일 되고 있는지 파악 가능 +- `-XX:+PrintCompilation` +- n 은 네이티브 메서드, s는 동기화 메서드, ! 은 예외 핸들러를 지닌 메서드 +- `-XX:+LogCompilation`은 진단용 옵션 `-XX:+UnlockDiagnosticVMOptions` 로 바이트 코드를 어떻게 네이티브 코드로 최적화 했는지 + - 큐잉은 어떻게 처리했는지 xml 태그형태로 담은 로그파일로 출력하라는 지시입니다. +### 9.3.3 핫스팟 내부의 컴파일러 +- C1, C2라는 두 JIT 컴파일러가 있음 +- C2 실행시간 긴 서버 애플리케이션 대상 +- 핫스팟은 기준없이 최대한 성능 맞게 변화 +- 메서드 호출 횟수에 따라 컴파일이 트리거링됨 +- 호출 횟수가 특정 한계치에 다다르면 VM이 알림 받고 해당메서드를 컴파일 큐에 넣음 +- SAA 같은 FINAL로 만드는 기법을 사용함 + +### 9.3.4 핫스팟의 단계별 컴파일 +- 컴파일모드 지원합니다. 하지만 디폴트라서 건드릴 이유 없음 + +## 9.4 코드 캐시 +- JIT 컴파일드 코드는 코드 캐시라는 메모리 영역에 저장 +- VM 시작시 코드 캐시는 설정된 값으로 최대 크기가 고정 확장 불가능 +- 코드 캐시가 차면 JIT 컴파일 안됨, 컴파일 되지않는 코드는 인터프리터에서 실행 +- 최대로 성능 못냄 +- 코드 캐시는 스위퍼로 재활용하고 프리블록연결리스트를 담은 힙으로 구현됩니다. +- 네이티브 코드를 제거할 때는 역최적화(추측 틀림) +- 다른 컴파일 버전으로 교체될때 +- 메서드를 지닌 클래스가 언로딩 될때 제거됩니다. + +### 9.4.1 단편화 +- 코드 캐시는 단편화 되기 쉽다. (JAVA 8) + +### 9.5 간단한 JIT 튜닝법 +- 컴파일을 원하는 메서드에게 리소스 먼저 다쓴다 +- 캐시 크기를 늘리면 컴파일드 메서드 규모가 유의미한 방향으로 커지는지 확인 필요 +- 주요 트랜잭션 경로상 위치한 주요 메서드 모두 컴파일 되는지 확인 +- 트랜잭션 메서드드들이 컴파일 로그에 뜨는 지 확인 + +### 9.6 마치며 +- JIT 컴파일드 코드의 동작이 인터프리터 동작보다 더 중요하다 +- 다음장은 JIT 컴파일 이론을 체크해봅시다. diff --git "a/ch10/\353\260\225\354\213\234\354\260\275.md" "b/ch10/\353\260\225\354\213\234\354\260\275.md" new file mode 100644 index 0000000..bf93596 --- /dev/null +++ "b/ch10/\353\260\225\354\213\234\354\260\275.md" @@ -0,0 +1,163 @@ +# 10. JIT 컴파일 +- JITWatch를 이용해서 핫스팟이 실제로 바이트코드에 무엇을 했는지 이해하는데 도움이 되고 측정할 수 있습니다. +- 핫스팟 컴파일 상세로그를 분석하여 자바 FX GUI 형태로 보여줍니다. +- `-XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoading -XX:+LogCompilation` 으로 스위치를 켜야한다. +### 10.1.1 기본적인 JITWatch 뷰 +- 샌드박스에서 간단한 애플리케이션에 대한 최적화를 실험해 볼 수 있습니다. +- 3단 뷰에서 + - 소스콛, 바이트코드 어셈블리로 어떻게 컴파일도니ㅡㄴ지 보여줍니다. + - java 8이전에는 컴파일드 메서드, 논프로파일드 메서드, vm 자체 네이티브 코드를 하나의 코드 캐시영역에 담았습니다. + - java 9 부터는 분할 코드 캐시가 새로 생겨서 네이티브 코드 유형마다 별도의 영역에 저장할 수 있습니다. +### 10.1.2 디버그 JVM과 hsdis +- JIT 서브시스템의 통계치를 얻으려면 디버그 JVM을 이용하면 됩니다. +- 디버그 JVM은 운영 JVM보다 더 상세한 디버깅 정보를 추출하려고 제작한 가상 머신입니다. + +## 10.2 JIT 컴파일 개요 +- 핫스팟은 프로파일 기반 최적화(PGO)를 통해 JIT 컴파일 여부를 판단 +- 핫스팟이 실행 프로그램 정보를 메서드 데이터 객체라는 구조체에 저장 +- MDO는 바이트 코드 인터프리터와 C1 컴파일러에서 JIT 컴파일러가 언제 무슨 최적화를 할지 결정하는데 필요한 정보를 기록 + - 어떤 메서드가 호출됐고, 어느 분기문으로 갈라졌고, 호출부에서는 무슨 타입이었는지 담겨져있습니다. +- 사용빈도를 카운터에 계속 기록하고 그렇게 기록한 값들은 프로파일링을 거치면서 차츰 사라집니다. +- 컴파일러는 컴파일할 코드의 내부 표현형을 빌드합니다. + - 인라이닝 + - 루프 펼치기 + - 탈출 분석 + - 락 생략/확장 + - 단일형 디스패치 + - 인트린직 + - 온-스택 치환 +- c1은 추측성 최적화하지않고 +- c2는 공격적인 최적화기로 런타임 실행을 주시한 결과를 토대로 추정하고 최적화를 수행합니다. +- 큰 성능 향상효과를 볼 수 있습니다. +- `가드`라는 타당성 검사를 합니다. +- 가드마저 실패하면 컴파일드 코드는 안전하지 않아서 제거하게 됩니다. + +## 10.3 인라이닝 +- 인라이닝은 호출된 메서드의 콘텐츠를 호출한 지점에 복사하는 것입니다. +- 호출시의 오버헤드를 제거할 수 있습니다. + - 전달할 매개변수 세팅 + - 호출할 메서드를 룩업 + - 런타임 자료구조(지역 변수, 평가스택) 생성 + - 새 메서드로 제어권 이송 + - 호출부에 결과 반환 +- 제일 먼저 적용하는 최적화로 `관문 최적화`라고 불립니다. +- 해당 전략들의 근간이 됩니다. + - 탈출분석 + - DCE(죽은 코드 제거) + - 루프 펼치기 + - 락 생략 +### 10.3.1 인라이닝 제한 +- JIT 컴파일러가 메서드를 최적화하는데 소비하는 시간 +- 생성된 네이티브 코드 크기(코드 캐시 메모리 사용량) +- 핫스팟의 인라이닝 조건 + - 인라이닝할 메서드의 바이트코드 크기 + - 현재 호출 체인에서 인라이닝할 메서드의 깊이 + - 메서드를 컴파일한 버전이 코드 캐시에서 차지하는 공간 + +### 10.4 루프 펼치기 +- 순회할 때 마다 루프 처음으로 되돌아가는 횟수를 줄이기 위해 루프를 펼칠 수 있습니다. +- 백 브랜치가 일어나면 그때마다 CPU는 유입된 명령어 파이프라인을 덤프하기 때문에 성능상 바람직 하지 않습니다. + - 백 브랜치란 배열 인덱스 변수가 배열 경계내에 있는지 확인하는 작업 + - 루프 바디가 짧을수록 백브랜치 비용은 상대적으로 높습니다. 핫스팟은 다음 기준을 가지게 됩니다 + - 루프 카운터 변수 유형(대부분 INT 사용) + - 루프 보폭(한번 순회할 때, 루프 카운터 값이 얼마나 바뀌는가?) + - 루프 내부의 탈출 지점 개수(RETURN 또는 BREAK) + - BASE 레지스터 + - INDEX 레지스터 + - OFFSET + - 루프 펼치기는 핫스팟 버전별로 로직이 상이하고 아키텍쳐마다 많이 다릅니다. + - 사전 루프 + - 경계 검사 + - 메인 루프 + - 경계 검사 x 순회 가능한 최대 횟수를 센다. + - 사후 루프 + - 경계 검사 + - long으로 순회하기 vs int로 순회하기 + - int 형 카운터 루프의 처리량이 64퍼센트 높다. + - long형 카운터를 쓰면 루프바디가 펼쳐지지 않고 루프안에 세이프 포인트 폴이 박힙니다. + - 오랜기간 걸리는 컴파일 코드는 세이프 포인트 폴이 삽입됩니다. +- 루프 펼치기 정리 + - 카운터가 int, short, char 형일때 루프를 최적화합니다. + - 루프 바디를 펼치고 세이프포인트폴을 제거합니다. + - 루프를 펼치면 백 브랜치 횟수가 줄고 분기 예측 비용도 줄어듭니다. +### 10.5 탈출 분석 +- 메서드 내부 수행 작업이 메서드 경계밖에서 볼수 있는지 side effect가 있는지 범위기반분석으로 판별합니다. +- 인라이닝해서 피호출부 메서드 바디를 호출부에 복사하면 호출부에 메서드 인수로 전달된 객체는 더이상 탈출 객체로 표시되지 않기 때문입니다. + - NOESCAPE -> 스칼라로 대체가능 + - ArgsEscape -> 호출 인수로 전달되거나 레퍼런스로 참조됨 호출 도중에는 탈출 하지 않는다. + - GlobalEscape -> 객체가 메서드를 탈출한다. +### 10.5.1 힙 할당 제거 +- 빡빡한 루프 안에서 객체를 새로 만들면 그만큼 메모리 할당 서브시스템을 압박하게 되고 마이너 gc 이벤트가 많이 발생해서 + - 영세대가 꽉차고 단명개체가 올드세대로 조기 승격할 수 있습니다. + - 이지경에 이르면 풀 gc이벤트를 발생시키겠죠 +- VM은 스칼라 치환이라는 최적화를 적용해 객체 필드를 지역변수였던것처럼 스칼라 값을 바꿈 (NOESCAPE 경우) +- 레지스터 할당기라는 컴포넌트에 의해 CPU 레지스터로 할당. +- 탈출 분석의 목표는 힙 할당을 막을 수 있는지 추론. 스택에 할당되고 GC압박을 던다. + - 예시 + - foo가 인수로 전달되면 argEscape (값이 다른 메서드의 인수로) + - 하지만 inlining이 되면 NoEscape로 변신이 가능함 + - foo가 메서드만 부르면 noEscape +### 10.5.2 락과 탈출 분석 +- 핫스팟은 탈출 분석 및 관련 기법을 통해 락 성능도 최적화 합니다. + - 비탈출객체에 있는 락은 제거한다. + - 같은 락을 공유한 영역은 병합한다. + - 핫스팟은 같은 객체에 걸린 중첩락을 감지해서 이미 락획득 상태면 내부락을 제거 + - 해제하지 않고 같은 락을 반복획득한 블록을 찾아낸다. +- 두 락 영역을 더 큰 단일 영역으로 합할 수 있는지 살핍니다. +### 10.5.3 탈출 분석의 한계 +- 탈출 분석 역시 트레드 오프가 있습니다. + - register는 희소한 리소스인데 기본적으로 원소가 64개 이상인 배열은 탈출 분석으 ㅣ혜택을 볼 수 없습니다. + - 배열 길이가 64를 초과하면 무조건 힙에 저장되고 코드의 할당률은 빠르게 상승합니다. + - jmh 벤치시에 성능이 떨어집니다. + - 분기 조건안에 객체 할당을 묶어버리면 탈출 분석의 혜택을 볼 수 있습니다. 그렇지 않으면 -> 객체 할당률 + GC 압 가중 +## 10.6 단형성 디스패치 +- C2 컴파일러가 수행하는 추측성 최적화는 대부분 경험적 연구 결과를 토대로 합니다. +- 단형성 디스패치는 한가지 런타임 타입이 수신자 객체 타입이 된다라는 팩트를 관찰한 것입니다. +- 어떤 객체에 있는 메서드를 호출 할때, 그 메서드를 최초로 호출한 객체의 런타임 타입을 알아내면 그 이후의 모든 호출도 + - 동일한 타입일 가능성이 크다. +- 해당 추측성 가설이 옳다면 + - 해당 호출부의 메서드 호출을 최적화 할수 있습니다. + - 특히, VTABLE에서 에둘러 메서드를 찾을 필요가 없습니다. + - invokevirtual로 가드후에 메서드 바디로 분기하는 코드로 치환하면 됩니다. + - klass 포인터 및 vtable을 통해 룩업하는것을 딱 한번만 하면 됩니다. +- `java.util.Date date = getDate();` + - -> getDate().toInstant() 가 java.util.Date만 반환하면 단형적이고 + - getDate().toInstant() 가 java.sql.Date를 반환하면 다른 구현체를 호출해야하니 추정이 안맞습니다. + - 서로 다른 두 타입을 호출부에서 상이한 klass 워드를 캐시해서 처리합니다. + - 이 호출부를 다형성이라고 합니다. + - 이 다형성도 instanceof 같이 벗겨내어 최적화도 가능합니다. +## 10.7 인트린직 +- 인트린직은 jit 서브시스템이 동적 생성하기 이전에 jvm이 알고있는 네이티브 메서드 구현체를 가리키는 용어입니다. +- 하드웨어 최적화 되어있다. +- Math.min() +- currentTimeMillis() +- JVM의 복잡성 가중시키기는 한다. +## 10.8 온-스택 치환 +- 호출 빈도가 높지는 않지만 메서드 내부에 핫 루프가 포함된 경우가 있습니다. +- 핫스팟은 이런 코드를 온스택 치환을 이용해 최적화 합니다. +- 루프백 브랜치 횟수르 세어보고 한계치를 초과하면 컴파일한 후 치환해서 실행합니다. + +## 10.9 세이프 포인트 복습 +- 세이프 포인트 조건들 + - 메서드 역최적화 + - 힙 덤프 생성 + - 바이어스 락 취소 + - 클래스 재정의 + - 루프 백 브랜치 지점 + - 메서드 반환지점 +- 오래걸리는 룹안에서 세이프 폴링을 할지도 성능 최적화의 관심사이다. + +## 10.10 코어 라이브러리 메서드 +- jarScan으로 바이트코드 한계치 이상 메서드 모두 찾아냅니다 +- 인라이닝 하기 최적화 사이즈로 줄여 낼 수 있습니다. +- 메서드 작게하면 좋은점 + - 인라이닝 가짓수를 늘릴수있습니다. + - 런타임 데이터가 다양해질수록 코드가 핫하게 될 가능성이 있습니다. + - 다양한 인라이닝 트리는 핫 경로를 최적화할 여지가 생깁니다. + - 메서드가 커지면 인라이닝 한계치를 초과해서 최적화 경로 포함 되지 않습니다. + +## 10.11 +- 먼저 좋은 코드를 작성하고 필요한 경우만 최적화하라 + - 인라이닝 한계치 확인 + - 단형성 디스패치 확인 + - 구현체가 하나라도 인터페이스에 맞게 설계 diff --git "a/ch11/\353\260\225\354\213\234\354\260\275.md" "b/ch11/\353\260\225\354\213\234\354\260\275.md" new file mode 100644 index 0000000..d8fd7dc --- /dev/null +++ "b/ch11/\353\260\225\354\213\234\354\260\275.md" @@ -0,0 +1,96 @@ +# 11. 자바언어의 성능향상기법 +- 쓰레기같은 코드들도 운영계에서는 최적화로 인해서 잘 돌아가게된다. +- 네트워크 연결 -> I/O -> DB 등의 애플리케이션 외부 요인 다음으로 병목을 일으킬 공산이 가장 큰 부분이 바로 `코드 설계`입니다. +- 코드의 기본원칙을 알아보자 + - 데이터 저장시 어떤 옵션이 있는지 + - 자바 컬렉션 API가 지원하는 자료구조및 구현 세부를 꿰뚫고 있어야합니다. + - 자료구조에 대한 조회 원리와 수정방법을 정확히 알고 자료구조를 선택해야합니다. + + - 도매인 객체가 무엇인지, 시스템 내부에서 도메인 객체의 수명이 어떠한지 아는지 알아야하고 가비지 수집에 대한 영향을 이해해야합니다. + +## 11.1 컬렉션 최적화 +- 순차 컨테이너: 수치 인덱스로 표기한 특정위치에 객체를 저장한다. (LIST) +- 연관 컨테이너: 객체 자체를 이용해 컬렉션 내부에 저장할 위치 결정 (HASHTABLE) + - 기본 조건: 모든 객체는 hashcode() 와 equals() 메서드 구현이 필요하다. + +## 11.2 List 최적화 +- ArrayList와 LinkedList 두가지 형태로 나타냅니다. + + +### 11.2.1 ArrayList +- ArrayList는 고정 크기 배열에 기반한 리스트입니다. +- ArrayList는 처음에 빈 배열로 시작하고 첫 원소가 추가될 때 10 용량을 할당합니다. +- 초기용량값을 잘 정해서 추가적으로 용량 할당하는 사이클을 제거할 수 있습니다. + +### 11.2.2 LinkedList +- 동적으로 증가하는 리스트 +- 조회가 O(N) + 추가 O(1) + +### 11.2.3 ArrayList vs LinkedList +- 리스트 끝에 원소를 삽입하는 작업은 ArrayList +- LinkedList 모두 일정한 시간이 소요됨 +- 인덱스에 따른 원소 추가는 arrayList 한칸씩 다 땡겨야함 +- 원소 삭제가 빈번하거나 추가가 빈번하면 LinkedList사용 아니면 ArrayList +- 하지만 랜덤 액세스가 잦은 경우 ArrayList 사용 + + +### 11.3 Map 최적화 +- 매핑이란 키와 연관된 값 사이의 관계 ( 연관 배열) +- 과학책에 나오는 hashtable을 구현한 개념입니다. + - hash 키를 가지고 access + - Static Class 로 Node를 가지고 있다. +- 버킷 엔트리를 리스트에 저장 +- 값을 찾으려면 키 해시값을 계산하고 equals() 메서드로 리스트에서 해당 키를 찾는다. +- 동등성 기준으로 찾는 메커니즘으로 키 중복은 허용하지 않는다. +- Hashmap 용량은 현재 생성된 버킷 개수(16개 디폴트) + loadFactor는 버킷 용량을 2배로 증가시키는 한계치 +- 용량을 2배 늘리고 데이터를 다시 배치하는 것을 재해시라고 부릅니다. +- initialCapacity와 loadFactor를 잘 설정해서 자동 재해시를 막는게 중요합니다. +- 순회시에 capacity에 따라 비용이 늘어나므로 잘 생각 해야합니다. +- 트리화도 성능에 영향을 주는 요인입니다. Treeify Threshold에 다 다르면 TreeNode로 바꿉니다. -> TreeMap처럼 작동 + - 순회에 유리하지만 노드가 2배더 커서 공간을 더 차지함 +- LinkedHashMap + - 삽입 순서이지만 액세스 순서로 바꿀 수 있습니다. 순서가 중요한 코드에서 많이 쓰이지만, Treemap과 같은 비용이 들지 않습니다. + +### 11.3.2 TreeMap +- TreeMap은 레드-블랙 트리를 구현한 Map 입니다. 트리 균형을 한쪽으로 치우치는 현상을 방지한 트리 +- TreeMap은 다양한 키가 필요할 때 유용하며 서브맵에 신속히 접근한다 +- get, put, containsKey, remove 모두 log(n) +- 스트림이나 람다로 Map을 처리해야할 때, TreeMap을 사용하게 됩니다. +- 대부분은 HashMap이면 충분하다. + +## 11.4 Set 최적화 +- 세종류 set이 있고 hashset은 hashmap으로 구현됨 +- Map과 동일함 -> 삽입,삭제, CONTAINS, O(1) 순회는 O(intialCapacity 와 loadFactor에 따라다름) + +## 11.5 도메인 객체 +- jmap -histo 로 자바 힙상태를 살펴 볼수 있고 도메인 객체의 메모리 누수 현상을 진단 할 수 있습니다. +- 가장 흔히 할당되는 자료 구조는 string, char, byte, collections +- jmap에서 누수되는 데이터는 비정상적으로 비대한 데이터 셋으로 나타냅니다. +- 애플리케이션에 속한 도메인 객체가 jmap 결과치의 상위 30위 정도 든다면 메모리 누수가 발생한 신호라고 볼수 있습니다. +- 알맞은 크기의 작업세트를 배정해야합니다 + +## 11.6 종료화 안하기 +- finalize 메서드는 리소스를 관리하려고 만든 장치 하지만 파일 핸들을 열고 close() 함수를 호출하는 일을 잊어버리는 경우도 많다. +- 리소스 소유권을 객체 수명과 단단히 묶ㄴ는것이 타당합니다. +- 가비지 수집 중 즉시 회수되지 않고 종료화 대상으로 등록된 객체는 다음과 같이 수명이 연장됩니다. + - 종료화가 가능한 객체는 큐로 이동한다. + - 별도의 종료화 스레드가 큐를 비우고 객체마다 finalize 메서드를 실행한다. + - finalize() 가 종료되면 객체는 다음 사이클에 진짜 수집될 준비를 마친다. +- 종료화된 객체는 한번의 gc사이클 더 보존됩니다. +- 자바 메모리 관리 서브시스템은 할당할 가용 메모리가 부족하면 가비지 수집기를 실행시키게 됩니다. +- 객체가 수집될 때에만 실행되는 finalize() 메서드 호출 시점 불분명 +- 항상 리소스가 고갈될 위험에 노출됨 + +## 11.6.3 TRY-WITH-RESOURCES +- TRY FINALLY BLOCK을 생성 + - AUTOCLOSEABLE INTERFACE 구현된 객체만 리소스로 생성가능 + - NULL이 아니라면 CLOSE 처리(예외 발생 여부와 상관없이 실행됨) + - JDBC에서 사용함 + +## 11.7 메서드 핸들 +- invokedynamic 명령어는 자바 7부터 선보인 기능 +- invokedynamic 호출부가 실제로 어느 메서드를 호출할지 런타임 전까지 결정 X +- 핵심은 메서드 핸들 -> invokedynamic 호출부에 의해 호출되는 메서드를 나타낸 객체 +- 리플렉션과 개념이 어느정도 빗비슷함 +- method handle은 실행가능한 메서드의 레퍼런스를 직접 반영 가능하다. +- 하부 메서드를 실행 가능한 메서드가 메서드 핸들 객체에 내장 되어 있다. diff --git "a/ch12/\353\260\225\354\213\234\354\260\275.md" "b/ch12/\353\260\225\354\213\234\354\260\275.md" new file mode 100644 index 0000000..0253d92 --- /dev/null +++ "b/ch12/\353\260\225\354\213\234\354\260\275.md" @@ -0,0 +1,184 @@ +# 12.동시성능기법 +- 순차형 코딩 해왔다 +- 무어의 법칙으로 프로그램 빨라져왔다. +- 멀티 코어 프로세서가 일반화됐다. +- jit 컴파일 철머 여러 프로세스 코어를 활용할 수 있는 VM스레드를 활용할 수 있다. +- Java Concurrency In Practice 책을 참고해라 + +## 12.1 병렬성 +- 태스크가 병렬 실행 가능 할때, 반드시 순차 실행(S)해야하는 파트로 구성됨 +- T(N) = S(순차 실행 시간) + (T(총실행 시간) - S) / N +- 프로세서 무한 증가 시켜도 총 소요 시간은 순차 작업 시간 이상 줄일수 없다. +- 아무리 코어 늘려도 20배 이상 속도 증가 X +- 데이터 공유 없이 동시처리하면 속도 개선 +- 데이터 공유 필요하면 일부 태스크 순차 처리 필요하고 오버헤드 생김 +- 상태 공유 하게 되면 정교한 보호 및 제어 장치가 필요합니다. -> JMM이라는 메모리 보증 세트 제공 + +## 12.1.1 자바 동시성 기초 +- 카운터 증가시키는 코드 +- 락으로 적절히 보호되지 않은 필드 수정 결과 소실 됨 + - OS 스레드 순서 보장 전혀되지 않음 + - 각 스레드는 메서드 개별 진입시 전용 평가 스택을 소유 -> 필드 작업 간섭 (객체의 필드는 힙에 위치 및 모든 쓰레드가 공유) + - VOLATILE 설정하면 연산할 수 있어 보이지만 업데이트 소실 문제는 계속 일어난다. + - Synchronized를 사용하면 업데이트 제어할 수 있다. + - 더느려질수도;; + - jvm의 저수준 메모리 모델과 동시 애플리케이션의 실제적 기법을 익혀야함 +## 12.2 JMM의 이해 +다음의 답을 해준다. +- 두 코어가 같은 데이터를 액세스하면 어떻게 되는가? +- 언제 두코어가 같은 데이터를 바라본다고 장담할 수 있는가? +- 메모리 캐시는 두질문의 답에 어떤 영향을 미치는가? + +- JMM은 순서에 관한 보장 + 여러 스레드에 대한 업데이트 가시성 보장을 약속해준다 +- 두가지 메모리 모델이 있다 + - 강한 메모리 모델 + - 전체 코어가 항상 같은 값 바라봄 + - 약한 메모리 모델 + - 코어마다 다른 값을 바라 볼수 있고 특별한 캐시 규칙이 있다. +- 강한 메모리 모델은 메모리를 후기록하는것이나 마찬가지이다. + - 캐시 무효화 알림이 메모리 버스를 잠식하고 메인 메모리 전송률은 급락할것이다. + - 멀티코어 체제에 안맞는다. +- JMM은 약한 메모리 모델이다. + - HAPPENS BEFORE + - 한 이벤트는 무조건 다른 이벤트보다 먼저 발생한다. + - Synchronizes with + - 이벤트가 객체 뷰를 메인 메모리와 동기화 시킨다. + - As-If-Serial (순차적인것처럼) + - 실행 스레드 밖에서는 명령어가 순차 실행된거 처럼 보인다. + - Release Before Acquire(획득하기전에 해제) + - 한 스레드에 걸린 락을 다른 스레드가 획득하기전에 해제한다. + - 동기화를 통한 락킹은 가변상태를 공유하는 가장 중요한 기법입니다. + - 이 때문에 jvm에는 저수준 메모리 액세스를 감싸놓은 구현 코드가 상당히 많습니다. + + - synchronized 키워드가 나타내는 의미는 분명 + - 모니터를 장악한 스레드의 로컬 뷰가 메인 메모리와 동기화 + - JMM은 동기화되지 않은 액세스에 대해서 할말이 없습니다. + - 한 스레드가 변경한 부분을 다른 스레드가 언제 바라볼 수 있는지 전혀 보장하지 않음 + - 해당 보장이 필요하면 쓰기 애세스를 동기화 블록으로 감싸서 메모리에 후기록 해야됨 + - 동기화 블록은 스레드가 반드시 동기를 맞춰야 할 접점에 해당, 동기화 메서드/블록이 시작되기 전에 완료되어야할 블록으로 정의 + - synchronized 락의 한계점 + - 락이 걸린 객체에서 일어나는 동기화 작업 모두 균등하게 취급 + - 락 획득 해제는 반드시 메서드 수준이나 메서드 내부의 동기화 블록에서 이뤄져야한다. + - 락을 얻지 못한 스레드는 블로킹 된다 + - 읽기 작업에 synhronized를 걸지 않으면 다른 스레드에서 업데이트 내용을 바라볼 수 없다. + - gee.cs.oswego.edu/dl/jmm/cookbook.html 확인 + - 자바 메모리 모델 유형 자세히 살펴보기 -> jmm 추가 해설 제공 +## 12.3 동시성 라이브러리 구축 +- java util concurrent 패키지는 멀티 스레드 애플리케이션을 자바로 더 쉽게 개발하게 도와줌 +- 이 라이브러리 구성요소들 + - 락, 세마포어 (semaphore) + - 아토믹스 (atomics) + - 블로킹 큐 + - 래치 + - 실행자 (executor) +- ## 12.3.1 Unsafe + +JVM의 표준 로직을 무너뜨리는 Unsafe는 모든 프레임워크의 구현 핵심부를 차지합니다. + +- 객체를 할당하지만 생성자 실행 x +- 원메모리에 액세스하고 포인터 수준의 연산을 한다. +- 프로세스별 하드웨어 특성을 이용한다. + +고프레임워크 기능을 구현 가능 + +1. 신속한 직렬화 +2. 스레드 안전한 네이티브 메모리 액세스 +3. 아토믹 메모리 연산 +4. 커스텀 메모리 펜스 +5. 네이티브 코드와 상호작용 +6. 배열 원소에 volatile 하게 액세스 + +## 12.3.2 아토믹스 CAS + +아토믹스는 값을 더하고 복합 연산하고 GET으로 결과값을 반환받습니다. + +아토믹스는 volatile의 확장판입니다. + +상태 의존적 업데이트를 안전하게 수행할 수 있습니다 + +AtomicInteger는 Integer를 상속한 클래스가 아닙니다. + +```jsx +public class AtomicInteger extends Number { + private volatile int value; + + private static final Unsafe unsafe = Unsafe.getUnsafe(); // Unsafe.compareAndSwap 사용 업데이트 + valueOffset = unsafe.objectFieldOffset( + AtomicIntegerExample.class.getDeclaredField("value") + ) + + public final int get() { + return value; + } + + + public final int getAndSet(int newValue){ + return unsafe.getAndSetInt(this, valueOffset, newValue); + } + +``` + +```jsx +Unsafe 코드 + +public final int getAndSetInt(Object o, long offset, int newValue){ + int v; + do { + + v - getIntVolatile(o, offset); + } while (!compareAndSwapInt(o, offset, v, newValue); + return v; +} + +public final native bollean compareAndSwapInt() +``` + +- 아토믹은 락프리해서 데드락 있을 수 없다. +- 비교후 업데이트 하는 작업이 실패할 경우 대비해서 내부적인 재시도 루프 동반 +- 성능을 고려할때, 처리율을 높은 수준을 유지하기 위해 경합 수준 잘 모니터링 해야한다. +- 저수준 하드웨어 명령어에 액세스할 수 있는 Unsafe를 사용하지 않고는 아토믹 클래스가 추구하는 취지를 살릴 길이 없다. + +### 12.3.3 락과 스핀락 + +- 인트린직 락은 유저코드에서 os 호출함 +- 경합 중인 리소스가 짧은 시간동안만 사용할 경우 막대한 오버헤드가 생깁니다. -> OS를 이용해 스레드가 무한정 기다리게됨 + - 블로킹된 스레드에서 락을 손에 넣을 때까지 CPU를 태워가며 계속 재시도하는 편이 효율적입니다. +- 이런 기법을 스핀락이라고 합니다. +- 최신 시스템들은 CAS로 스핀락을 구현합니다. + +--- 아토믹 클래스와 단순 락을 구현하는데 사용하는 저수준 기법 소개 +### 12.4 동시 라이브러리 정리 +- java 5 부터 락 인터페이스가 java.util.concurrent.locks.Lock에 추가됐습니다. +- 인터페이스를 이용하면 인트린직 락보다 더많은 일 가능하다. +- `lock` 기존 방식대로 락을 획득하고 락을 사용할 수 있을때까지 블로킹 +- `new Condition` 락 주위에 조건을 설정해 좀더 유연하게 락 활용 +- `tryLock` 락을 획득하려고 시도 (타임아웃) +- ` unlock` 라긍ㄹ 해제 +- ReentrantLock은 Lock의 주요 구현체로 내부적으로 int 값으로 compareAndSwap을 합니다. + - 경합만없으면 락 획득 과정이 락 프리합니다. +- 스레드가 동일한 락을 다시 획득하는 것을 재진입 락킹이라고 합니다. + - 스레드가 스스로를 블로킹 하는 현상을 방지할 수 있습니다. + - 최신 애플리케이션에서는 재진입 라킹이 적용 되어 있습니다. +- 해당 락은 LockSupport 클래스를 이용하는데 스레드이ㅔ게 퍼밋 발행해줍니다. + - park, unpark를 사용합니다. + +### 12.4.2 읽기/쓰기 락 +- 기존 synchronized 나 ReentrantLock을 이용하면 한가지 락 정책을 따를 수 밖에 없습니다. +- 쓰기와 읽기 스레드에 달려드는 상황에서 블로킹하느라 불필요한 시간이 낭비가 됩니다. +- 이때 ReentrantReadWriteLock 클래스의 ReadLock과 WriteLock을 활용해서 블로킹을 처리할 숭있습니다. + - 동시 컬렉션 정도면 합리적인 추상화로 볼 수 있고 스레드 핫 성능도 훨 씬 좋아집니다. +### 12.4.3 세마포어 +- 세마포어는 DB 접속 객체등 여러 리소스의 액세스를 허용하는 기술을 제공합니다. +- 최대 O개의 객체에 대해서 액세스를 허용한다 로 퍼밋으로 액세스 제어합니다. +- 세마 포어는 비소유 스레드도 해제할 수 있다는 점이 다릅니다. +- 여러 퍼밋을 획득/해제할 수 잇는 능력입니다. +- 공정 모드는 필수 입니다. + +### 12.4.4 동시 컬렉션 +- Map 구현채(ConcurrentHashMap)은 버킷 또는 세그먼트 로 분할된 구조를 활용해서 실질적 성능 개선 효과를 얻음 +- 각 세그먼트 마다 자체 락킹 정책을 걸고 락을 걸지 않은 읽기 스레드는 put, remove 작업을하고 완료된 업데이트는 happens before 에 따라 읽는다. +- 이터레이터는 일종의 스냅샷으로 ConcurrentModifcation 에러가 뜨지 않는다. + +### 12.4.5 래치와 베리어 +- 태스크#1 -> 태스크#2 -> 태스크#3 순서로 진행되는것이 이상적이면 래치를 쓰기에 좋은 경우이다. +-