문제는 Java 11 이상 버전으로 이동해야 하는지 가 아니라언제인지 여부입니다. 향후 몇 년 내에 Java 8은 더 이상 지원되지 않으며 사용자는 Java 11 이상으로 이동해야 합니다. 우리는 Java 11로 이동하는 이점이 있다고 주장하고 팀이 가능한 한 빨리 그렇게하도록 장려합니다.
Java 8 이후 새로운 기능이 추가되었으며 향상된 기능이 적용되었습니다. API에 눈에 띄는 추가 및 수정 사항이 있으며 시작, 성능 및 메모리 사용량을 향상시키는 향상된 기능이 있습니다.
Java 11로 전환
Java 11로의 전환은 단계별로 수행할 수 있습니다. 코드에서 Java 모듈을 사용하여 Java 11에서 실행할 필요는 없습니다 . Java 11은 JDK 8을 사용하여 개발 및 빌드된 코드를 실행하는 데 사용할 수 있습니다. 그러나 주로 사용되지 않는 API, 클래스 로더 및 리플렉션과 관련된 몇 가지 잠재적인 문제가 있습니다.
Microsoft Java 엔지니어링 그룹에는 Java 8에서 Java 11로 전환하는 가이드가 있습니다. Java 플랫폼, Standard Edition Oracle JDK 9 마이그레이션 가이드 및 모듈 시스템의 상태: 호환성 및 마이그레이션은 다른 유용한 가이드입니다.
Java 8과 11 간의 개략적인 변경
이 섹션에서는 Java 버전 9 [1], 10 [2] 및 11 [3]의 모든 변경 내용을 열거하지는 않습니다. 성능, 진단 및 생산성에 영향을 주는 변경 내용이 강조 표시됩니다.
모듈 [4]
모듈은 클래스 경로에서 실행되는 대규모 애플리케이션에서 관리하기 어려운 구성 및 캡슐화 문제를 해결합니다. 모듈은 Java 클래스 및 인터페이스 및 관련 리소스의 자체 설명 컬렉션입니다.
모듈을 사용하면 애플리케이션에 필요한 구성 요소만 포함하는 런타임 구성을 사용자 지정할 수 있습니다. 이 사용자 지정은 더 작은 공간을 만들고 jlink를 사용하여 애플리케이션을 배포를 위한 사용자 지정 런타임에 정적으로 연결할 수 있도록 합니다. 이 더 작은 공간은 마이크로 서비스 아키텍처에서 특히 유용할 수 있습니다.
내부적으로 JVM은 클래스 로드를 보다 효율적으로 만드는 방식으로 모듈을 활용할 수 있습니다. 그 결과 더 작고 가벼우며 더 빠르게 시작할 수 있는 런타임이 생성됩니다. 모듈이 클래스에 필요한 구성 요소를 인코딩하기 때문에 JVM에서 애플리케이션 성능을 향상시키는 데 사용하는 최적화 기술이 더 효과적일 수 있습니다.
프로그래머의 경우 모듈은 모듈이 내보내는 패키지와 필요한 구성 요소에 대한 명시적 선언을 요구하고 반사 액세스를 제한하여 강력한 캡슐화를 적용하는 데 도움이 됩니다. 이 수준의 캡슐화는 애플리케이션을 보다 안전하고 쉽게 유지 관리할 수 있도록 합니다.
애플리케이션은 클래스 경로를 계속 사용할 수 있으며 Java 11에서 실행하기 위한 필수 조건으로 모듈로 전환할 필요가 없습니다.
프로파일링 및 진단
Java 플라이트 기록기 [5]
JFR(Java Flight Recorder)은 실행 중인 Java 애플리케이션에서 진단 및 프로파일링 데이터를 수집합니다. JFR은 실행 중인 Java 애플리케이션에 거의 영향을 주지 않습니다. 그런 다음 JMC(Java Mission Control) 및 기타 도구를 사용하여 수집된 데이터를 분석할 수 있습니다. JFR 및 JMC는 Java 8의 상용 기능인 반면, 둘 다 Java 11의 오픈 소스입니다.
Java Mission Control [6]
JMC(Java Mission Control)는 JFR(Java Flight Recorder)에서 수집한 데이터의 그래픽 표시를 제공하며 Java 11의 오픈 소스입니다. JMC는 실행 중인 애플리케이션에 대한 일반적인 정보 외에도 사용자가 데이터를 드릴다운할 수 있도록 합니다. JFR 및 JMC를 사용하여 메모리 누수, GC 오버헤드, 핫 메서드, 스레드 병목 상태 및 차단 I/O와 같은 런타임 문제를 진단할 수 있습니다.
통합 로깅 [7]
Java 11에는 JVM의 모든 구성 요소에 대한 공통 로깅 시스템이 있습니다. 이 통합 로깅 시스템을 사용하면 사용자가 기록할 구성 요소와 수준을 정의할 수 있습니다. 이 세분화된 로깅은 JVM 크래시에 대한 근본 원인 분석을 수행하고 프로덕션 환경에서 성능 문제를 진단하는 데 유용합니다.
오버헤드가 낮은 힙 프로파일링 [8]
Java 힙 할당 샘플링을 위해 JVMTI(Java Virtual Machine Tool Interface)에 새 API가 추가되었습니다. 샘플링은 오버헤드가 낮으며 지속적으로 사용하도록 설정할 수 있습니다. JFR(Java Flight Recorder)을 사용하여 힙 할당을 모니터링할 수 있지만 JFR의 샘플링 방법은 할당에서만 작동합니다. JFR 구현에서 할당을 놓칠 수도 있습니다. 반면 Java 11의 힙 샘플링은 라이브 개체와 데드 개체 모두에 대한 정보를 제공할 수 있습니다.
APM(애플리케이션 성능 모니터링) 공급업체는 이 새로운 기능을 활용하기 시작했으며 Java 엔지니어링 그룹은 Azure 성능 모니터링 도구의 잠재적 사용을 조사하고 있습니다.
StackWalker [9]
현재 스레드에 대한 스택의 스냅샷 가져오기는 로깅할 때 자주 사용됩니다. 문제는 기록할 스택 추적의 양과 스택 추적을 기록할지 여부입니다. 예를 들어 메서드의 특정 예외에 대해서만 스택 추적을 볼 수 있습니다. StackWalker 클래스(Java 9에 추가됨)는 스택의 스냅샷을 제공하고 프로그래머에게 스택 추적을 사용하는 방법을 세밀하게 제어하는 메서드를 제공합니다.
가비지 수집 [10]
Java 11에서 사용할 수 있는 가비지 수집기는 직렬, 병렬, 가비지 우선 및 엡실론입니다. Java 11의 기본 가비지 수집기는 G1GC(Garbage First Garbage Collector, 가비지 우선 수집기)입니다.
여기에 완전성을 위해 세 명의 다른 수집가가 언급되어 있습니다. ZGC(Z 가비지 수집기)는 일시 중지 시간을 10ms 미만으로 유지하려고 시도하는 동시 대기 시간이 짧은 수집기입니다. ZGC는 Java 11에서 실험적 기능으로 사용할 수 있습니다. Shenandoah 수집기는 실행 중인 Java 프로그램과 동시에 더 많은 가비지 수집을 수행하여 GC 일시 중지 시간을 줄이는 낮은 일시 중지 수집기입니다. Shenandoah는 Java 12의 실험적 기능이지만 Java 11에 대한 백포트가 있습니다. CMS(동시 표시 및 스윕 수집기)는 사용할 수 있지만 Java 9 이후 사용되지 않습니다.
JVM은 평균 사용 사례에 대한 GC 기본값을 설정합니다. 종종 이러한 기본값 및 기타 GC 설정은 애플리케이션의 요구 사항에 따라 최적의 처리량 또는 대기 시간을 조정해야 합니다. GC를 올바르게 튜닝하려면 Microsoft Java 엔지니어링 그룹이 제공하는 전문 지식인 GC에 대한 깊은 지식이 필요합니다.
G1GC
Java 11의 기본 가비지 수집기는 G1GC(G1 가비지 수집기)입니다. G1GC의 목표는 대기 시간과 처리량 사이의 균형을 맞추는 것입니다. G1 가비지 수집기는 높은 확률로 일시 중지 시간 목표를 충족하여 높은 처리량을 달성하려고 합니다. G1GC는 전체 컬렉션을 방지하도록 설계되었지만 동시 컬렉션이 메모리를 충분히 빨리 회수할 수 없는 경우 전체 GC가 대체됩니다. 전체 GC는 젊고 혼합된 컬렉션과 동일한 수의 병렬 작업자 스레드를 사용합니다.
병렬 GC
병렬 수집기는 Java 8의 기본 수집기입니다. 병렬 GC는 여러 스레드를 사용하여 가비지 수집 속도를 높일 수 있는 처리량 수집기입니다.
엡실론 [11]
Epsilon 가비지 수집기는 할당을 처리하지만 메모리를 회수하지는 않습니다. 힙이 소진되면 JVM이 종료됩니다. Epsilon은 단기 서비스 및 가비지 없는 것으로 알려진 애플리케이션에 유용합니다.
Docker 컨테이너에 대한 개선 사항 [12]
Java 10 이전에는 컨테이너에 설정된 메모리 및 CPU 제약 조건이 JVM에서 인식되지 않았습니다. 예를 들어 Java 8에서 JVM은 기본 호스트의 실제 메모리 중 최대 힙 크기를 1/4로 기본값으로 설정합니다. Java 10부터 JVM은 컨테이너 제어 그룹(cgroups)에서 설정한 제약 조건을 사용하여 메모리 및 CPU 제한을 설정합니다(아래 참고 참조). 예를 들어 기본 최대 힙 크기는 컨테이너 메모리 제한의 1/4입니다(예: -m2G경우 500MB).
또한 Java 힙에 사용할 시스템 메모리 양을 세분화하여 Docker 컨테이너 사용자에게 제어할 수 있도록 JVM 옵션도 추가되었습니다.
이 지원은 기본적으로 사용하도록 설정되며 Linux 기반 플랫폼에서만 사용할 수 있습니다.
비고
대부분의 cgroup 사용 작업은 jdk8u191 기준으로 Java 8로 백포크되었습니다. 추가 개선 사항은 반드시 8로 백포칭되지 않을 수 있습니다.
다중 릴리스 jar 파일 [13]
Java 11에서는 여러 Java 릴리스 관련 클래스 파일이 포함된 jar 파일을 만들 수 있습니다. 다중 릴리스 jar 파일을 사용하면 라이브러리 개발자가 여러 버전의 jar 파일을 발송하지 않고도 여러 버전의 Java를 지원할 수 있습니다. 이러한 라이브러리의 소비자의 경우 다중 릴리스 jar 파일은 특정 jar 파일을 특정 런타임 대상과 일치해야 하는 문제를 해결합니다.
기타 성능 향상
JVM에 대한 다음 변경 내용은 성능에 직접적인 영향을 줍니다.
JEP 197: 분할된 코드 캐시 [14] - 코드 캐시를 고유 세그먼트로 나눕니다. 이 구분은 JVM 메모리 공간을 더 잘 제어하고, 컴파일된 메서드의 검색 시간을 단축하고, 코드 캐시의 조각화를 크게 줄이고, 성능을 향상시킵니다.
JEP 254: 압축 문자열 [15] - 문자 인코딩에 따라 문자열의 내부 표현을 문자당 2바이트에서 문자당 1바이트 또는 2바이트로 변경합니다. 대부분의 문자열에는 ISO-8859-1/Latin-1 문자가 포함되어 있으므로 이 변경은 문자열을 저장하는 데 필요한 공간의 양을 효과적으로 절반으로 줄입니다.
JEP 310: 애플리케이션 Class-Data 공유 [16] - Class-Data 공유는 런타임에 보관된 클래스를 메모리 매핑할 수 있도록 하여 시작 시간을 줄입니다. 애플리케이션 Class-Data 공유는 애플리케이션 클래스를 CDS 보관에 배치할 수 있도록 하여 클래스 데이터 공유를 확장합니다. 여러 JVM이 동일한 보관 파일을 공유하면 메모리가 저장되고 전체 시스템 응답 시간이 향상됩니다.
JEP 312: 스레드 로컬 핸드셰이크 [17] - 전역 VM 세이프포인트를 사용하지 않고도 스레드에서 콜백을 실행할 수 있으며, 이는 전역 세이프포인트 수를 줄여 VM의 지연 시간을 단축합니다.
컴파일러 스레드 지연 할당 [18] - 계층화된 컴파일 모드에서 VM은 많은 수의 컴파일러 스레드를 시작합니다. 이 모드는 CPU가 많은 시스템의 기본값입니다. 이러한 스레드는 사용 가능한 메모리 또는 컴파일 요청 수에 관계없이 생성됩니다. 스레드는 유휴 상태일 때(거의 항상) 메모리를 소비하므로 리소스를 비효율적으로 사용할 수 있습니다. 이 문제를 해결하기 위해 시작 중에 각 형식의 컴파일러 스레드를 하나만 시작하도록 구현이 변경되었습니다. 추가 스레드를 시작하고 사용하지 않는 스레드를 종료하면 동적으로 처리됩니다.
핵심 라이브러리에 대한 다음 변경 내용은 새 코드 또는 수정된 코드의 성능에 영향을 줍니다.
JEP 193: 변수 핸들 [19] - 개체 필드 및 배열 요소에 대해 다양한 java.util.concurrent.atomic 및 sun.misc.Unsafe 연산의 동등성을 호출하고, 메모리 순서를 세밀하게 제어하는 표준 펜스 작업 집합과 참조된 객체가 강력하게 접근 가능하도록 보장하는 표준 접근성-펜스 작업을 정의합니다.
JEP 269: 컬렉션에 대한 편의 팩터리 메서드 [20] - 소수의 요소가 있는 컬렉션 및 맵의 인스턴스를 편리하게 만들 수 있도록 라이브러리 API를 정의합니다. 압축되고 수정할 수 없는 컬렉션 인스턴스를 만드는 컬렉션 인터페이스의 정적 팩터리 메서드입니다. 이러한 인스턴스는 본질적으로 더 효율적입니다. API는 압축적으로 표현되고 래퍼 클래스가 없는 컬렉션을 만듭니다.
JEP 285: Spin-Wait 힌트 [21] - Java가 런타임 시스템에 스핀 루프에 있음을 암시할 수 있는 API를 제공합니다. 특정 하드웨어 플랫폼은 스레드가 대기 중 상태라는 소프트웨어 표시를 통해 이점을 얻을 수 있습니다.
JEP 321: HTTP 클라이언트(표준) [22]- HTTP/2 및 WebSocket을 구현하고 레거시 HttpURLConnection API를 대체할 수 있는 새 HTTP 클라이언트 API를 제공합니다.
References
[1] Oracle Corporation, "Java Development Kit 9 릴리스 정보"(온라인). 사용 가능: https://www.oracle.com/technetwork/java/javase/9u-relnotes-3704429.html. (2019년 11월 13일 액세스)
[2] Oracle Corporation, "Java Development Kit 10 릴리스 정보"(온라인). 사용 가능: https://www.oracle.com/technetwork/java/javase/10u-relnotes-4108739.html. (2019년 11월 13일 액세스)
[3] Oracle Corporation, "Java Development Kit 11 릴리스 정보"(온라인). 사용 가능: https://www.oracle.com/technetwork/java/javase/11u-relnotes-5093844.html. (2019년 11월 13일 액세스)
[4] Oracle Corporation, "Project Jigsaw" 2017년 9월 22일. (온라인). 사용 가능: http://openjdk.java.net/projects/jigsaw/. (2019년 11월 13일 액세스)
[5] Oracle Corporation, "JEP 328: Flight Recorder," 2018년 9월 9일. (온라인). 사용 가능: http://openjdk.java.net/jeps/328. (2019년 11월 13일 액세스)
[6] Oracle Corporation, "Mission Control" 2019년 4월 25일. (온라인). 사용 가능: https://wiki.openjdk.java.net/display/jmc/Main. (2019년 11월 13일 액세스)
[7] 오라클 코퍼레이션, "JEP 158: 통합 JVM 로깅," 2019년 2월 14일. (온라인). 사용 가능: http://openjdk.java.net/jeps/158. (2019년 11월 13일 액세스)
[8] Oracle Corporation, "JEP 331: 저부하 힙 프로파일링," 2018년 9월 5일. (온라인). 사용 가능: http://openjdk.java.net/jeps/331. (2019년 11월 13일 액세스)
[9] Oracle Corporation, "JEP 259: Stack-Walking API," 2017년 7월 18일. (온라인). 사용 가능: http://openjdk.java.net/jeps/259. (2019년 11월 13일 액세스)
[10] Oracle Corporation, "JEP 248: G1을 기본 가비지 수집기로 만들기", 2017년 9월 12일. (온라인). 사용 가능: http://openjdk.java.net/jeps/248. (2019년 11월 13일 액세스)
[11] 오라클 코퍼레이션, "JEP 318: 엡실론: 무작동(빈 작업) 가비지 컬렉터," 2018년 9월 24일. (온라인). 사용 가능: http://openjdk.java.net/jeps/318. (2019년 11월 13일 액세스)
[12] Oracle Corporation, "JDK-8146115: Docker 컨테이너 검색 및 리소스 구성 사용 개선" 2019년 9월 16일. (온라인). 사용 가능: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115. (2019년 11월 13일 액세스)
[13] Oracle Corporation, "JEP 238: 다중 릴리스 JAR 파일" 2017년 6월 22일. (온라인). 사용 가능: http://openjdk.java.net/jeps/238. (2019년 11월 13일 액세스)
[14] Oracle Corporation, "JEP 197: Segmented Code Cache," April 28, 2017. (온라인). 사용 가능: http://openjdk.java.net/jeps/197. (2019년 11월 13일 액세스)
[15] Oracle Corporation, "JEP 254: Compact Strings," May 18, 2019. (온라인). 사용 가능: http://openjdk.java.net/jeps/254. (2019년 11월 13일 액세스)
[16] Oracle Corporation, "JEP 310: Application Class-Data Sharing," 2018년 8월 17일. (온라인). 사용 가능: https://openjdk.java.net/jeps/310. (2019년 11월 13일 액세스)
[17] Oracle Corporation, "JEP 312: Thread-Local 핸드셰이크," 2019년 8월 21일. (온라인). 사용 가능: https://openjdk.java.net/jeps/312. (2019년 11월 13일 액세스)
[18] Oracle Corporation, "JDK-8198756: 컴파일러 스레드 지연 할당" 2018년 10월 29일. (온라인). 사용 가능: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8198756. (2019년 11월 13일 액세스)
[19] Oracle Corporation, "JEP 193: Variable Handles," August 17, 2017. (온라인). 사용 가능: https://openjdk.java.net/jeps/193. (2019년 11월 13일 액세스)
[20] Oracle Corporation, "JEP 269: 컬렉션에 대한 편리한 팩터리 메서드," 2017년 6월 26일. (온라인). 사용 가능: https://openjdk.java.net/jeps/269. (2019년 11월 13일 액세스)
[21] Oracle Corporation, "JEP 285: Spin-Wait Hints," August 20, 2017. (온라인). 사용 가능: https://openjdk.java.net/jeps/285. (2019년 11월 13일 액세스)
[22] Oracle Corporation, "JEP 321: HTTP 클라이언트(표준)", 2018년 9월 27일. (온라인). 사용 가능: https://openjdk.java.net/jeps/321. (2019년 11월 13일 액세스)