grep

Engineering

Big things in JDK 13

NHN

2020년 5월 27일

원문에서 보기 ↗

Java 13

6개월마다 GA 배포되는 신규 버전으로, openjdk13은 2019년 9월 17일에 배포되었습니다. 주요 기능은 아래와 같습니다.

참고: https://openjdk.java.net/projects/jdk/13/

JEP350: Dynamic CDS Archives

openjdk12에서는 Default CDS Archives로 기본 기능으로 추가되었었죠. Dynamic CDS Archives는 확장된 버전입니다. 사용성을 개선하고 Default CDS Archives에는 없는, 로드된 애플리케이션의 클래스와 라이브러리 클래스를 포함하도록 개선되었습니다. AppCDS(Application Class-Data Sharing)는 Java 애플리케이션 실행이 종료될 때 동작하여, Java 프로세스들 사이에 공통으로 사용되는 메타데이터를 공유하는 방식으로 성능(주로 시작 시간)을 향상하기 위한 기능입니다.

Motivation

HotSpot에서 AppCDS를 사용하여 애플리케이션 클래스를 저장하면, 추가적인 Startup 시간과 메모리의 이점을 볼 수 있습니다만, 여전히 세 가지 정도의 추가적인 절차가 필요합니다.

  1. 클래스 리스트를 생성하기 위한 하나 이상의 trial run
  2. 생성된 클래스 리스트를 사용하여 아카이브를 덤프
  3. 아카이브와 함께 실행

또한, 이 세 가지의 절차는 기본 클래스 로더를 사용하는 애플리케이션에서만 동작합니다. HotSpot에서 experimental로 지원하기는 하지만 사용하기가 쉽지는 않다고 합니다.

Goals

JEP350에서는 이러한 불편함을 해결하기 위해서 Java 애플리케이션 실행 시, 간단하게 커맨드라인에 -XX:ArchiveClassesAtExit 옵션을 주는 것으로 AppCDS를 활성화 할 수 있습니다. 이렇게 실행된 Java 애플리케이션은 종료 시에 jsa라는 시스템 아카이브 파일을 생성하는데요. 해당 파일을 이용해 메타데이터를 공유하는 Java 애플리케이션을 향상된 성능으로 실행시킬 수 있습니다. 또한, 아래와 같이 옵션에 인수를 주어 생성될 아카이브 파일의 이름을 지정할 수 있습니다.

% bin/java -XX:ArchiveClassesAtExit=hello.jsa -cp hello.jar Hello

이렇게 생성된 아카이브 파일은 아래와 같이 사용할 수 있습니다.

% bin/java -XX:SharedArchiveFile=hello.jsa -cp hello.jar Hello

이 방식은 위의 '1. trial run' 절차를 제거하여 AppCDS의 사용이 간편해지고, 기본 제공 클래스 로더와 사용자 정의 클래스 로더 효과를 모두 지원한다고 합니다. 또한, JEP350의 개선기능은 애플리케이션의 첫 실행에서 자동 아카이브 생성을 수행할 수 있습니다. 그러면 '2. 아카이브 덤프' 절차를 제거할 수 있게 되고, 이는 CDS/AppCDS의 사용을 자동화할 수 있습니다.

JEP351: ZGC: Uncommit Unused Memory (Experimental)

사용하지 않는 힙 메모리를 OS에 반환하도록 ZGC를 개선하는 JEP입니다.

ZGC는 JDK11부터 실험적으로 도입된 GC입니다. ZGC에 관련된 간단한 설명은 이전에도 Big things in JDK 11에서도 이야기되었는데요. -XX:+UnlockExperimentalVMOptions XX:+UseZGC으로 ZGC를 활성화할 수 있습니다. 자세한 사용법과 설명은 여기를 참고해주세요.

Motivation

이 당시 ZGC는 오랫동안 사용하지 않더라도 메모리를 OS에 반환하지 않는 문제가 있었는데요. 이는 아래와 같은 몇몇 환경에서는 좋은 방식이 아니었습니다.

Goals

따라서 ZGC는 ZPage라는, page cache 내의 사용되지 않는 메모리 집합을 정해진 정책에 따라 커밋 해제하여 OS로 반환하되, 최소 힙 크기(-Xms) 아래로는 줄어들지 않도록 지정하게 하였습니다. (-Xms와 -Xmx가 동일한 경우, 이 기능이 암시적으로 비활성화됩니다. 명시적으로 비활성화하기 위해서는 -XX:-ZUncommit을 사용할 수 있습니다) 일반적으로 page cache는 LRU(Least Recently Used) 방식을 사용하고 page 크기별로 구분하기 때문에 메모리를 해제하는 방법은 비교적 간단합니다. 하지만, 문제는 캐시에서 ZPage를 제거할 시기를 결정하는 데 있습니다.

단순하게는 일정 시간이 지나면 제거되도록 설정할 수 있고, 실제로 이 방식은 Shenandoah GC에서, 기본값 5분으로 사용하고 있습니다. ZGC도 -XX:ZUncommitDelay=<seconds>(default 300초) 으로 간단한 시간 정책을 제공할 수 있습니다. 이러한 방식 외에도, 새 옵션을 추가하지 않고 GC가 일어나는 빈도에 기초하여 메모리 해제 주기를 설정할 수도 있습니다.

JEP353: Reimplement the Legacy Socket API

java.net.Socket 및 java.net.ServerSocket API의 기본 구현을 유지보수와 디버그하기 쉬운 형태로 리팩토링하는 JEP입니다.

Motivation

java.net.Socket과 java.net.ServerSocket은 JDK 1.0에서 처음 등장하였는데요. 유지보수와 디버깅이 어려운 레거시 Java 및 C 코드의 혼합 형태로 구현되었습니다. 이 구현에서는 아래와 같은 몇 가지 문제가 있었습니다.

Goals

따라서, java.net.Socket 및 java.net.ServerSocket API는 모든 조작을 SPI(Service Provider Interface) 메커니즘인 java.net.SocketImpl에 위임하는데요. 내장 구현을 "plain" 구현이라고 하며, SocketInputStream 및 SocketOutputStream 클래스를 지원하는 비공개 PlainSocketImpl에 의해 구현됩니다.

아래는 새로운 구현에 대한 내용입니다.

그리고 아래는 이전 구현과 다르게 동작하는 상황에 해당하는 내용입니다. 처음 두 개를 제외한 나머지들은 -Djdk.net.usePlainSocketImpl 옵션으로 완화할 수 있습니다.

SPI(Service Provider Interface): 플러그인 형태로 제공하는 인터페이스. 인터페이스만 정의하고 각 구현은 가져다 사용하는 벤더에서 구현하도록 함. 예) Java Cryptography Extension

JEP354: Switch Expressions (Second Preview)

JDK 12에서 preview로 언급되었던 Switch Expressions의 second preview입니다. 내용은 JEP325와 동일하고 break 키워드가 yield 키워드로 변경되었습니다.

int result = switch (s) {
    case "Foo": 
        yield 1;
    case "Bar":
        yield 2;
    default:
        System.out.println("Neither Foo nor Bar, hmmm...");
        yield 0;
};

python을 사용하신 분들이라면 익숙한 키워드이실 텐데요. 동작은 비슷합니다. switch 표현식 내부에서 생성된 특정 값을 반환하는데요. JDK12에서 나왔던 break value; 표현식이 yield value;로 대체되었습니다. return value;가 제어권이 함수 호출자나 생성자에게 있었다면, yield value;는 스위치 표현식에게 제어권을 전달합니다.

JEP325에서 제안된 화살표 방식은 단일 표현식을 나타낼 때는 유효하게 동작합니다.

int j = switch (day) {
    case MONDAY  -> 0;
    case TUESDAY -> 1;
    default      -> {   //블록이나 기존의 switch-case문에서 yield를 사용
        int k = day.toString().length();
        int result = f(k);
        yield result;
    }
};

JEP355: Text Blocks (Preview)

JDK15에 제안될 JEP378의 preview입니다. 다른 언어에서도 등장하는 Text block이 Java에도 추가됩니다. 기존에 여러 줄의 텍스트를 사용할 때, +와 new line으로 연결해주었다면 이 제안에서는 """을 통해 multi line으로 텍스트를 입력할 수 있도록 제안하고 있습니다.

HTML example (as-is)
String html = "<html>\n" +
              "    <body>\n" +
              "        <p>Hello, world</p>\n" +
              "    </body>\n" +
              "</html>\n";
HTML example (to-be)
String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
              """;
SQL example (as-is)
String query = "SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`\n" +
               "WHERE `CITY` = 'INDIANAPOLIS'\n" +
               "ORDER BY `EMP_ID`, `LAST_NAME`;\n";
SQL example (to-be)
String query = """
               SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
               WHERE `CITY` = 'INDIANAPOLIS'
               ORDER BY `EMP_ID`, `LAST_NAME`;
               """;
Polyglot language example (as-is)
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {\n" +
                         "    print('\"Hello, world\"');\n" +
                         "}\n" +
                         "\n" +
                         "hello();\n");
Polyglot language example (to-be)
ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("""
                         function hello() {
                             print('"Hello, world"');
                         }
                         
                         hello();
                         """);

이 제안은 Java string의 가독성을 향상하고 이스케이프 문자열 사용을 피하고자 만들어졌습니다. 특수문자 이스케이프 방식을 기존의 문자열 사용법과 동일하고 필요한 경우, 아래 String 관련 함수들을 사용할 수 있습니다.


이전 JDK 변경사항은 아래에서 확인하실 수 있습니다.