Post

Java 10버전은 뭐가 달라졌나

개요

2018년 3월 20일에 출시된 자바 10버전은 뭐가 달라졌을까?

현재 19버전까지 나왔는데, 좋은 글을 발견하여 이 글을 읽어가면서 예제를 작성하고 학습할 예정이다.


✏️ Local-variable Type inference(“var”)

10버전에서부터 로컬변수에 var를 사용하여 선언할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
@Test
@DisplayName("1.var를 사용할 수 있다.")
void test1(){
    var i = 0;
    var stringTest = "kms is kms";
    var list = List.of(1,2,3,4,5);
    var httpClient = HttpClient.newBuilder().build();
    logger.debug("i: {}", i);
    logger.debug("stringTest: {}", stringTest);
    logger.debug("list: {}", list);
    logger.debug("httpClient: {}", httpClient);
}

image

하지만 var의 사용을 고려해야할 사항이 있다. 데이터 타입을 명확히 알고 있어야하고, 협업시 해당 데이터에 대한 논의를 할 수도 있다는 것이다.

그래서 var를 사용할때는 팀원간의 협의가 이루어져야 할 것이다.


✏️ Immutable Collections

불변객체의 컬렉션들이 생겼다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
@DisplayName("2. 불변 컬렉션이 생겼다.")
void immutableCollections(){
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    List<Integer> unmodifiedList = Collections.unmodifiableList(list);
    try{
        unmodifiedList.add(4);
    }catch (UnsupportedOperationException e){
        logger.warn("error: ",e);
    }finally {
        list.add(10);
        logger.debug("list: {}", list);
        logger.debug("unmodifiedList: {}", unmodifiedList);
    }
}

image

사진과 같이 add(4)로 요소를 추가해주면 예외가 발생한다.

또한, 원본 list가 변경됨에 따라 수정불가능한 자료구조 객체도 따라 바뀌는걸 확인할 수 있다.

이와 같은 이유는 불변객체가 리스트의 복사본을 포함하지 않기 때문이라는데.. 잘 모르겠다.

피드백 부탁드립니다.

1
This is because the wrapper does not contain a copy of the list

List.copyOf(), Set.copyOf(), and Map.copyOf()

Java10에서는 불변 컬렉션들을 위의 함수들을 통해 복사할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
@DisplayName("2.1 List.copyOf(), Set.copyOf(),Map.copyOf()")
void immutableCollectionsFunction(){
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    List<Integer> immutable = List.copyOf(list);
    list.add(4);
    logger.debug("immutableList: {}",immutable);

    try{
        immutable.add(10);
    }catch (UnsupportedOperationException e){
        logger.warn("error: ",e);
    }
}

image

코드를 보면 List.copyOf()를 통해 복사를 하고 add()함수를 통해 요소를 추가해줬는데 불변 컬렉션에는 2번의 예제와 달리 값이 들어가지 않은걸 볼 수 있다. 즉, 원본 컬렉션을 변경해도 복사된 불변 컬렉션에는 값이 들어가지 않는다는 것이다.

그리고, try~catch문에서도 볼 수 있는데, 불변객체에 값을 추가하려하니 예외가 발생하는 모습이다.

Collectors.toUnmodifiableList(), toUnmodifiableSet(), and toUnmodifiableMap()

제목에서도 알 수 있다싶이 저 함수들을 이용해 불변 컬렉션들을 만들 수 있다.

가변 컬렉션들은 이렇게 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
@DisplayName("2.2 Collectors.toUnmodifiableList(), toUnmodifiableSet(), and toUnmodifiableMap()")
void toUnmodifiedCollectors(){
    List<Integer> list = IntStream.rangeClosed(1,5).boxed().collect(Collectors.toList());
    Set<Integer> set = IntStream.rangeClosed(1,5).boxed().collect(Collectors.toSet());
    Map<Integer,String> map = IntStream.rangeClosed(1,5).boxed().collect(Collectors.toMap(Function.identity(),String::valueOf));

    list.add(8);
    set.add(8);
    map.put(8,"8");

    logger.debug("list: {}",list);
    logger.debug("set: {}",set);
    logger.debug("map: {}",map);

    //불변
    List<Integer> immutableList = IntStream.rangeClosed(1,5).boxed().collect(Collectors.toUnmodifiableList());
    Set<Integer> immutableSet = IntStream.rangeClosed(1,5).boxed().collect(Collectors.toUnmodifiableSet());
    Map<Integer,String> immutableMap = IntStream.rangeClosed(1,5).boxed().collect(Collectors.toUnmodifiableMap(Function.identity(),String::valueOf));
    
    //error
    immutableList.add(4);
}

image

✏️ Optional.orElseThrow()

자바8에서 Optional이 나왔는데, get()함수를 통해 값을 받을 수 있었다.

근데, get()을 호출하기 전에는 반드시 ifPresent()함수를 통해 검증절차를 진행해야했다.

1
2
3
4
Optional<String> result = getResult();
if (result.isPresent()) {
  System.out.println(result.get());
}

왜냐하면 만약 객체가 없는 경우 get()을 호출했다면 NoSuchElementException 예외가 터지기 때문이다.

인텔리제이에서는 이를 방지하기 위해 마우스를 올려놓으면 경고를 띄웠다.

image

때로는 의도적으로 예외를 발생시켜야하는 경우가 생기는데 이때는 경고들을 무시하기 위해서 @SuppressWarnings 어노테이션을 적절한곳에 삽입하기도 하였다.

자바 10으로 넘어오면서 orElseThrow()가 등장하였고, 이 함수를 통해 좀 더 깔끔하게 예외를 인지시킬 수 있게 되었다.

orElseThrow()get()함수와 이름만 다르고 동일하게 동작한다. 이름부터 예외를 던진다고 되어있기에 가독성이 증가하였고, 개발자의 의도를 파악할 수 있게 되었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//orElseThrow()의 구현 부분
public T orElseThrow() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}
//get()의 구현 부분
public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

✏️ Time-Based Release Versioning

java -version에 대한 내용이다.

java8버전부터 11버전까지 달라진 점을 나타낸다.

  • Java8
1
2
$ java -version
java version "1.8.0_291"
  • Java9
1
2
$ java -version
java version "9.0.4" //가독성이 좋게 버전을 보여줌
  • Java10
1
2
$ java -version
java version "10.0.2" 2018-07-17 //배포날짜를 보여줌
  • Java11
1
2
$ java -version
java version "11.0.11" 2021-04-20 LTS // Long-Term Support가 추가됨

✏️ Parallel Full GC for G1

Java9에서는 G1 가비지 컬렉터를 사용했다. 이전에는 병렬 컬렉터를 사용했는 G1으로 바뀌면서 병렬적으로 수행하지 못했다. Java9의 G1은 임시적으로 STW를 실행하여 응답시간의 지연이 조금 있었다.
Java10에서는 병렬적으로 G1 가비지 컬렉터를 수행할 수 있게 하였다.


✏️ Application Class-Data Sharing

Class-Data Sharing

JVM이 뜰 때, JDK 클래스 라이브러리를 읽고 클래스들을 추출하고 스펙에 명시된 바이너리 폼으로 바꿔버린다. 그리고 메인 메모리에 JVM 프로세스를 올리는데

하나의 컴퓨터에서 JVM을 여러개 띄워버리면 저 방식을 그대로 똑같이 반복할 것이다. 이는 시간적으로나 메모리적으로도 부담이 갈 수 밖에 없다.

image

여러개의 JVM을 띄울때

Class-Data Sharing(CDS)는 두가지 목표가 있는데

  • JVM이 띄워질때까지의 시간을 줄이기
  • JVM의 메모리를 줄이기

CDS는 다음과 같이 동작한다.

  1. java -Xshare:dump명령을 통해 classes.jsa(현재 아키텍처의 클래스 라이브러리를 이진 형식으로 나타낸 파일)을 만든다. 이를 사용하여 JVM이 시작될 때 클래스 라이브러리를 메모리에 적재하는 시간을 줄일 수 있게 해준다.
  2. JVM이 시작될 때 운영체제는 이 파일을 메모리 매핑 I/O를 이용하여 JVM 메모리에 매핑시킨다. 이렇게하면 jar또는 jmod파일을 로드하는 것보다 더 빠르게 동작한다. 또한 운영체제는 파일을 RAM에 한 번만 로드하므로 각 JVM 프로세스는 동일한 메모리 영역의 읽기 전용 뷰를 제공받게 된다. 이 덕분에 JVM 프로세스 간의 리소스를 지원하는 장점이 있다.

image

예제는 이 깃허브 주소을 참고하기 바란다.

위 스텝대로 진행하면

image

와 같은 절차를 거친다는 말과 같다.


✏️ Experimental Java-Based JIT Compiler

Java9 버전에서는 Graal Compiler라는 컴파일러가 테스트 되었는데, 이 컴파일러를 통해 네이티브 실행 파일(exe파일 같은거)를 컴파일 할 수 있게 되었다.

자바 10 버전에서는 Graal Compiler가 정식적으로 JIT컴파일러로 사용할 수 있고, Linux/x86플랫폼에서만 사용할 수 있다.

1
-XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler

이 명령어를 치면 된다.


✏️ Heap Allocation on Alternative Memory Devices

Java 힙을 일반적인 RAM 대신에 다른 메모리 장치(NV-DIMM와 같은 비휘발성)메모리에 할당할 수 있게 되었다.

이 방식을 사용하려면 운영체제가 파일시스템 경로를 제공해야한다. 즉, 접근이 가능해야한다는 것이다.

그리고 다음 Java 명령행에 옵션을 사용하여 추가할 수 있다.

1
-XX:AllocateHeapAt=<file>

이 방식을 통해 Java 힙을 메모리 대신 다른 메모리 장치에 할당할 수 있으므로 어플리케이션 성능을 향상시킬 수 있다.


✏️ Additional Unicode Language-Tag Extensions

Locale 객체에 다음과 같은 추가 정보를 저장할 수 있다.

  1. 지역 이름과 언어 이름의 정규화 형태
  2. 지역 이름과 언어 이름에 관련된 추가 메타데이터
  3. 지역 이름과 언어 이름에 대한 호환성 정보

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
@DisplayName("Locale 테스트")
void LocaleTest(){
    Locale locale = Locale.forLanguageTag("de-DE-u-cu-usd-fw-wed-tz-uslax");

    Currency currency = Currency.getInstance(locale);

    Calendar calendar = Calendar.getInstance(locale);
    DayOfWeek firstDayOfWeek = DayOfWeek.of((calendar.getFirstDayOfWeek() + 5) % 7 + 1);

    DateFormat dateFormat = DateFormat.getTimeInstance(LONG, locale);
    String time = dateFormat.format(new Date());

    System.out.println("currency       = " + currency);
    System.out.println("firstDayOfWeek = " + firstDayOfWeek);
    System.out.println("time           = " + time);
}

image

de-DE-u-cu-usd-fw-wed-tz-uslax 구문 해석을 해보자

  • de-De: 독일의 로케일정보다.
  • cu-usd: US달러를 의미한다.
  • fw-wed: 첫 주 수요일(wednesday)를 의미한다.
  • tz-uslax: 로스 엔젤레스의 타임존(tz)를 의미한다.

✏️ Garbage Collector Interface

Java9버전까지는 가비지 컬렉터 소스 코드의 일부가 Java 인터프리터와 C1, C2 컴파일러의 소스 코드 내부의 긴 if-else문에 있었다고 한다. 새로운 가비지 컬렉터를 구현하려면 개발자는 이러한 구문의 모든 위치를 알고 있어야 하고, 요구사항에 맞게 확장해야 했는데, 이번 Java10에서는 가비지 컬렉터 알고리즘을 인터프리터와 컴파일러로 부터 분리를 했다는 것이다.

이 인터페이스를 통해서 더 이상 인터프리터와 컴파일러에 의존성 없이 GC를 추가할 수 있게 되었다.


✏️ Root Certificates

Java9에서는 cacerts 키 저장소 파일에 루트 인증서가 포함되어 있지 않아서 SSL/TLS 기반의 기능을 쉽게 실행할 수 없었는데, Java10에서는 루트 인증서가 포함되어 SSL/TLS 기능을 사용할 수 있게 되었다.


결론

조금 더 많은 내용이 있지만 이해하기 어려운 내용들이므로

좀 더 자세한건 https://www.happycoders.eu/java/java-10-features/#ListcopyOf_SetcopyOf_and_MapcopyOf 이 글을 참고하길 바랍니다.

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