Java 8에서는 기존 버전들과 비교해서 가장 큰 변화가 있었다. 간략하게 정리하면 다음과 같다.
- 스트림 API
- 메서드에 코드를 전달하는 기법(메서드 레퍼런스와 람다)
- 인터페이스의 디폴트 메서드
Java 8을 공부하고 써오다가 Java 11로 변환한 이유를 정리해보려고 한다.
1. 람다 파라미터를 위한 지역변수 표현
Java 10에서 var 구문이 생겼다.
Java 11에서는 람다 파라미터에서 좀 더 명시적으로 var를 사용할 수 있게 되었다.
list.stream()
.map((@NotNull var s) -> s.toLowerCase())
.collect(Collectors.toList());
- Java 8에 등장했으나 Java 10에서 사라졌다가 Java 11에서 복귀
- 람다는 타입을 스킵할 수 있는데 이걸 사용하는 이유는, 1@Nullable1 등의 어노테이션을 사용하기 위해 타입을 명시해야 할 때
var
를 사용하려면 괄호를 써야하며 모든 파라미터에 사용해야 하고 다른 타입과 혼용하거나 일부 스킵은 불가능하다.
2. HTTP Client (Standard)
Non-Blocking request and response 지원 (with CompletableFuture)
Backpressure 지원(java.util.concurrent.Flow 패키지를 통해 Rx Flow를 구현체에 적용)
Factory method 형태로 지원
HTTP/2 지원
spring5에서는 Rest Client에 RestTemplate 대신해 WebClient를 사용하여 비동기 구현을 할 수 있다.
기존 멀티스레드 방식을 버리고 Reactor(WebFlux)와 함께 스프링 기반 웹서비스를 구현할 경우,
그에 따른 성능과 효율 향상은 어마어마하다.
3. 모듈
모듈을 통해 애플리케이션에 필요한 구성 요소만 포함하는 런타임 구성을 사용자 지정할 수 있다.
이 사용자 지정은 메모리 공간을 더 적게 사용하며 애플리케이션이 jlink를 사용하여
배포용 사용자 지정 런타임에 정적으로 연결될 수 있게 해준다.
메모리 공간을 적게 사용하면 특히 마이크로서비스 아키텍처에서 유용할 수 있다.
내부적으로 JVM은 모듈을 활용하여 클래스 로딩을 보다 효율적으로 만들 수 있다.
그 결과 런타임이 더 작아지고, 더 가벼워져서 더 빠르게 시작할 수 있습니다.
모듈은 클래스에 필요한 구성 요소를 인코딩하기 때문에
JVM에서 애플리케이션 성능을 개선하기 위해 사용하는
최적화 기법의 효과가 더 좋아질 수 있다.
프로그래머의 경우 모듈은 모듈이 내보내는 패키지와 필요한 구성 요소를 명시적으로 선언하고
반사적 액세스를 제한하여 강력한 캡슐화를 적용하는 데 도움이 된다.
이 캡슐화 수준을 사용하면 애플리케이션을 더 안전하고 쉽게 유지 관리할 수 있다.
4. 프로파일링 및 진단
Java Flight Recorder
JFR(Java Flight Recorder)은 실행 중인 Java 애플리케이션에서 진단 및 프로파일링 데이터를 수집한다.
JFR 및 JMC는 Java 8에서 상용 기능이지만 Java 11에서는 둘 다 오픈 소스이다.
Java Mission Control
JMC(Java Mission Control)는 JFR(Java Flight Recorder)에서
수집한 데이터를 그래픽으로 표시하고 Java에서는 오픈 소스로 제공된다.
JFR 및 JMC를 사용하면 메모리 누수, GC 오버헤드, 핫 메서드,
스레드 병목 상태 및 I/O 블로킹과 같은 런타임 문제를 진단할 수 있다.
통합 로깅
Java 11에는 JVM의 모든 구성 요소에 대한 일반적인 로깅 시스템이 있다.
이 세분화된 로깅은 JVM 충돌에 대한 근본 원인 분석을 수행하고
프로덕션 환경에서 성능 문제를 진단하는 데 유용하다.
오버헤드가 낮은 힙 프로파일링
Java 힙 할당을 샘플링하는 데 사용할 수 있는 새 API가
JVMTI(Java Virtual Machine Tool Interface)에 추가되었다.
JFR 구현에서는 할당이 누락될 수도 있다.
반면 Java 11의 힙 샘플링은 라이브 개체와 데드 개체 모두에 대한 정보를 제공할 수 있다.
APM(애플리케이션 성능 모니터링) 공급업체가 이 새로운 기능을 활용하기 시작했다.
StackWalker
현재 스레드의 스택에 대한 스냅샷 가져오기는 로깅할 때 주로 사용된다.
5. Garbage 수집
Java 11에서 사용할 수 있는 가비지 수집기는 직렬, 병렬, 가비지 우선 및 엡실론(Epsilon)이다.
Java 11의 기본 가비지 수집기는 G1GC(가비지 우선 가비지 수집기)이다.
ZGC는 일시 중지 시간을 10ms 미만으로 유지하려고 하는 대기 시간이 짧은 동시 수집기이다.
ZGC는 Java 11에서 실험적 기능으로 사용할 수 있다.
CMS(Concurrent Mark and Sweep) 수집기는 사용할 수 있지만 Java 9 이후에는 사용되지 않는다.
Epsilon(엡실론)
엡실론 가비지 수집기는 할당을 처리하지만 메모리를 회수하지는 않는다.
힙이 소진되면 JVM이 종료된다.
엡실론은 수명이 짧은 서비스와 가비지를 사용하지 않는 것으로 알려진 애플리케이션에 유용하다.
Docker 컨테이너의 향상된 기능
Java 10 이전에 컨테이너에 설정된 메모리 및 CPU 제약 조건은 JVM에서 인식되지 않았다.
예를 들어 Java 8에서 JVM은 최대 힙 크기의 기본값을 기본 호스트의 실제 메모리의 ¼로 설정한다.
Java 10부터 JVM은 컨테이너 제어 그룹(cgroup)에 의해 설정된 제약 조건을 사용하여 메모리 및 CPU 제한을 설정한다.
예를 들어 기본 최대 힙 크기는 컨테이너의 메모리 제한의 ¼이다. (예: -m2G의 경우 500MB)
다중 릴리스 jar 파일
Java 11에서 클래스 파일의 여러 Java 릴리스별 버전을 포함하는 jar 파일을 만들 수 있다.
6. 성능 향상
JVM 성능 향상
-
Segmented Code Cache: 코드 캐시를 고유 세그먼트로 나눈다.
이는 JVM 메모리 공간을 효율적으로 제어하고 컴파일된 메서드의 검색시간을 단축하고
코드 캐시의 조각화를 줄여 성능을 향상시킨다. -
Compact Strings: 문자열을 저장하는 데 필요한 공간이 효율적으로 변경되었다.
-
Application Class-Data Sharing: 클래스-데이터 공유는 보관된 클래스가 런타임 시 메모리에 매핑될 수 있게 하여 시작 시간을 줄여준다.
애플리케이션 클래스-데이터 공유는 애플리케이션 클래스를 CDS 보관함에 배치할 수 있도록 하여 클래스-데이터 공유를 확장한다.
여러 JVM이 동일한 보관 파일을 공유하는 경우 메모리가 저장되고 전체 시스템 응답 시간이 단축된다. -
Thread-Local Handshakes: 글로벌 VM 세이프포인트를 수행하지 않고 스레드에 대한 콜백을 실행하여 VM이 글로벌 세이프포인트의 수를 줄여 대기 시간을 단축할 수 있게 해준다.
-
Lazy Allocation of Compiler Threads: VM은 단계별 컴파일 모드에서 다량의 컴파일러 스레드를 시작한다.
이 모드는 CPU가 여러 개 있는 시스템에서 기본값이다. 이러한 스레드는 사용 가능한 메모리 또는 컴파일 요청 수에 관계없이 생성된다.
스레드는 유휴 상태일 때(거의 모든 시간) 메모리를 사용하므로 리소스를 비효율적으로 사용한다.
이 문제를 해결하기 위해 시작 시 각 유형의 컴파일러 스레드를 하나씩만 시작하도록 구현이 변경되었다. 추가 스레드를 시작하고 사용하지 않는 스레드를 종료하는 것은 동적으로 처리된다.
핵심 라이브러리 성능 향상
-
Variable Handles: 표준을 정의한다는 것은 개체 필드 및 배열 요소, 메모리 정렬의 세부적인 제어를 위한 표준 펜스 작업 세트,
참조된 개체의 강력한 연결성을 유지하기 위한 표준 연결성 펜스 작업에 대한 다양한
java.util.concurrent.atomic
및sun.misc.Unsafe
작업의 동급 요소를 호출하는 것을 의미한다. -
Convenience Factory Methods for Collections: 소량의 요소를 사용하여 컬렉션 및 맵 인스턴스를
편리하게 만들 수 있게 해주는 라이브러리 API를 정의한다.
컬렉션 인터페이스에서 간결하고 수정할 수 없는 컬렉션 인스턴스를 만드는 고정 팩터리 메서드이다.
이러한 인스턴스는 본질적으로 더 효율적이다. API는 조밀하게 표시되고 래퍼 클래스가 없는 컬렉션을 만든다. -
Spin-Wait Hints: Java에서 스핀 루프에 있음을 런타임 시스템에 암시할 수 있게 해주는 API를 제공한다.
특정 하드웨어 플랫폼은 스레드가 바쁜 대기(busy-wait) 상태라고 소프트웨어가 알려주면 이점을 얻을 수 있다. -
HTTP Client(Standard): HTTP/2 및 WebSocket을 구현하고 레거시 HttpURLConnection API를 대체할 수 있는 새로운 HTTP 클라이언트 API를 제공한다.
7. Nest-Based Access Control
class Test {
static class Nest1 {
private int nest1Var;
}
static class Nest2 {
private int nest2Var;
}
}
위 코드와 같이 Nested class의 경우, Test
, Nest1
, Nest2
는 모두 nestmate
이다.
기존 JVM 상에서는 nestmate끼리 private 멤버 변수를 접근하려면 컴파일러가 중간에 bridge method를 만들어야 했다.
따라서, reflection을 사용하여 nestmate class의 private 멤버 변수에 접근하려고 하면,
llegalAccessException이 발생한다.
이러한 모순을 해결하고자, 새로운 ‘nest’라는 class file 개념을 도입해 하나의 중첩 클래스이지만
서로 다른 클래스파일로 분리하여 bridge method의 도움 없이도 서로의 private 멤버에 접근할 수 있도록 하였다.