[자바] 7. 인터페이스 : default·static 메서드와 Metaspace (Java 8)

자바 버전 여행 7편 - default·static 메서드와 Metaspace

작성자

카테고리:

* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

(자바 버전 여행 ⑦)

자바 8의 람다·스트림은 화려해서 다들 안다. 그런데 그 화려함을 떠받치려고 조용히 바뀐 것들이 있다. 앞선 편들에 이어, 인터페이스에 구현을 넣을 수 있게 된 것, 그리고 JVM 내부에서 PermGen이 사라진 것. 자바 8 여행의 마지막 편은 이 두 가지를 짚는다.

인터페이스에 왜 구현을 넣게 됐나

스트림을 떠올려 보자. 자바 8은 기존 List·Collection 인터페이스에 stream(), forEach() 같은 메서드를 추가해야 했다. 그런데 인터페이스에 추상 메서드를 하나 추가하면, 그걸 구현한 세상의 모든 클래스가 컴파일 에러가 난다. 하위호환이 깨지는 것이다.

그래서 나온 게 default 메서드다. 인터페이스에 기본 구현을 함께 주면, 기존 구현체는 손대지 않아도 그 메서드를 물려받는다. 덕분에 자바는 인터페이스를 깨지 않고 기능을 확장할 수 있었다.

public interface Greeter {
    String name();

    // default 메서드 — 기본 구현을 인터페이스가 제공
    default String greet() {
        return "Hello, " + name();
    }

    // static 메서드 — 인터페이스에 딸린 유틸
    static Greeter of(String n) {
        return () -> n;
    }
}

Greeter g = Greeter.of("Cozmo");
g.greet();   // "Hello, Cozmo"  (구현 안 해도 동작)

다이아몬드 문제 — 두 default가 부딪히면

두 인터페이스가 같은 이름의 default 메서드를 주고, 한 클래스가 둘 다 구현하면 충돌한다. 이때 컴파일러는 개발자에게 명시적으로 고르라고 요구한다.

interface A { default String hi() { return "A"; } }
interface B { default String hi() { return "B"; } }

class C implements A, B {
    // 안 고르면 컴파일 에러 → 직접 지정
    public String hi() { return A.super.hi(); }
}

보이지 않는 큰 변화 — PermGen이 사라졌다

문법 말고 JVM 내부에도 큰 변화가 있었다. 자바 8 이전에는 클래스 메타데이터(클래스 정보, 메서드 정보 등)를 PermGen이라는 고정 크기 영역에 담았다. 크기가 고정이라, 클래스를 많이 로딩하는 서버에서 java.lang.OutOfMemoryError: PermGen space가 악명 높았다.

자바 8은 PermGen을 없애고 Metaspace로 바꿨다. Metaspace는 힙이 아니라 네이티브 메모리를 쓰고, 기본적으로 필요한 만큼 늘어난다. 그래서 PermGen 특유의 그 에러는 사실상 사라졌다.

항목PermGen (~7)Metaspace (8~)
위치JVM 힙 안네이티브 메모리
크기고정(-XX:MaxPermSize)기본 자동 확장
대표 에러OOM: PermGen space(거의 사라짐)

주의할 점도 있다. 자동 확장이라 방치하면 클래스로더 누수 시 네이티브 메모리를 계속 먹을 수 있다. 그래서 실무에서는 -XX:MaxMetaspaceSize로 상한을 걸어두기도 한다.

직접 해보기 — default 메서드와 다이아몬드, 그리고 Metaspace

default 메서드의 상속, 충돌 해결, 그리고 Metaspace 옵션까지 손으로 확인하자.

  1. default 메서드가 있는 인터페이스를 만들고, 구현 클래스에서 오버라이드 없이 호출되는지 본다.
  2. 같은 이름 default를 가진 인터페이스 둘을 한 클래스에 구현해 컴파일 에러를 만든 뒤 X.super.메서드()로 해결한다.
  3. -XX:MaxMetaspaceSize 옵션을 걸고 실행되는지 확인한다.
interface A { default String hi() { return "A"; } }
interface B { default String hi() { return "B"; } }

class C implements A, B {
    // 안 고르면 컴파일 에러 → 명시적으로 선택
    public String hi() { return A.super.hi() + B.super.hi(); }
}

// new C().hi()  ->  "AB"
// 실행: java -XX:MaxMetaspaceSize=64m Main

실행해 보면 이렇게 나온다.

AB

여기서 조금씩 바꿔 보자.

  • C에서 hi() 오버라이드를 지워 다이아몬드 충돌 컴파일 에러를 직접 본다.
  • A.super.hi()만 반환하도록 바꿔 “A”만 나오게 한다.

한 걸음 더 — java -XX:+PrintFlagsFinal -version | grep -i metaspace로 Metaspace 관련 기본값들을 살펴보자.

✅ 확인 — default 메서드가 왜 필요했는지(하위호환)와 PermGen→Metaspace 변화를 설명할 수 있으면 자바 8 편은 끝이다.

default 메서드의 규칙 — 언제 override하나

두 인터페이스가 같은 이름의 default를 주면(다이아몬드), 컴파일러가 구현 클래스에 override를 강제한다. 그 안에서 InterfaceName.super.method()로 어느 쪽을 쓸지 고른다. 규칙 하나 더 — 클래스 메서드가 인터페이스 default를 항상 이긴다(‘클래스가 인터페이스를 이긴다’). static 메서드도 인터페이스에 넣을 수 있게 됐지만, 이건 상속되지 않는다.

interface A { default String hi() { return "A"; } }
interface B { default String hi() { return "B"; } }
class C implements A, B {
    public String hi() { return A.super.hi(); }   // 충돌 → 명시적으로 선택
}

default를 남용하지 마라

default는 ‘기존 인터페이스에 새 메서드를 추가하되 구현체를 안 깨뜨리기‘ 위한 장치다. 스트림을 도입할 때 Collection에 stream()을 더한 게 대표적이다. 이걸 다중상속 흉내로 남용하면 계층이 헷갈린다. 원래 목적 — 하위호환을 지키며 인터페이스를 진화시키기 — 에 맞게 쓰는 게 좋다.

PermGen → Metaspace, 무엇이 달라졌나

자바 8에서 PermGen이 사라지고 Metaspace로 대체됐다. 클래스 메타데이터가 고정 크기 PermGen(자주 터지던 OutOfMemoryError: PermGen space)이 아니라, 네이티브 메모리의 Metaspace(기본 무제한, 필요 시 확장)에 저장된다. 그래서 그 악명 높은 PermGen OOM이 역사 속으로 사라졌다.

Metaspace도 무한은 아니다

대신 주의할 게 생겼다. 클래스를 무한정 로드하면(동적 클래스 생성·잦은 리로드) 이제 Metaspace가 네이티브 메모리를 먹는다. 그래서 필요하면 -XX:MaxMetaspaceSize로 상한을 둔다. ‘OutOfMemoryError: Metaspace‘를 만난다면 클래스 로딩이 새는 지점을 의심하자. 자바 8의 화려한 문법 뒤에는 이런 JVM 내부 정리가 조용히 함께 있었다.

여기까지가 자바 8이다. 람다·스트림·Optional·java.time, 그리고 오늘의 인터페이스·Metaspace까지 — 지금 쓰는 자바의 뼈대가 대부분 이때 놓였다. 다음 모듈부터는 자바 9~11로 넘어가, 잘 알려지지 않은 모듈 시스템과 자바 11이 정리한 것들을 다룬다.

자주 묻는 질문 (FAQ)

default 메서드는 왜 생겼나요?

기존 인터페이스에 메서드를 추가해도 구현체가 깨지지 않게 하려고요. 하위호환을 지키며 인터페이스를 확장할 수 있습니다.

PermGen OutOfMemory는 이제 안 나나요?

자바 8부터 PermGen이 Metaspace(네이티브 메모리·자동 확장)로 바뀌어 그 특유의 오류는 거의 사라졌습니다. 필요하면 상한을 걸 수 있습니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다