반응형
작성 계기
- 우리는 자바를 배울 때 javac로 컴파일 하고 java 파일로 실행하는 단계를 먼저 배운다
- 우리가 처음 배울 때 고생했던 Java 프로퍼티가 왜 사용되는거고, Java에서 이를 어떻게 인식해서 사용하는걸까?
- 또한, Java라는 언어는 OS 독립적으로 사용될 수 있다고 하는데, 어떻게 그게 가능한걸까?
- 이러한 근복적인 시스템 프로그래밍적인 질문이 문득 머릿속을 지나치게 되었고 어쩌면 다른 오픈소스 라이브러리들보다 먼저 분석했어야할 Open JDK 라이브러리 내에서 JVM이 어떻게 동작하는지에 대한 분석을 시작하게 되었다
방대한 JDK 중에 무엇을 알아볼 것인가?
- 우리가 Java 파일을 실행할 때, JVM이 어떻게 이를 인식하는지 실행흐름을 대략적으로 살펴보려한다
- 소제목에서 말했듯이 JDK 자체가 너무 방대하기 때문에 코드를 깊게 파고들면 실행 흐름의 숲을 보지 못하게 된다
[Open JDK 저장소]
JVM 실행 흐름도
ㅇㅇ
Class 파일을 어디서 만들어지나?
- java compiler의 진입점은 아래 링크에 Java 코드로 작성되어 있다
- 지금은 컴파일러가 어떻게 .class 파일을 만드는지에 대한 주제가 아니기 때문에 넘어간다
- https://github.com/openjdk/jdk/blob/master/src/jdk.compiler/share/classes/com/sun/tools/javac/Main.java
JVM 실행에 대한 코드 흐름 분석
파일 진입점
- 우선 Java 실행 파일부터 configure 파일을 거쳐서 진입점인 Jni.cpp 파일에 도달하는 부분은 생략한다
- 파일을 실행하는 과정 또한 굉장히 복잡해서 따로 작성하려 한다
- JVM 실행해 대한 모든 파일은 Java 언어가 아닌, C++로 작성되어 있다.
Jni.cpp
- 처음 VM 실행을 위해 시작하는 진입점이 되는 파일.
- JNI_CreateJavaVM 메서드를 통해 우리가 Java를 실행할 때 필요한 다양한 옵션들을 파라미터로 받게 된다
- 내부 코드인 JNI_CreateJavaVM_inner 메서드에 파라미터를 전부 넘겨주고
- 이 메서드 내부에서 글의 본론으로 들어가게될 Threads::create_vm 메서드를 호출하게 된다
Threads.cpp
- JVM 관련 정보를 초기화하고 이전의 JVM 흐름도에 대한 설정을 통해 메모리에 구성하는 코드가 담긴 파일이다
- 이 메서드 내에 JNI_CreateJavaVM_inner에서 호출했던 create_vm 메서드를 통해 초기화 작업이 진행된다
static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {
HOTSPOT_JNI_CREATEJAVAVM_ENTRY((void **) vm, penv, args);
jint result = JNI_ERR;
DT_RETURN_MARK(CreateJavaVM, jint, (const jint&)result);
// We're about to use Atomic::xchg for synchronization. Some Zero
// platforms use the GCC builtin __sync_lock_test_and_set for this,
// but __sync_lock_test_and_set is not guaranteed to do what we want
// on all architectures. So we check it works before relying on it.
#if defined(ZERO) && defined(ASSERT)
{
jint a = 0xcafebabe;
jint b = Atomic::xchg(&a, (jint) 0xdeadbeef);
void *c = &a;
void *d = Atomic::xchg(&c, &b);
assert(a == (jint) 0xdeadbeef && b == (jint) 0xcafebabe, "Atomic::xchg() works");
assert(c == &b && d == &a, "Atomic::xchg() works");
}
#endif // ZERO && ASSERT
...
result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
...
// Flush stdout and stderr before exit.
fflush(stdout);
fflush(stderr);
return result;
}
Threads.cpp::create_vm
- vm을 시작하는 메서드 자체만으로도 400줄이 넘고, 심지어 기능 별로 분리를 해놓은 코드다.
- 그만큼 이 메서드 내부에서 정말 다양한 작업이 이루어진다는 것을 알 수 있다
- 따라서, 우리가 알고싶었던 클래스 로더 설정하는 코드나 스레드 메모리를 할당하는 코드가 어디에 있는지 중점으로 살펴보도록 하겠다
Line: 411 ~ 475 주요 코드
- VM_Version::early_initialize():
- JVM 버전 정보를 초기화
- Arguments::process_sun_java_launcher_properties(args):
- 자바 명령어를 실행할 때 전달된 다양한 속성들을 처리
- os::init():
- JVM이 실행되는 운영 체제에 맞게 초기 설정
- JDK_Version_init():
- JDK 버전 정보를 초기화
- Arguments::init_version_specific_system_properties():
- JVM의 특정 버전에 따라 달라지는 시스템 속성을 설정
- Arguments::parse(args):
- JavaVMInitArgs 구조체에 포함된 JVM 초기화 인수를 파싱
- os::init_before_ergo():
- JVM 초기화 과정에서 운영 체제 관련 설정
Line: 477 ~ 658 주요 코드
- HOTSPOT_VM_INIT_BEGIN() ~ HOTSPOT_VM_INIT_END():
- VM 초기화 단락을 구분하는 키워드 매크로
- os::init_2():
- 기본 설정을 바탕으로 추가적이고 더 구체적인 OS 관련 초기화 작업을 수행
- SafepointMechanism::initialize():
- 가비지 컬렉션, 디버깅, 프로파일링 등의 작업을 수행할 때, 모든 Java 스레드를 일시 정지시키는 지점인 Safepoint를 초기화
- Safetypoint란?
- JVM이 모든 스레드를 일시 정지시키는 지점으로, 다음과 같은 작업을 안전하게 수행하기 위해 필요하다
- 가비지 컬렉션: 힙 메모리를 검사하고 정리하기 위해 모든 스레드를 정지시킨다
- 디버깅: 디버거가 JVM의 상태를 검사하고 제어하기 위해 스레드를 정지시킨다
- 프로파일링: 성능 분석을 위해 JVM의 상태를 검사한다
- 클래스 리로딩: 새로운 클래스나 업데이트된 클래스를 로드하기 위해 스레드를 정지시킨다
- 작동 방식
- Safepoint 요청 → 스레드 정지 → 작업 수행 → 스레드 재개
- Arguments::adjust_after_os()
- JVM이 운영 체제와 관련된 설정을 적절히 조정하도록 도와주는 메서드
- ostream_init_log()
- JVM 초기화 과정에서 출력 스트림 로깅을 설정하는 역할
- JvmtiAgentList::load_agents()
- VMTI (Java Virtual Machine Tool Interface) 에이전트를 로드하고 초기화하는 역할
- JVMTI란?
- ava 프로파일링 및 디버깅 인터페이스로, 외부 도구가 JVM 내부의 상태를 모니터링하고 조작할 수 있도록 지원한다
- vm_init_globals()
- JVM 초기화 과정에서 전역 데이터 구조를 초기화하고, 힙에 시스템 클래스를 생성하는 역할
- java.lang.Object, java.lang.String과 같은 클래스가 여기에 포함된다
- OopStorageSet::create_strong("Thread OopStorage", mtThread)
- JVM에서 오브젝트 참조를 안전하고 효율적으로 관리하기 위한 데이터 구조를 만드는 메서드
- OopStorage를 통해 가비지 컬렉션 중에 해당 오브젝트 참조가 회수되지 않도록 보장한다
- OopStorage란?
- "Ordinary Object Pointer Storage"의 약자로, JVM 내에서 오브젝트 참조를 안전하고 효율적으로 저장하고 관리하기 위한 데이터 구조
- OopStorage란?
- mtThread는 메모리 유형을 나타내는 인수로, OopStorage가 threadObj에 사용된다는 것을 나타낸다
- JavaThread* main_thread = new JavaThread()
- JVM 초기화 과정에서 메인 스레드를 현재 운영 체제 스레드에 연결하고, 여러 초기화 작업을 수행하여 메인 스레드가 정상적으로 실행될 수 있도록 설정한다.
- 주요 작업으로는 스레드 상태 설정, 스레드 초기화, 스택 정보 기록 및 NMT 등록, JNI 핸들 블록 설정, 그리고 특정 플랫폼(macOS AArch64)에서의 추가 초기화 작업이 포함된다
- ObjectMonitor::Initialize()
- JVM 초기화 과정에서 Java 레벨의 동기화 서브시스템을 초기화하는 역할
- ObjectSynchronizer::initialize()
- 동기화 구성 요소 초기화
- jint status = init_globals()
- JVM 초기화 과정에서 전역 모듈을 초기화하고, 초기화 결과를 확인하여 실패할 경우 적절한 오류 처리를 수행하는 역할
- 예: 클래스 로더 초기화, 메모리 관리 초기화, 네이티브 인터페이스 초기화 등
- Threads::add(main_thread)
- JVM 초기화 과정에서 메인 스레드를 스레드 목록에 추가하는 역할
- MutexLocker mu(Threads_lock)
- Threads_lock을 사용하여 뮤텍스를 잠근다. 이는 스레드 목록에 대한 동시 접근을 방지하여 스레드 안전성을 보장한다
- MutexLocker 객체는 RAII(Resource Acquisition Is Initialization) 패턴을 사용하여 뮤텍스를 자동으로 잠그고, 스코프가 끝날 때 자동으로 해제한다
- init_globals2()
- 예: 추가적인 클래스 초기화, 성능 최적화, 시스템 리소스 설정 등
- java.lang, java.util 등 핵심 Java 클래스 초기화
- JIT 컴파일러 초기화, 인터프리터 설정, 가비지 컬렉션 최적화 등
- main_thread->cache_global_variables()
- JVM 초기화 과정에서 메인 스레드가 전역 변수를 캐시하도록 설정하는 역할
- 특히, 힙(heap)이 완전히 생성된 후에 이 작업이 수행되어야 함을 강조하고 있다
- Create the VMThread 블럭
- VMThread는 JVM의 주요 시스템 스레드로, 가비지 컬렉션, JIT 컴파일, 다른 내부 작업을 처리한다
- 이 코드 블록은 VMThread를 생성하고, 실행 상태로 전환하며, 스레드가 준비될 때까지 기다린다
- Arguments::update_vm_info_property(VM_Version::vm_info_string())
- JVM 초기화 과정에서 java.vm.info 시스템 속성을 업데이트하는 역할
- 초기화 과정 중에 변경될 수 있는 플래그를 반영하기 위해 필요
- JavaThread* THREAD = JavaThread::current()
- JVM에서 현재 실행 중인 스레드(JavaThread)를 가져오고, 예외 처리를 위한 매크로와 객체 핸들 관리의 범위를 설정하는 역할
- initialize_java_lang_classes(main_thread, CHECK_JNI_ERR)
- Java 기본 클래스를 초기화
- java.lng 패키지의 기본 클래스(예: java.lang.Object, java.lang.String)를 초기화
- MutexLockerImpl::post_initialize()
- 뮤텍스를 초기화하여 스레드 간의 동기화 작업을 관리
Line: 660 ~ 730
- Arena::start_chunk_pool_cleaner_task()
- 메모리 관리에서 사용되는 청크 풀을 정리하는 태스크
- ServiceThread::initialize()
- JVM의 서비스 스레드를 초기화하고 시작
- 서비스 스레드는 JVMTI 지연 이벤트를 처리하고, 다양한 해시 테이블 및 기타 정리 작업을 수행
- initialize compiler(s) 블럭
- 컴파일러 초기화 조건에 따라 분기 처리
- 이는 특정 컴파일러가 JVM에 포함되어 있을 때만 초기화를 수행하기 위함
- JVMCI 초기화
- JVMCI란, JVMCI(Java Virtual Machine Compiler Interface)는 JVM(Java Virtual Machine)의 컴파일러 인터페이스로, JVM과 컴파일러 사이의 상호작용을 지원하는 표준화된 API
- Graal 컴파일러, 다른 JIT 컴파일러 등 다양한 JIT(Just-In-Time) 컴파일러와 상호작용할 수 있으며, 새로운 컴파일러를 쉽게 통합할 수 있다
- initialize_jsr292_core_classes(CHECK_JNI_ERR)
- 이 코드는 일부 핵심 JSR292 클래스를 미리 초기화하여 클래스 로딩 중 데드락을 피할 수 있게 해주는 역할
- call_initPhase2(CHECK_JNI_ERR)
- 모듈 시스템을 초기화
- call_initPhase3(CHECK_JNI_ERR)
- 최종 시스템 초기화로, 보안 관리자 및 시스템 클래스 로더를 포함한 최종 시스템 초기화를 수행
- SystemDictionary::compute_java_loaders(CHECK_JNI_ERR)
- 시스템 및 플랫폼 클래스 로더를 캐싱하는 메서드
정리
- JVM 실행에 사용되는 주요 코드에 대해 대략적인 파악을 하는 시간을 통해 JDK 프로젝트가 어떤 방식으로 JVM을 초기화하는지 알아보았다
- 막상 뜯어보려니 너무 방대한 느낌이어서 흐름이라는 숲을 먼저 파악하고 내부 기능에 해당하는 나무들을 파악하는게 좋을 것 같았고 이에 따라 대략적인 코드 흐름을 정리해보는 계기가 되었다
반응형
LIST
'언어 > java' 카테고리의 다른 글
JDK 동적 프록시 (2) | 2023.12.05 |
---|---|
JDK21 Update 정리 (2) | 2023.11.22 |
[Java] 자바의 컬렉션(Collection) (0) | 2020.05.10 |
JAVA EE,SE,ME에 대하여 (0) | 2019.08.08 |
JDK와 JRE (0) | 2019.08.08 |