Post

자바 버전별 릴리즈

언어의 성장 방향

문득 자바의 개발 방향은 어디로 향하고 있는 것인지 궁금해졌다.

그래서 각 버전별로 릴리즈 노트를 찾아보았고, 다 정리하기엔 너무 많아서 일부 목록만 발췌한 후 어떤 패치들이 이루어졌는지 살펴보려 한다.

버전별 릴리즈

Java 11

  • HTTP 클라이언트 API를 표준화
  • 새 Collection.toArray(IntFunction) 기본 메서드
  • Lambda 매개변수에 대한 로컬 변수 구문 (var)
  • 컴파일러 스레드의 지연 할당 (-XX:+UseDynamicNumberOfCompilerThreads)
  • ZGC 확장 가능한 저지연 가비지 수집기
  • 무작동 가비지 수집기 Epsilon GC, 메모리 할당만 처리하고 메모리 회수 메커니즘을 구현 X
  • jvmti를 통한 낮은 오버헤드 힙 프로파일링

코드 레벨은 편의성 기능들이 패치되었고, 인프라는 주로 성능 관련 개선이 많이 이루어진 것을 확인할 수 있다. Epsilon GC는 특이해서 한번 찾아봤는데

실제 내 어플리케이션이 얼마나 메모리를 사용하는 지에 대한 임계치나 어플리케이션 퍼포먼스 등을 보다 정확하게 측정할 수 있다

고 한다. 임베디드 환경에서 유용할 수 있다는 글도 봤는데 왜 그런지는 잘 모르겠다. 만약 신호등 같은 교통 제어 시스템에서 OOM나면 대참사 아닌가??

Java 15

  • CharSequence에 isEmpty 기본 메서드 추가
  • 유니코드 13.0 지원
  • Nashorn JavaScript 엔진 제거
  • 사용되지 않음 -XXUseAdaptiveGCBoundary

Nashorn JavaScript 엔진 제거가 눈에 띈다. 왜 없앴을까? 코드 간 섞임을 방지해 경계를 명확히 하려는 의도일까?

Java 16

  • Stream.toList() 메서드 추가
  • record 클래스 추가
  • ZGC 동시 스택 처리
  • Elastic Metaspace

엘라스틱 메타스페이스는 VM 내부 메타스페이스 및 클래스 공간 구현을 정밀 검사해서 클래스 메타데이터에 사용되는 메모리를 줄여주는 기술이라고 한다.

record 클래스는 들어보기만 해서 찾아봤는데, 순수하게 데이터를 보유하기 위한 특수 클래스라고 한다. 생긴게 클래스가 아니라 함수같기도 한데, 부가적인 코드를 굉장히 줄여주는 것 같다. 예를 들자면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Person {
    private final String name;
    private final int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
}

위와 같은 코드가

1
2
public record Person(String name, int age) {
}

다음과 같이 간결해 진다고 한다. 여기서 단순히 데이터 클래스로만 사용하지 않고,

1
2
3
4
5
6
7
8
@RestController
public record HelloController(HelloService helloService) {

    @GetMapping("/")
    public void sampleRecord() {
        helloService.hello();
    }
}

이런 식으로도 사용이 가능하다고 한다. 뭔가 코틀린 코드 같다.

Java 17

  • Sealed class 추가
  • macOS의 ARM 얼리 액세스 사용 가능
  • 새로운 macOS 렌더링 파이프라인
  • macOS에서 UserDefinedFileAttributeView에 대한 지원 추가

왜 이렇게 맥 관련 패치가 많나 확인해보니 17 버전이 21년 9월에 출시되었는데, 비슷한 시점에 애플 신제품들이 많이 출시되었다. 아마 그에 맞게 여러가지 패치를 한게 아닌가 싶다.

Sealed 클래스는 상속하거나 구현할 클래스를 지정해두고, 해당 클래스들만 상속/구현이 가능하도록 제한하는 기능이라고 한다. 다음과 같이 사용한다고 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public sealed class Character permits Wizard, Warrior {

}

public sealed class Wizard extends Character permits A, B {
    
}

// non-sealed는 누구나 상속 가능
public non-sealed class Warrior extends Character {

}

public final class A extends Wizard {

}

public final class B extends Wizard {

}

// 상속 불가능, 에러
public class BadDog extends Wizard {

} 

// 상속 가능
public class GoodDog extends Warrior {

} 

Java 18

  • UTF-8 기본값 지정
  • 최소 정적 웹 서버 jwebserver 추가
  • ZGC, SerialGC, ParallelGC 문자열 중복 제거 지원
  • 최대 512MB의 G1 힙 영역 허용
  • 지원 중단된 Thread.stop()

UTF-8이 기본값으로 지정되며 범용성이 높아졌다. jwebserver라는 테스트 용도의 가벼운 정적 웹 서버도 추가되었으며, G1 가바지 컬렉터의 힙 사이즈 허용 영역이 늘어났다. 큰 규모의 서버에 대비해서 이루어진 패치가 아닐까 싶다.

Java 20

  • 스위치용 패턴 매칭(4차 프리뷰)
  • Foreign Function & Memory API (2차 프리뷰)
  • 벡터 API(5차 인큐베이터)
  • Scoped Values (인큐베이터)
  • Structured Concurrency API (2차 인큐베이터)
  • 가상 스레드(2차 프리뷰)
  • 유니코드 15.0 지원

19를 생략한 이유는 대부분의 주요 기능들이 20까지 이어지는 프리뷰 기능들이라 그렇다. 지금까지 프리뷰 기능은 기록하지 않았는데 20에서는 워낙 요즘 핫한 아이들이 많아서 적어야 할 것 같다.

먼저 스위치 구문이 개선되고 있다. 기존의 스위치 문은 약간 노가다성이 짙었는데, 다음과 같이 향상되고 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 기존 방식, 참 길다
switch (day) {
    case MONDAY:
        break;
    case TUESDAY:
        System.out.println(1);
        break;
    case WEDNESDAY:
        System.out.println(1);
        break;
    case THURSDAY:
        break;
    case FRIDAY:
        break;
    case SATURDAY:
        System.out.println(1);
        break;
    case SUNDAY:
        System.out.println(1);
        break;
    default:
        break;   
}

// 개선되는 방식
switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY	-> System.out.println(1);
    case THURSDAY				-> System.out.println(2);
    case FRIDAY, SATURDAY		-> System.out.println(3);
    case SUNDAY				-> System.out.println(4);
}

// 변수 대입도 가능
int value = switch (day) {
    case MONDAY, TUESDAY, WEDNESDAY	-> 1;
    case THURSDAY				-> 2;
    case FRIDAY, SATURDAY		-> 3;
    case SUNDAY				-> 4;
};

메모리 API는 힙 영역이 아닌 네이티브 메모리에 접근하기 위해 나온 기술로 안전하고 효율적으로 네이티브 메모리에 액세스하도록 도와준다고 한다.

벡터 API는 벡터 계산을 표현하는 API를 도입하여 동등한 스칼라 계산보다 우수한 성능을 달성한다고 하며, Scoped Value는 스레드 내부 및 스레드 간에 변경 불가능한 데이터를 공유할 수 있도록 한다고 한다

Structured Concurrency API는 다중 스레드 코드를 관리하기 위한 API라고 한다. 아래의 코드를 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
Future<Shelter> shelter;
Future<List<Dog>> dogs;
try (ExecutorService executorService = Executors.newFixedThreadPool(3)) {
    shelter = executorService.submit(this::getShelter); // 정상 처리됨
    dogs = executorService.submit(this::getDogs); // 작업 실패함
    
    Shelter theShelter = shelter.get(); // 실패 여부에 대해 전혀 모름
    List<Dog> theDogs = dogs.get();  // 실패한 상태, 예외 던짐

    Response response = new Response(theShelter, theDogs);
} catch (ExecutionException | InterruptedException e) {
    throw new RuntimeException(e);
}

기존의 여러 스레드를 활용한 작업에서는 특정 작업의 실패가 전파되지 않으며 리소스가 낭비된다. Structured Concurrency API에서는 이러한 문제를 해결한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
record Shelter(String name) { }

record Dog(String name) { }

record Response(Shelter shelter, List<Dog> dogs) { }


try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<Shelter> shelter = scope.fork(this::getShelter);
    Future<List<Dog>> dogs = scope.fork(this::getDogs);
    scope.join();
    Response response = new Response(shelter.resultNow(), dogs.resultNow());
    // ...
}

ShutdownOnFailure()는 문제가 발생할 경우 하위 작업을 종료해서 리소스 낭비를 방지한다. 그 외에도 ShutdownOnSuccess(), throwIfFailed(), joinUntil()등을 통해 불필요한 작업을 방지하고 효과적인 코드 작성이 가능하며, 다중 스레드 코드를 마치 동기식처럼 쉽게 작성할 수 있다.

가상스레드

가상 스레드는 기존 자바의 스레드를 개선하기 위해 등장했다.

자바의 스레드는 OS의 스레드를 이용하는 방식인데 이를 효율적으로 이용하기 위해 스레드풀을 만들어 사용한다. 제한적인 갯수의 스레드풀 내에서 요청당 하나의 스레드가 배정되고 작업을 대기 및 처리하는 과정에서 많은 리소스가 수반된다. 이를 해결하기 위해 리액티브 프로그래밍(Webflux)이 발전했지만 코드의 숙련도, 기존 코드와의 호환성 등의 문제가 있었다.

이를 해결하기 위해 가상 스레드가 탄생하게 되었다. JVM 자체적으로 가상 스레드를 OS의 스레드와 매칭시켜주면서 기존 스레드 방식의 이점은 취하되 효율적인 자원 사용이 가능해졌다.

결론

뻔한 결론이지만 코드 작성은 편하게, 성능은 좋아지게 발전하고 있는 것 같다.

한 가지 든 생각이 있다면 메모리 여분을 최대한 확보하고 효율적으로 활용하려는 시도들이 많이 보이는 것 같다.

요즘 스프링 진영에서는 GraalVM 등을 활용한 네이티브 이미지로 굉장히 많은 메모리를 절약할 수 있는 방식들을 고안했고 자바에서는 가비지 컬렉터들의 지속적인 개선, 가상 스레드, 메모리 API와 같은 시도들을 통해 성능 및 메모리 최적화를 진행하는 것을 확인할 수 있었다. 둘의 시너지가 잘 맞아보길 기대한다.

참고

  • Oracle, JDK 11 Release Notes, https://www.oracle.com/java/technologies/javase/11-relnote-issues.html
  • Oracle, JDK 15 Release Notes, https://www.oracle.com/java/technologies/javase/15-relnote-issues.html
  • Oracle, JDK 16 Release Notes, https://www.oracle.com/java/technologies/javase/16-relnote-issues.html
  • Oracle, JDK 17 Release Notes, https://www.oracle.com/java/technologies/javase/17-relnote-issues.html
  • Oracle, JDK 18 Release Notes, https://www.oracle.com/java/technologies/javase/18-relnote-issues.html
  • Oracle, JDK 19 Release Notes, https://www.oracle.com/java/technologies/javase/19-relnote-issues.html
  • Oracle, JDK 20 Release Notes, https://www.oracle.com/java/technologies/javase/20-relnote-issues.html
  • baeldung, Foreign Memory Access API in Java 14, https://www.baeldung.com/java-foreign-memory-access
  • baeldung, Structured Concurrency in Java 19, https://www.baeldung.com/java-structured-concurrency
  • congcoding, [Java 14] 개선된 switch 문(Enhanced Switch Expressions), https://congcoding.tistory.com/73
  • MarrRang Dev Blog, [Java] Sealed Class, https://marrrang.tistory.com/82
  • 코딩스타트, Java - jdk 14 record(레코드) 란?! Data class(데이터 클래스), https://coding-start.tistory.com/355
  • HYPEBEAST, 9월 예정, ‘애플 이벤트’에서는 어떤 신제품들이 공개될까?, https://hypebeast.kr/2021/8/apple-event-rumor-new-iphone-macbook-pro-airpods-ipad
  • Javarevisited, How to Use Java 19 Virtual Threads, https://medium.com/javarevisited/how-to-use-java-19-virtual-threads-c16a32bad5f7
  • findstar, Virtual Thread란 무엇일까? (1), https://findstar.pe.kr/2023/04/17/java-virtual-threads-1/
This post is licensed under CC BY 4.0 by the author.

© . Some rights reserved.

Using the Jekyll theme Chirpy