First element | Last element | |
List | list.get(0) | list.get(list.size() - 1) |
Deque | deque.getFirst() | deque.getLast() |
SortedSet | sortedSet.first() | sortedSet.last() |
LinkedHashSet | linkedHashSet.iterator().next() |
자바 컬렉션 프레임워크에서 순서를 가지는 시퀀스를 표현하는 컬렉션 유형 Sequenced Collections를 추가하였다.
Sequenced Collections는 요소들의 명시적인 순서를 가지는 컬렉션을 나타내며 컬렉션 프레임워크 내에서 요소의 순서를 명확하고 일관되게 표현할 수 있는 방법이 생겨났다.
interface SequencedCollection<E> extends Collection<E> {
// new method
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
기존 대칭키 암호화 방식의 문제점
전통적인 대칭키 암호화에서는 키를 무작위로 생성하고, 이 키를 안전하게 공유하기 위해 수신자의 공개 키로 암호화합니다. 그러나 이 방법은 데이터를 공개 키 암호화 알고리즘에 맞게 조정하기 위해 추가적인 ‘패딩’이 필요하며, 이 과정이 보안성을 증명하기 어렵고 보안에 취약할 수 있다.
KEM 도입
이런 문제를 해결하기 위해 자바에 도입된 새로운 암호화 방식이다. KEM은 대칭키를 직접 암호화하는 대신, 공개 키를 활용하여 관련 대칭 키를 유도하는 방식을 사용한다. 이 과정에서는 복잡한 패딩 절차가 필요 없으며, 결과적으로 보안이 강화된다.
대칭 키를 직접 공개 키로 암호화하는 대신, 공개 키를 이용하여 ‘대칭 키를 얻을 수 있는 방법’을 생성하고 수신자에게 보내 수신자는 이 방법을 통해 실제 대칭 키를 얻는다. 이 과정에서는 패딩이 필요하지 않으며, 이로 인해 더 단순하고 보안적으로 더 강력하다.
현재 사용되는 많은 공개 키 암호화 방식은 여러 수학적 문제에 기반을 둔다. 예를 들어 RSA 암호화 방식은 큰 소수를 찾는 문제에 기반한다.
현재 양자 컴퓨터의 개발이 이루어지고 있고 이는 현재 사용되고 있는 공개키의 크나큰 보안 위험 요소이다. KEM은 양자 공격을 방어하는 데 중요한 도구가 될 것이고 여러 보안 업체들이 이미 표준 KEM 표준화하는 API에 대한 필요성을 표명했다.
public final class KEM {
...
public static final class Encapsulator {
String providerName();
int secretSize(); // Size of the shared secret
int encapsulationSize(); // Size of the key encapsulation message
Encapsulated encapsulate();
Encapsulated encapsulate(int from, int to, String algorithm);
}
public static final class Decapsulator {
String providerName();
int secretSize(); // Size of the shared secret
int encapsulationSize(); // Size of the key encapsulation message
SecretKey decapsulate(byte[] encapsulation) throws DecapsulateException;
SecretKey decapsulate(byte[] encapsulation, int from, int to,
String algorithm)
throws DecapsulateException;
}
}
기존에 문자열을 만들 때 읽기 어려운 코드, 장확하고, 매개 변수에서 분리하여 소수성과 유형 불일치와 같은 여러 문제들이 존재한다.
String s = x + " plus " + y + " equals " + (x + y);
String s = new StringBuilder()
.append(x)
.append(" plus ")
.append(y)
.append(" equals ")
.append(x + y)
.toString();
String s = String.format("%2$d plus %1$d equals %3$d", x, y, x + y);
String t = "%2$d plus %1$d equals %3$d".formatted(x, y, x + y);
자바에서 새로운 템플릿 표현식이 추가되었고 개발자들이 안전하고 효율적으로 문자열을 구성할 수 있도록 지원한다.
사용법 : STR."\{field name|method|arithmetic}"
// Embedded expressions can be strings
String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan"); // true
// Embedded expressions can perform arithmetic
int x = 10, y = 20;
String s = STR."\{x} + \{y} = \{x + y}";
// Embedded expressions can invoke methods and access fields
String s = STR."You have a \{getOfferType()} waiting for you!";
String t = STR."Access at \{req.date} \{req.time} from \{req.ipAddress}";
String json = STR."""
{
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
}
""";
레코드 패턴을 통해 레코드 값을 분해하여 보다 정교한 데이터 작업을 가능하게 한다.
record Point(int x, int y) {}
자바 16부터 도입된 레코드는 데이터를 운반하는 데 최적화된, 불변의 객체를 정의하기 위한 짧고 간결한 방법을 제공
Java 16에서 확장된 instanceof 연산자의 타입 패턴으로 타입 캐스팅을 단순화하였다.
// Prior to Java 16
if (obj instanceof String) {
String s = (String)obj;
... use s ...
}
// As of Java 16
if (obj instanceof String s) { // s 패턴 변수
... use s ...
}
기존에 레코드 타입의 클래스를 instanceof 연산자로 사용하려면 다음과 같이 사용하였다.
// As of Java 16
record Point(int x, int y) {}
static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
이번에 도입되는 레코드 패턴은 레코드의 구성 요소를 패턴 변수로 할당이 가능해졌다. 이렇게 되어 추가적인 접근자 메서드 호출 없이 x, y 를 바로 사용할 수 있게 되어 코드가 더욱 간결해졌다.
// As of Java 21
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) { // x, y 패턴 변수
System.out.println(x+y);
}
}
Pattern Matching을 switch 문에서도 사용할 수 있도록 지원한다.
Pattern Matching
특정한 형식이나 구조를 가진 데이터를 인식하고, 그에 맞게 동작을 결정하는 방법
기존에는 switch 문에 제한된 타입의 값으로 열거형, 정수, 문자열만 가능했지만 이제는 객체 타입에 따라 분기를 가능하게 한다.
// Prior to Java 21
static String formatter(Object obj) {
String formatted = "unknown";
if (obj instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (obj instanceof Long l) {
formatted = String.format("long %d", l);
} else if (obj instanceof Double d) {
formatted = String.format("double %f", d);
} else if (obj instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
// As of Java 21
static String formatterPatternSwitch(Object obj) {
return switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> obj.toString();
};
}
널 타입에 대한 처리가 가능해져 따로 널 처리를 위한 코드를 따로 작성하지 않아도 된다.
// Prior to Java 21
static void testFooBarOld(String s) {
if (s == null) {
System.out.println("Oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
// As of Java 21
static void testFooBarNew(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
when이 추가되어 조건 식도 추가할 수 있다. 좀 더 세분화된 처리가 가능해졌다.
// As of Java 21
static void testStringNew(String response) {
switch (response) {
case null -> { }
case String s
when s.equalsIgnoreCase("YES") -> {
System.out.println("You got it");
}
case String s
when s.equalsIgnoreCase("NO") -> {
System.out.println("Shame");
}
case String s -> {
System.out.println("Sorry?");
}
}
}
레코드 패턴의 가독성을 개선하고 선언은 필요하지만 사용 되지 않은 변수를 식별하여 코드의 유지보수성을 향상시킨다.
불필요한 패턴이나 변수를 사용하지 않도록 하여, 코드를 간결하게 명확하게 만든다.
if (r instanceof ColoredPoint(Point(int x, int y), _)) { ... }
for (Order _: orders) {
}
try {
}
catch (NumberFormatException _) {
}
GC 순서
Serial GC → Parallel GC → Concurrent Mark-Sweep(CMS) GC → G1 GC → ZGC
weak generational hypothesis 이라는 가설은 대부분의 객체는 젊고 늙기 전에 GC에 의해 수거된다. 즉, 비교적 짧은 시간만 생존한다는 말이다.
이전 세대 GC에서는 Young Generation, Old Generation 영역을 나누어 효율적으로 관리하였지만 ZGC는
ZGC currently stores all objects together, regardless of age, so it must collect all objects every time it runs.
모든 객체를 Generation 영역 구분 없이 함께 저장하여 실행될 때 모두 동시에 수집되어야 한다. 가설의 이점을 활용 하지 않아 비효율적이다.
ZGC 또한 Generation 영역을 분리함으로써 young, old objects를 구분하여 관리하여 좀 더 효율적으로 수집할 수 있는 구조로 변경하였다.
가상 스레드의 도입 배경
고전적인 멀티스레딩 모델에서는 I/O 작업 중 많은 스레드가 Blocking 되면서 리소스가 낭비되었다. 이는 많은 메모리 사용과 CPU 시간의 비효율적 사용으로 이어졌다.
이러한 문제를 해결하기 위해 비동기 I/O로 Webflux와 같은 프레임워크를 사용해왔지만, 러닝 커브 존재와 동시에 비동기로 인해 단일 스레드로 처리하는 것이 아니어서 신경 써야 할 부분들이 존재했다.
가상 스레드 도입
가상 스레드는 위에서 언급한 문제를 해결하고 I/O 중심의 작업에 처리량을 증가시키기 위해 도입
가상 스레드 구조
기존 Java의 스레드 모델은 Java의 유저 스레드를 만들면 Java Native Interface(JNI)를 통해 커널 영역을 호출하여 OS가 커널 스레드를 생성하고 매핑하여 작업을 수행한다.
Virtual Thread는 기존 Java의 스레드 모델과 달리, 플랫폼 스레드와 가상 스레드로 나뉜다. 플랫폼 스레드 위에서 여러 Virtual Thread가 번갈아 가며 실행되는 형태로 동작한다.
가상 스레드의 성능 이점
가상 스레드 사용 시 유의사항
32비트 운영을 지원하는 Window 10이 2025년 10월에 종료할 예정이고 가상 스레드의 지원 하기에는 어려원 환경 등 여러 복합적인 이유로 인해서 더 현대적이고 성능이 우수한 64비트에 집중하고 32비트 점진적으로 폐지하는 것으로 결정하였다.
이번에는 32비트에서 빌드시에 오류 메시지를 발생시키는 빌드 시스템의 업데이트가 포함되었다.
자바 에이전트를 통해서 동적으로 애플리케이션 코드를 수정하는 것을 허용해왔고 이를 통해 모니터링을 관찰하는 방법이 많이 탄생하게 되었다.
문제점
다음과 같은 문제로 보안과 무결성을 해친다.
실행중인 JVM에 동적으로 agent가 적재되는 것을 경고하도록 수정이 되었고 이러한 경고는 향 후 릴리스에 에이전트를 지원하지 않는 것에 대해서 준비시키기는 것을 목표로 한다.