박종훈 기술블로그
close menu

JDK-8382312 기여기: instanceKlass의 dead code 제거

JDK-8382312에 기여해보기로 했다. HotSpot JVM에서 Java 클래스를 표현하는 InstanceKlass의 헤더 파일에 선언만 남아있던 dead code 2건을 제거하는 작업이다.

이슈의 제목은 다음과 같다.

Cleanup instanceKlass dead code

이슈 내용

hotspot-dev 메일링 리스트에서 Peter Firmstone가 instanceKlass.hpp에 선언만 있고 구현이 없는 메서드를 발견했다는 보고로부터 시작되었다.

Just noticed, the method [is_shareable] declared in the header, but implementation was removed, I think this is left over dead code.

확인해보니 JDK-8265602에서 구현(.cpp)은 삭제되었으나, 헤더(.hpp)의 선언이 남아있었다. is_shareable() 외에 log_to_classlist()도 같은 상태였다.

// 선언만 남아있는 dead code (instanceKlass.hpp)
bool is_shareable() const;
void log_to_classlist() const;

배경 지식

Klass와 InstanceKlass

JVM 내부나 Java 관련 서적을 보면 Klass라는 단어가 자주 등장한다. C++에도 class라는 키워드가 있기 때문에, Java의 클래스를 표현하는 C++ 클래스의 이름으로 class를 사용할 수 없다. 그래서 HotSpot에서는 이를 구별하기 위해 Klass라는 이름을 사용한다.

Klass는 JVM 내부에서 Java 타입의 메타데이터를 표현하는 C++ 클래스다. Metadata를 상속하며, 주요 계층 구조는 다음과 같다.

Metadata
  └── Klass
        ├── InstanceKlass
        │     ├── InstanceMirrorKlass
        │     ├── InstanceRefKlass
        │     └── InstanceClassLoaderKlass
        └── ArrayKlass
              ├── TypeArrayKlass             (기본형 배열: int[], byte[] 등)
              └── ObjArrayKlass              (참조형 배열: String[], Object[] 등)

InstanceKlass의 하위 클래스들은 각각 특수한 Java 타입을 처리하기 위해 존재한다.

  • InstanceMirrorKlassjava.lang.Class 객체를 표현한다. Java에서 SomeClass.classgetClass()로 얻는 Class 객체가 이것이다. 일반 클래스의 인스턴스와 달리, Class 객체는 해당 클래스의 static 필드를 직접 보유하기 때문에 별도의 Klass로 관리된다.
  • InstanceRefKlassjava.lang.ref.Reference의 하위 타입(SoftReference, WeakReference, PhantomReference)을 표현한다. 이 타입들은 GC와 밀접하게 연관되어 있어, GC가 참조 객체의 referent 필드를 특별하게 처리해야 하므로 별도의 Klass로 분리되어 있다.
  • InstanceClassLoaderKlassjava.lang.ClassLoader를 상속받은 클래스를 표현한다. GC가 클래스 로더와 그것이 로딩한 클래스들 사이의 관계를 추적할 때 사용된다.

이번 이슈에서 다루는 InstanceKlass는 이 중 가장 핵심적인 클래스로, Java 클래스 하나가 JVM에 로딩되면 InstanceKlass 객체가 생성되어 필드, 메서드, 부모 클래스, 클래스 로더 등의 정보를 관리한다.

구현 없는 선언이 문제가 되지 않는 이유

C++에서 헤더의 선언(declaration)은 “이런 함수가 있다”는 약속이고, 실제 코드(definition)는 .cpp에 작성한다. 선언만 있고 구현이 없어도, 아무도 호출하지 않으면 에러가 나지 않는다. 누군가 호출하는 순간 링크 에러(undefined reference)가 발생한다.

선언과 정의의 분리, 링커의 심볼 해석 과정에 대해서는 이전 기여기(JDK-8379873)에서 자세히 다루었다.

제거된 메서드들의 원래 역할

is_shareable()

해당 클래스가 CDS(Class Data Sharing) 아카이브에 공유될 수 있는지 판단하는 함수였다. (CDS에 대해서는 이전 글(JDK-8381924)에서 정리한 바 있다.)

이 함수는 3가지 조건을 확인한다.

  1. 클래스 로더가 공유 가능한 타입인가 (is_sharing_possible)
  2. hidden 클래스가 아닌가 (is_hidden)
  3. 패치된 모듈이 아닌가 (module()->is_patched())

그리고 log_to_classlist() 내부에서만 호출되고 있었다.

log_to_classlist()

클래스 로딩 시 -XX:DumpLoadedClassList 옵션으로 지정한 파일에 클래스 이름을 기록하는 함수였다. is_shareable()로 공유 가능 여부를 확인한 뒤, 가능하면 클래스 이름을 한 줄씩 파일에 썼다.

JDK-8265602에서 구현이 제거된 이유

CDS 아카이브 덤프 방식을 먼저 짚고 넘어가면 다음과 같다.

  • JDK 12 이후: 기본 클래스에 대해서는 JDK 빌드 시 기본 아카이브(classes.jsa)가 자동 생성된다. -Xshare:auto가 기본값이다.
  • 애플리케이션 클래스: 여전히 수동 설정이 필요하다 (-XX:DumpLoadedClassList + -Xshare:dump).

JDK-8265602가 개선한 것은 이 수동 과정에서 커스텀 클래스 로더의 클래스도 classlist에 기록되도록 한 것이다.

기존 log_to_classlist()는 부트/앱/플랫폼 클래스 로더의 클래스만 단순히 이름을 기록했는데, 커스텀 로더까지 지원하려면 로더 타입, 소스 위치 등 더 많은 정보를 기록해야 했다. 그래서 로직이 ClassListWriter라는 별도 클래스로 이동되었다.

// Before (instanceKlass.cpp에서 직접 처리)
log_to_classlist();

// After (ClassListWriter로 위임)
if (ClassListWriter::is_enabled()) {
    ClassListWriter::write(this, cfs);
}

is_shareable()의 검증 로직은 어디로 갔나

ClassListWriter::write_to_stream() 안으로 흡수되었으며, 더 많은 조건이 추가되었다.

순서검증 내용비고
1클래스 로더 검증기존 is_sharing_possible() 대체 + 커스텀 로더 조건부 허용으로 확장
2동적 생성 클래스 제외_ClassSpecializer_generateConcreteSpeciesCode, __ 접두사 등
3부모/인터페이스 기록 여부 확인부모나 인터페이스에 id가 부여되어 있는지 확인
4hidden 클래스 제외기존 is_shareable()와 동일
5패치된 모듈 제외기존 is_shareable()와 동일

수정 내용

instanceKlass.hpp에서 다음 두 선언을 제거했다.

// 제거됨
// Check if the class can be shared in CDS
bool is_shareable() const;

// 제거됨
// log class name to classlist
void log_to_classlist() const;

마무리

이번에도 변경된 코드는 단순하다. 하지만 히스토리를 따라가면서 Klass 계층 구조에 대해 알 수 있었고, 지난주(JDK-8381924)에 이어 CDS에 대해서도 조금 더 깊이 들여다볼 수 있었다.

categories: 개발

tags: JDK , OpenJDK , HotSpot , CDS , 오픈소스 기여