Java 8, 11, 17 그리고 Spring boot 3.0

Java는 오랜역사를 가진만큼 현재까지 여러버전이 출시되었다. 각 버전마다 큰 변화를 가지고 오기도 했는데 그 중 8, 11, 17 버전에서의 변화를 살펴보자.

Java 8

Java8은 가장 큰 변화가 생긴 버전이다. 전체적으로 현대식 프로그래밍 방식을 지원하기 위한 기능들이 많이 추가 되었다. 다음은 Java 8에서 변경된 내역이다. 하나씩 살펴보도록 하자.

  • JVM 메모리 구조 변경
  • interface 내 Default, Static Method 지원
  • 함수형 인터페이스 지원
  • 람다 표현식 추가
  • 메서드 참조 추가
  • Stream API 등장
  • Data, Time API의 추가
  • Optional 타입 등장


JVM 메모리 구조 변경

기존 JVM 메모리 구조 중 하나인 Heap 영역의 하위 영역인 PermGen(Permanent Gerneration) 영역이 제거되고 Metaspace 영역이 따로 추가되었다.

Metaspace는 Native Memory에 존재한다. Native Memory라 함은 OS에서 직접 관리되는 메모리 영역을 의미한다. 이 영역은 OS의 메모리 관리 기능에 의해 할당과 해제가 이루어진다.

Metaspace는 기존 PermGen 영역과 마찬가지로 클래스의 메타데이터, 클래스 로더 정보를 가지고 있다.

  • 클래스 메타데이터
    클래스(.class)의 구조, 특성에 대한 정보가 포함된다. 클래스의 이름, 상위 클래스와 인터페이스, 필드와 메소드의 구성, 어노테이션 등이 포함된다. JVM이 클래스를 로딩하고, 객체를 생성하고, 메소드를 호출하는 등의 작업에 필요하다.
  • 클래스 로더 정보
    클래스 로더에 대한 정보를 저장한다. 이 클래스 로더는 클래스(.class)를 로딩하고 정의하는 역할을 담당한다. 클래스 로더의 식별자, 계층 구조, 관련된 클래스들의 참조등을 저장하고 있다.

장점

  • JVM Heap 영역에서 분리되어 GC의 부담을 줄이는 효과
    단, class loader가 더 이상 사용되지 않는 경우에만 해당 클래스 로더와 관련된 메타데이터를 정리하기 위해 GC가 동작한다.
  • 환경에 맞추어 사이즈 조절 가능
    기존 PermGen의 경우 영역의 크기가 고정되어 있었다. 때문에 PermGen Out of Memory 오류가 발생할 수 있었다. Metaspace는 Native Memory를 사용하므로 이러한 오류를 방지하고, Metaspace 영역의 크기를 조절할 수 있다. Metaspace 크기의 조절로 클래스 로딩 및 언로딩의 빈도에 따라 효율적이게 동적으로 할당할 수 있게 되었고, 어플리케이션이 돌아가는 시스템의 가용 메모리에 따라 동적으로 크기를 조정해 메모리 사용을 최적화하고 다른 어플리케이션에 충분한 메모리 자원을 제공할 수도 있다. Metaspace의 크기를 지정하는 옵션으로는 아래와 같은 것들이 있다.
    -XX:MetaspaceSize 초기, 최소 사이즈
    -XX:MaxMetaspaceSize 최대 사이즈 등

💡 JVM Heap 영역
OS가 할당하는 메모리 영역 중 하나인 Heap영역에서 자바 프로그램의 동적 할당 메모리를 담당하는 부분이다. GC의 대상이 되는 영역으로 Java8 이상부터는 Young Generation과 Old Generation으로 나누어 자바 객체를 관리한다.


interface 내 Default, Static 메서드 추가

interface에 Default, Static 메서드를 사용할 수 있게 되었다. 덕분에 interface의 기능 확장과 코드 재사용성을 향상되었다.

Default 메서드는 인터페이스 내에서 메서드의 기본 구현을 제공하는 것을 말한다. 이를 통해 기존 interface를 구현한 클래스들에 대해 호환성을 유지하면서 새로운 기능을 추가할 수 있다.

Static 메서드는 정적 메서드이다. interface와 관련된 유틸리티 메서드나 도우미 메서드를 제공할때 유용하다. 인터페이스를 구현한 클래스의 인스턴스와는 관련이 없다.

interface MyInterface {
    // default 메서드
    default void defaultMethod() {
        System.out.println("This is a default method.");
    }
    
    // static 메서드
    static void staticMethod() {
        System.out.println("This is a static method.");
    }
}


함수형 인터페이스 지원

하나의 추상 메소드를 정의하는 인터페이스가 추가되었다. 추상 메서드 외 Default, Static 메서드의 수는 상관없다. 또한, 많이 사용하는 함수형 인터페이스를 기본적으로 몇 가지 지원해준다.

@FunctionalInterface 어노테이션을 통해 컴파일 타임에 함수형 인터페이스의 규칙을 지키는지 확인할 수 있다.

다음은 기본으로 지원하는 함수형 인터페이스들이다.

  • Consumer<T>: 입력값(T)을 받아 처리만 하고 반환하지 않는 함수를 표현
  • Supplier<T>: 아무런 입력값 없이 결과값(T)을 제공하는 함수를 표현
  • Function<T, R>: 입력값(T)을 받아 결과값(R)을 반환하는 함수를 표현
  • Predicate<T>: 입력값(T)을 받아 boolean 값을 반환하는 함수를 표현
  • Runnable<T>: 입력값도 결과값도 없이 실행만 하는 함수를 표현


람다 표현식 추가

메소드를 하나의 식으로 표현할 수 있다. 하지만 엄밀하게 말하자면 메소드를 가진 객체를 생생해내는 것이다. 주로 함수형 인터페이스의 익명 객체를 대체하기 위해 람다식을 사용한다.

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");

// 문자열을 길이 순서대로 정렬
Collections.sort(names, (s1, s2) -> s1.length() - s2.length());


메소드 참조 추가

람다가 필요한 곳에 기존의 메서드를 사용하기 위한 기능이다. 정적메서드, 인스턴스 메서드, 생성자 모두 참조할 수 있다. Class::메서드이름 의 형식으로 사용한다.


Stream API

시퀀스를 처리하기 위한 작업을 표현하는 인터페이스이다. Stream API는 연속된 요소들을 내부적으로 반복작업을 수행한다. 또한 최종연산(forEach, collect, reduce 등)이 수행되기 전까지 중간연산이 실제로 수행되지않는 지연 연산을 지원한다. 이를 통해 효율적인 연산 체인을 구성할 수 있다.

Stream API를 통해서 데이터 처리 로직을 선언적으로 표현할 수 있어 가독성과 유지보수성이 향상된다.


Date, Time API 추가

이전 java.util.Datejava.util.Calendar API보다 향상된 기능을 제공한다. 날짜와 시간을 쉽게 다룰 수 있고 다양한 연산과 변환 기능을 제공한다.


Optional 추가

null을 대체하고 NullPointerException을 방지하기 위해 만들어졌다. 값이 없을 수 있다는 것을 명시적으로 표현하여 가독성을 높여준다.


Java 11

기본적으로 편리한 기능들이 많이 추가되었다.

  • 새로운 GC인 ZGC 추가
  • JShell 등장 - 메인 Method 없이 자바 코드를 넣고 즉석에서 실행 가능
  • HTTP2 Client 추가 (http2를 구현하는 클라이언트 API 제공)
  • Reactive Stream 제공 (비동기 스트림 처리 지원 API 추가)
  • var 키원드 제공(로컬 변수 선언 시 타입 추론을 이용한 변수 선언이 가능)
  • 문자열 Method 추가(isBlank, lines, repeat, strip 등)


Java 17

Java11과 비교해서 드라마틱한 변화가 일어나지는 않았다.

  • 다중문자열 추가 (텍스트 블록 기능)
  • Record Data Class 추가 (immutable 객체를 생성하는 클래스 - toStrig, equals, hashCode 메서드에 대한 구현을 자동 제공)
  • Sealed Class 추가 (지정한 클래스 외 상속 불가능)
  • Stream에 toList 추가 (Collectors 없이 List로 변환 가능)
  • Switch문을 식으로 사용가능 (Switch문이 값을 반환하며 람다 스타일 구문을 사용할 수 있다. - like 코틀린)


Spring Boot 3.0 (Spring Framework 6)

22년 말에 출시된 Spring Boot 3.0은 Java17 사용이 필수이다. 사실상 Java17에서의 큰 변화가 없었기 때문에 Java17을 사용하는 이유는 Spring Boot 3.0을 사용하기 위해서가 아닌가 싶다.

  • 상표권 문제로 JavaEE → Jakarta EE로 변환
    javax.* → jakarta.*로 변경되어야 함(javax.sql.DataSource 같은 클래스는 바뀌지 않았으므로 조심!)
  • Spring Native 공식지원
    클라우드 환경에서 배포를 쾌적하게 하기위한 프로젝트이다. JVM을 이용하지않고 바로 기계어로 변환(Native bianry)로 만들어서 빠르게 배포 - 실행할 수 있다는 장점을 가진다. 빌드는 GraalVM을 통해 native image를 만들고 해당 이미지를 실행하는 방식이다. 기존의 JVM에서 JIT 방식으로 실행했을 때 실행시간, 메모리 사용량이 높은 것에 비해 더 빠르게 실행된다.

참고

카테고리:

업데이트:

댓글남기기