Post

Java 11 이상으로 이동해야 하는 이유

1. Java 11 이상으로 이동해야 하는 이유

문제는 Java 11 이상 버전으로 이동해야 하는지 여부가 아니라시기이다. 향후 몇 년 내에 Java 8은 더 이상 지원되지 않으며 사용자는 Java 11 이상으로 이동해야 한다. Java 11로 전환하면 이점이 있으며 팀에서 최대한 빨리 전환하도록 권장한다.

Java 8 이후 새 기능이 추가되었고 기능이 향상되었다. API에 대한 눈에 띄는 추가 및 수정 사항이 있으며, 시작, 성능 및 메모리 사용을 개선하는 향상된 기능이 있다.

java-version

2. Java 11로 전환

Java 11로 전환할 때 단계별 방식을 사용할 수 있다. Java 모듈을 사용하여 Java 11에서 코드를 실행할 필요가 없다. Java 11은 JDK 8을 사용하여 개발되고 빌드된 코드를 실행하는데 사용할 수 있다. 그러나 주로 사용되지 않는 API, 클래스 로더 및 리플렉션과 관련하여 몇 가지 잠재적인 문제가 있다.

3. Java 8과 11 간의 대략적인 변경 내용

1) 모듈

모듈은 classpath에서 실행되는 대규모 애플리케이션에서 관리하기 어려운 구성 및 캡슐화 문제를 해결한다. 모듈은 Java 클래스와 인터페이스 및 관련 리소스의 자동 기술 컬렉션이다.

모듈을 통해 애플리케이션에 필요한 구성 요소만 포함하는 런타임 구성을 사용자 지정할 수 있다. 이 사용자 지정은 메모리 공간을 더 적게 사용하며 애플리케이션이 jlink를 사용하여 배포용 사용자 지정 런타임에 정적으로 연결될 수 있게 해준다. 메모리 공간을 적게 사용하면 특히 마이크로서비스 아키텍처에서 유용할 수 있다.

내부적으로 JVM은 모듈을 활용하여 클래스 로딩을 보다 효율적으로 만들 수 있다. 그 결과 런타임이 더 작아지고, 더 가벼워져서 더 빠르게 시작할 수 있다. 모듈은 클래스에 필요한 구성 요소를 인코딩하기 때문에 JVM에서 애플리케이션 성능을 개선하기 위해 사용하는 최적화 기법의 효과가 더 좋아질 수 있다.

프로그래머의 경우 모듈은 모듈이 내보내는 패키지와 필요한 구성 요소를 명시적으로 선언하고 반사적 액세스를 제한하여 강력한 캡슐화를 적용하는데 도움이 된다. 이 캡슐화 수준을 사용하면 애플리케이션을 더 안전하고 쉽게 유지 관리할 수 있다.

애플리케이션은 classpath를 계속 사용할 수 있으며 Java 11에서 실행하기 위한 필수 요건인 모듈 전환이 필요 없다.

2) 프로파일링 및 진단

  • Java 플라이트 레코더 JFR(Java Flight Recorder)은 실행 중인 Java 애플리케이션에서 진단 및 프로파일링 데이터를 수집한다. JFR은 실행 중인 Java 애플리케이션에 거의 영향을 주지 않는다. 따라서 수집된 데이터를 JMC(Java Mission Control) 및 기타 도구를 사용하여 분석할 수 있다. JFR 및 JMC는 Java 8에서 상용 기능이지만 Java 11에서는 둘 다 오픈 소스이다.

3) Java Mission Control

JMC(Java Mission Control)는 JFR(Java Flight Recorder)에서 수집한 데이터의 그래픽 표시를 제공하며 Java 11에서 오픈 소스이다. JMC는 실행 중인 애플리케이션에 대한 일반적인 정보 외에도 사용자가 데이터를 드릴다운할 수 있도록 한다. JFR 및 JMC를 사용하면 메모리 누수, GC 오버헤드, 핫 메서드, 스레드 병목 상태 및 I/O 블로킹과 같은 런타임 문제를 진단할 수 있다.

4) 통합 로깅

Java 11에는 JVM의 모든 구성 요소에 대한 일반적인 로깅 시스템이 있다. 이 통합 로깅 시스템을 통해 사용자는 무슨 구성 요소를 어느 수준까지 로깅할지 정의할 수 있다. 이 세분화된 로깅은 JVM 충돌에 대한 근본 원인 분석을 수행하고 프로덕션 환경에서 성능 문제를 진단하는데 유용하다.

5) 오버헤드가 낮은 힙 프로파일링

Java 힙 할당을 샘플링하는데 사용할 수 있는 새 API가 JVMTI(Java Virtual Machine Tool Interface)에 추가되었다. 이 샘플링은 오버헤드가 적고 지속적으로 사용하도록 설정할 수 있다. JFR(Java Flight Recorder)을 사용하여 힙 할당을 모니터링할 수 있지만 JFR의 샘플링 방법은 할당에서만 작동한다. JFR 구현에서 할당이 누락될 수도 있다. 반면 Java 11의 힙 샘플링은 라이브 개체와 데드 개체 모두에 대한 정보를 제공할 수 있다.

6) StackWalker

현재 스레드의 스택에 대한 스냅샷 가져오기는 로깅할 때 주로 사용된다. 문제는 로깅할 스택 추적의 양과 스택 추적의 로깅 여부이다. 예를 들어 메서드의 특정 예외에 대한 스택 추적만 보려는 경우가 있을 수 있다. StackWalker 클래스(Java 9에서 추가됨)는 스택의 스냅샷을 제공하고, 프로그래머가 스택 추적을 사용하는 방법을 세부적으로 제어할 수 있는 메서드를 제공한다.

4. 가비지 수집

Java 11에서 사용할 수 있는 가비지 수집기는 직렬, 병렬, 가비지 우선 및 엡실론이다. Java 11의 기본 가비지 수집기는 G1GC(가비지 우선 가비지 수집기)이다.

Z 가비지 수집기(ZGC)는 일시 중지 시간을 10ms 미만으로 유지하려고 하는 대기 시간이 짧은 동시 수집기이다. ZGC는 Java 11에서 실험적 기능으로 사용할 수 있다. Shenandoah 수집기는 실행 중인 Java 프로그램과 동시에 더 많은 가비지 수집을 수행하여 GC 일시 중지 시간을 줄이는 일시 중지 시간이 짧은 수집기이다. Shenandoah는 Java 12의 실험적 기능이지만 Java 11에 대한 백포트가 있다. CMS(Concurrent Mark and Sweep) 수집기는 사용할 수 있지만 Java 9 이후에는 사용되지 않는다.

JVM은 평균 사용 사례에서 GC를 기본값으로 설정한다. 이러한 기본값 및 기타 GC 설정은 애플리케이션의 요구 사항에 따라 최적의 처리량 또는 대기 시간에 맞게 조정해야 하는 경우가 많다.

1) G1GC

Java 11의 기본 가비지 수집기는 G1GC(G1 가비지 수집기)이다. G1GC의 목표는 대기 시간과 처리량 간의 균형을 유지하는 것이다. G1 가비지 수집기는 높은 확률의 일시 중지 시간 목표를 충족하여 높은 처리량을 달성하려고 시도한다. G1GC는 전체 컬렉션을 방지하도록 설계되었지만 동시 컬렉션이 메모리를 충분히 빨리 회수할 수 없는 경우 전체 GC 대체가 발생한다. 전체 GC는 젊고 혼합된 컬렉션과 동일한 수의 병렬 작업자 스레드를 사용한다.

2) 병렬 GC

병렬 수집기는 Java 8의 기본 수집기이다. 병렬 GC는 여러 스레드를 사용하여 가비지 수집 속도를 높이는 처리량 수집기이다.

3) 엡실론

엡실론 가비지 수집기는 할당을 처리하지만 메모리를 회수하지는 않는다. 힙이 소진되면 JVM이 종료된다. 엡실론은 수명이 짧은 서비스와 가비지를 사용하지 않는 것으로 알려진 애플리케이션에 유용하다.

5. Docker 컨테이너에 대한 개선 사항

Java 10 이전에 컨테이너에 설정된 메모리 및 CPU 제약 조건은 JVM에서 인식되지 않았다. 예를 들어 Java 8에서 JVM은 최대 힙 크기의 기본값을 기본 호스트의 실제 메모리의 ¼로 설정한다. Java 10부터 JVM은 컨테이너 제어 그룹(cgroup)에 의해 설정된 제약 조건을 사용하여 메모리 및 CPU 제한을 설정한다. 예를 들어 기본 최대 힙 크기는 컨테이너의 메모리 제한의 ¼이다(예: -m2G의 경우 500MB).

Docker 컨테이너 사용자가 Java 힙에 사용되는 시스템 메모리 양을 세부적으로 제어할 수 있도록 JVM 옵션도 추가되었다.

이 지원은 기본적으로 사용하도록 설정되어 있으며 Linux 기반 플랫폼에서만 사용할 수 있다.

대부분의 cgroup 사용 작업은 jdk8u191부터 Java 8로 백포팅되었다. 추가 향상 기능을 8로 백포팅할 필요는 없다.

1) 다중 릴리스 jar 파일

Java 11에서 클래스 파일의 여러 Java 릴리스별 버전을 포함하는 jar 파일을 만들 수 있다. 라이브러리 개발자는 다중 릴리스 jar 파일을 통해 여러 버전의 jar 파일을 제공하지 않고도 여러 버전의 Java를 지원할 수 있다. 이러한 라이브러리의 소비자를 위해 다중 릴리스 jar 파일은 특정 jar 파일을 특정 런타임 대상과 일치시켜야 하는 문제를 해결한다.

6. 기타 성능 향상

JVM에 대한 다음과 같은 변경 내용은 성능에 직접적인 영향을 준다.

  • JEP 197: 분할된 코드 캐시 - 코드 캐시를 고유 세그먼트로 나눈다. 이러한 구분은 JVM 메모리 공간을 보다 효율적으로 제어하고, 컴파일된 메서드의 검색 시간을 단축하고, 코드 캐시의 조각화를 크게 줄이고, 성능을 향상시킨다.

  • JEP 254: 압축 문자열 - 문자 인코딩에 따라 문자열의 내부 표현을 문자당 2바이트에서 문자당 1바이트 또는 2바이트로 변경한다. 대부분의 문자열은 ISO-8859-1/라틴어-1 문자를 포함하므로 이 변경 내용으로 인해 문자열을 저장하는데 필요한 공간이 효율적으로 변경된다.

  • JEP 310: 애플리케이션 Class-Data 공유 - Class-Data 공유는 런타임에 보관된 클래스를 메모리 매핑할 수 있도록 하여 시작 시간을 줄인다. 애플리케이션 클래스-데이터 공유는 애플리케이션 클래스를 CDS 보관함에 배치할 수 있도록 하여 클래스-데이터 공유를 확장한다. 여러 JVM이 동일한 보관 파일을 공유하는 경우 메모리가 저장되고 전체 시스템 응답 시간이 단축된다.

  • JEP 312: Thread-Local 핸드셰이크 - 전역 VM 안전점을 수행하지 않고도 스레드에서 콜백을 실행할 수 있으므로 전역 안전점 수를 줄여 VM이 대기 시간을 단축할 수 있다.

  • 컴파일러 스레드 지연 할당 - 계층화된 컴파일 모드에서 VM은 많은 수의 컴파일러 스레드를 시작한다. 이 모드는 CPU가 여러 개 있는 시스템에서 기본값이다. 이러한 스레드는 사용 가능한 메모리 또는 컴파일 요청 수에 관계없이 생성된다. 스레드는 유휴 상태일 때(거의 모든 시간) 메모리를 사용하므로 리소스를 비효율적으로 사용한다. 이 문제를 해결하기 위해 시작 시 각 유형의 컴파일러 스레드를 하나씩만 시작하도록 구현이 변경되었다. 추가 스레드를 시작하고 사용하지 않는 스레드를 종료하는 것은 동적으로 처리된다.

핵심 라이브러리를 다음과 같이 변경하면 새 코드 또는 수정된 코드의 성능에 영향이 있다.

  • JEP 193: 변수 핸들 - 개체 필드 및 배열 요소에 대한 다양한 java.util.concurrent.atomic 및 sun.misc.Unsafe 연산, 메모리 순서를 세분화한 제어를 위한 표준 펜스 작업 집합 및 참조된 개체에 대한 연결성이 매우 높은 상태로 유지되도록 표준 연결 가능성 펜스 작업을 호출하는 표준 수단을 정의한다.

  • JEP 269: 컬렉션에 대한 편의 팩터리 메서드 - 적은 수의 요소로 컬렉션 및 맵의 인스턴스를 편리하게 만들 수 있도록 라이브러리 API를 정의한다. 컬렉션 인터페이스에서 간결하고 수정할 수 없는 컬렉션 인스턴스를 만드는 고정 팩터리 메서드이다. 이러한 인스턴스는 본질적으로 더 효율적이다. API는 조밀하게 표시되고 래퍼 클래스가 없는 컬렉션을 만든다.

  • JEP 285: Spin-Wait 힌트 - Java가 런타임 시스템에 스핀 루프에 있음을 암시할 수 있는 API를 제공한다. 특정 하드웨어 플랫폼은 스레드가 바쁜 대기(busy-wait) 상태라고 소프트웨어가 알려주면 이점을 얻을 수 있다.

  • JEP 321: HTTP 클라이언트(표준) - HTTP/2 및 WebSocket을 구현하고 레거시 HttpURLConnection API를 대체할 수 있는 새 HTTP 클라이언트 API를 제공한다.

[출처 및 참고]

This post is licensed under CC BY 4.0 by the author.