OpenJDK 소스 직접 빌드해보기

OpenJDK 소스 구조 분석 및 컴파일 과정에서 마주친 빌드 이슈와 JVM 내부 모듈의 역할 정리

#JDK#OpenJDK#JVM

뭘 하는가

https://github.com/openjdk/jdk

해당 레포지토리에서 클론하던지 다운로드 하여 jdk를 로컬에서 빌드한 후, 내부 구조를 디버깅해보기

목적, JDK 구조

목적은 JDK 내부 구조를 살펴보고 싶어서이다. 더 구체적인 목표에 맞춰서 살펴봐야 좋을 거 같은데, 미리 정리해두고 하나씩 살펴보려고 한다.

1. javac, APT의 동작 원리

- `src/jdk.compiler/share/classes/com/sun/tools/javac/processing`
  - `JavacProcessingEnvironment.java` : APT가 실행되는 전체 환경을 관리하는 클래스, 어노테이션 프로세서(Processor)를 로드하고 실행하는 역할
  - `JavacRoundEnvironment.java` : 각 컴파일 라운드(round)에 대한 정보를 제공하는 클래스
  - `Messager.java` : 어노테이션 프로세서가 컴파일러에 메시지(경고, 오류 등)를 보낼 때 사용하는 인터페이스
  - `Filer.java` : 어노테이션 프로세서가 새로운 소스 파일이나 리소스 파일을 생성할 때 사용하는 인터페이스
- `src/jdk.compiler/share/classes/com/sun/tools/javac/api` : javac 컴파일러를 프로그래밍 방식으로 호출하는 데 사용되는 API들
  - JavacTool.java : 컴파일러의 진입점, APT 설정도 여기서
- `src/jdk.compiler/share/classes/com/sun/tools/javac/main` : 컴파일러의 메인 로직
  - `JavaCompiler.java` : AST(Abstract Syntax Tree) 생성, 타입 체크, 코드 생성 등 컴파일의 모든 단계가 이 클래스의 메서드들을 통해 실행된다.
  • APT는 JavacProcessingEnvironmentProcessorFiler 등을 통해 작동한다.

2. GC 모듈화, 커스텀 GC, G1, ZGC, Shenandoah

-`src/hotspot/share/gc/`
    - `g1`, `z`, `shenandoah`, `parallel`, `serial` → 각각 GC 구현
    - `shared` → GC 인터페이스 및 공통 로직 (`gcInterface`, `collectedHeap.*` 등)
  • CollectedHeap이 모든 GC의 추상 인터페이스
  • G1CollectedHeap, ZHeap 등의 하위 클래스가 실제 구현이다.
  • JDK 10 이후 GC 인터페이스 구조가 정리되었고, 플러그인처럼 GC를 교체할 수 있음
  • 원한다면 구현해서 넣어놓고 커스텀 GC 개발도 이론상 가능

3. 클래스로더, CDS 구조 이해

-`src/java.base/share/classes/java/lang/ClassLoader.java`
-`src/hotspot/share/classfile/`
    - `classLoader.*` → 클래스 로딩 과정의 네이티브 처리
    - `systemDictionary.cpp` → JVM이 클래스 로딩/캐시하는 핵심
    - `classFileParser.*` → `.class` 파일 분석기
  • Java 레벨: defineClass, findClass, ClassLoader 상속 전략
  • JVM 내부: SystemDictionary는 전역 클래스 테이블
  • CDS는 클래스 메타데이터를 미리 dump하여 빠르게 로딩

4. JIT 컴파일러, C1, C2, Graal, JVMCI 구조 분석

-`src/hotspot/share/compiler/`
    - `compilerDefinitions.cpp`, `compilerDirectives.*`
-`src/hotspot/share/opto/` → C2 최적화 컴파일러
-`src/hotspot/share/c1/` → C1 컴파일러
-`src/jdk.internal.vm.compiler` → Graal 컴파일러
-`src/jdk.internal.vm.ci` → JVMCI: 외부 컴파일러(Graal 등)를 JVM과 연결
  • AbstractCompiler → C1, C2, Graal의 공통 인터페이스이다.
  • CompileBroker → 컴파일 요청 처리
  • JVMCI는 Java로 작성된 컴파일러(Graal 등)를 JVM에 연결하는 다리 역할

5. AOT 컴파일 & Substrate VM (GraalVM)

서브스트레이트 VMGraalVM의 한 요소이다. 사전 컴파일된 네이티브 코드를 핫스팟 가상 머신 없이 실행하는 기술임. 독자적인 예외 처리, 스레드 및 메모리 관리, 자바 네이티브 인터페이스 접근 메커니즘을 갖췄다. 이로 인해 VM의 메모리 사용량과 시작 속도도 굉장히 내려갔음.. 이 부분을 보려면 GraalVM 의 저장소를 직접 보는게 좋을 것 같다.

https://github.com/oracle/graal 특히 substratevm, compiler, sdk 디렉터리 위주로

  • Graal은 Truffle (동적 언어 실행 프레임워크)도 지원
  • Substrate VM은 예외처리, GC, 클래스 로딩 등 독자 구현
  • native-image 도구가 어떻게 Graal로 코드를 AOT 컴파일하는지 추적

6. Project Loom: 가상 스레드

개인적으론 JVM이 가상 스레드를 어떻게 스케줄링하는지가 궁금하긴 함.

-`src/java.base/share/classes/java/lang/VirtualThread.java`
- `src/java.base/share/classes/jdk/internal/vm/Continuation.java`
-`src/hotspot/share/runtime/continuation.*`
  • Continuation은 가상 스레드의 스택 상태를 저장/복원하는 핵심 구조
  • VirtualThreadThread를 상속하고 JVM에서 특별하게 처리
  • FiberScheduler와 스케줄링 전략은 JVM의 osThread.cpp, thread.cpp 쪽에도 존재하는듯??

7. 추가 참고 자료

HotSpot의 구조를 공식적으로 요약 https://wiki.openjdk.org/display/HotSpot

GraalVM 공식문서 https://www.graalvm.org/latest/introduction/

Loom 프로젝트 공식 위키 https://wiki.openjdk.org/display/loom/Main

JDK 빌드하기

환경

  • macOS (M4 칩)
  • Xcode 16.4
  • JDK 21을 빌드
  • 로컬 JDK 21

참고로 17-35는 MacOS에서 빌드가 어려운 공식 이슈있음

error: invalid integral value '16-DMAC_OS_X_VERSION_MIN_REQUIRED=110000' in '-mstack-alignment=16-DMAC_OS_X_VERSION_MIN_REQUIRED=110000'

https://bugs.openjdk.org/browse/JDK-8272700 https://github.com/openjdk/jdk/commit/d007be0952abdc8beb7b68ebf7529a939162307b

해당 플래그 처리가 붙어서 되어버리는 현상

과정

1. OpenJdk 소스코드 받기

git clone https://github.com/openjdk/jdk.git

버전이 명시하려면 git checkout jdk-17+35 와 같이 태그나 브랜치를 체크아웃 아니면 해당 태그로 찾아서 직접 다운로드

2. macOS 기준으로 Homebrew 설치

brew install autoconf
xcode-select --install

3. Configure

bash configure

하면 되어야 정상인데, 안되는 경우

  • Xcode 설치 안됨 → xcode-select --install
  • freetype 없음 → brew install freetype
  • pkg-config 없음 → brew install pkg-config
  • xcode-select가 SDK 경로를 제대로 못 찾을 때 아래 옵션 추가
    • bash configure --with-sysroot=$(xcrun --sdk macosx --show-sdk-path)
configure: error: XCode tool 'metal' neither found in path nor with xcrun

metal이라는 Xcode 툴체인이 PATH에도 없고, xcrun으로도 찾을 수 없다는 에러

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer 위 커맨드로 입력한 후 다시 ㄱㄱ

4. build

make

하면 됨

xattr: [Errno 13] Permission denied: '/Users/mac/Documents/jdk-jdk-17-35/build/.../jmxremote.password.template'

권한 없는 경우 전부 권한 처리 해주면 되는데, sudo chmod -R u+w /Users/mac/Developer/jdk-jdk-21-35

저는 귀찮아서 sudo make 했습니다..

  1. zlib 관련 네이티브 코드(libzip/zutil.c)에서 발생한 매크로 충돌 및 시스템 헤더 재정의 문제
=== Output from failing command(s) repeated here ===
* For target support_native_java.base_libzip_zutil.o:
In file included from /Users/mac/Developer/jdk/src/java.base/share/native/libzip/zlib/zutil.c:32:
/Users/mac/Developer/jdk/src/java.base/share/native/libzip/zlib/zutil.h:194:11: warning: 'OS_CODE' macro redefined [-Wmacro-redefined]
  194 | #  define OS_CODE 19
      |           ^
/Users/mac/Developer/jdk/src/java.base/share/native/libzip/zlib/zutil.h:165:11: note: previous definition is here
  165 | #  define OS_CODE  7
      |           ^
In file included from /Users/mac/Developer/jdk/src/java.base/share/native/libzip/zlib/zutil.c:34:
In file included from /Users/mac/Developer/jdk/src/java.base/share/native/libzip/zlib/gzguts.h:45:
In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/include/stdio.h:61:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.5.sdk/usr/include/_stdio.h:318:7: error: expected identifier or '('
  318 | FILE    *fdopen(int, const char *) __DARWIN_ALIAS_STARTING(__MAC_10_6, __IPHONE_2_0, __DARWIN_ALIAS(fdopen));
      |          ^
/Users/mac/Developer/jdk/src/java.base/share/native/libzip/zlib/zutil.h:171:33: note: expanded from macro 'fdopen'
  171 | #        define fdopen(fd,mode) NULL /* No fdopen() */
   ... (rest of output omitted)

* All command lines available in /Users/mac/Developer/jdk/build/macosx-aarch64-server-release/make-support/failure-logs.
=== End of repeated output ===

fdopen이라는 표준 시스템 함수가 zlib 내부 매크로로 재정의되었는데, 이것이 macOS SDK의 stdio.h 안의 진짜 fdopen() 함수와 충돌해서 컴파일이 깨진 상황임..

해결 시스템 헤더보다 zlib 헤더가 먼저 포함되도록 #undef fdopen을 넣는 커스텀 패치 -> 직접 소스코드 일부 수정

수정 대상 src/java.base/share/native/libzip/zlib/zutil.h

define fdopen(fd, mode) NULL 부분 전체 주석처리

  1. libpng 라이브러리 컴파일 과정에서 헤더 파일이 누락
=== Output from failing command(s) repeated here ===

* For target support_native_java.desktop_libsplashscreen_png.o:

In file included from /Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/png.c:42:

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h:552:16: fatal error: 'fp.h' file not found

  552 | #      include <fp.h>

      |                ^~~~~~

1 error generated.

* For target support_native_java.desktop_libsplashscreen_pngerror.o:

In file included from /Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngerror.c:47:

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h:552:16: fatal error: 'fp.h' file not found

  552 | #      include <fp.h>

      |                ^~~~~~

1 error generated.

* For target support_native_java.desktop_libsplashscreen_pngget.o:

In file included from /Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngget.c:43:

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h:552:16: fatal error: 'fp.h' file not found

  552 | #      include <fp.h>

      |                ^~~~~~

1 error generated.

* For target support_native_java.desktop_libsplashscreen_pngmem.o:

In file included from /Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngmem.c:48:

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h:552:16: fatal error: 'fp.h' file not found

  552 | #      include <fp.h>

      |                ^~~~~~

1 error generated.



* All command lines available in /Users/mac/Developer/jdk/build/macosx-aarch64-server-release/make-support/failure-logs.

=== End of repeated output ===

fp.h는 오래된 Mac OS 또는 특정 컴파일러 환경에서 사용되던 부동소수점 관련 헤더 파일이라고 한다... 최신 macOS 및 Xcode 환경에서는 더 이상 사용되지 않거나 다른 이름으로 대체되었다. OpenJDKlibpng 코드는 이 헤더가 존재한다고 가정하고 컴파일을 시도하지만, 실제 시스템에는 없기 때문에 오류가 발생하는 것

해결 fp.h 헤더를 포함하는 코드를 비활성화 시키기

수정 대상 src/java.desktop/share/native/libsplashscreen/libpng/pngpriv.h # include <fp.h> 부분을 찾아서, 로그에 따르면 552번째 줄 근처.. 해당 부분을 주석처리 하기

  1. libpng 라이브러리 컴파일 과정에서 함수 누락
=== Output from failing command(s) repeated here ===

* For target support_native_java.desktop_libsplashscreen_png.o:

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/png.c:2970:16: error: call to undeclared library function 'frexp' with type 'double (double, int *)'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]

 2970 |          (void)frexp(fp, &exp_b10); /* exponent to base 2 */

      |                ^

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/png.c:2970:16: note: include the header <math.h> or explicitly provide a declaration for 'frexp'

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/png.c:3042:24: error: call to undeclared library function 'modf' with type 'double (double, double *)'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]

 3042 |                   fp = modf(fp, &d);

      |                        ^

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/png.c:3042:24: note: include the header <math.h> or explicitly provide a declaration for 'modf'

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/png.c:3046:23: error: call to undeclared library function 'floor' with type 'double (double)'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]

 3046 |                   d = floor(fp + .5);

      |                       ^

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/png.c:3046:23: note: include the header <math.h> or explicitly provide a declaration for 'floor'

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/png.c:3921:31: error: call to undeclared library function 'pow' with type 'double (double, double)'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]

 3921 |          double r = floor(255*pow((int)/*SAFE*/value/255.,gamma_val*.00001)+.5);

      |                               ^

   ... (rest of output omitted)

* For target support_native_java.desktop_libsplashscreen_pngrtran.o:

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c:303:19: error: call to undeclared library function 'floor' with type 'double (double)'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]

  303 |    output_gamma = floor(output_gamma + .5);

      |                   ^

/Users/mac/Developer/jdk/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c:303:19: note: include the header <math.h> or explicitly provide a declaration for 'floor'

1 error generated.



* All command lines available in /Users/mac/Developer/jdk/build/macosx-aarch64-server-release/make-support/failure-logs.

=== End of repeated output ===

frexp, modf, floor, pow와 같은 함수들은 <math.h> 헤더 파일에 정의되어 있음. 근데, C99 표준 이후부터는 헤더 파일에 선언되지 않은 함수를 호출하면 컴파일 오류 발생함. libpng 소스 코드의 일부 파일들이 이 함수들을 사용하면서도 <math.h>를 포함하지 않았기 때문에 오류가 발생한 것이다.

해결 해당 파일에 가서 #include <math.h> 해주기

수정 대상 src/java.desktop/share/native/libsplashscreen/libpng/png.c src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c

#include "pngpriv.h"
#include <math.h>

이런식으로 추가해주면 된다.

여기까지 하고 나서 make 성공

Compiling up to 4 files for BUILD_JIGSAW_TOOLS

Optimizing the exploded image

Stopping javac server

Finished building target 'default (exploded-image)' in configuration 'macosx-aarch64-server-release'

이런 로그가 나오면 컴파일이 된 것이다.

앞으로 직접 디버깅 해가며 깨달은게 있다면, 추후 프로젝트에도 반영해보고 글로 남겨도 좋을 것 같다.

참고 도서

https://product.kyobobook.co.kr/detail/S000213057051