Class Loader에 대해 알아보자

wnajsldkf·2022년 10월 9일
0

Java

목록 보기
11/19
post-thumbnail

공부하면서 알게된 내용을 이해하면서 정리하였습니다. 잘못된 내용이 있다면 피드백 주시면 감사하겠습니다.🙇‍♀️

자바의 실행 과정 공부를 시작했다.

class 파일을 JVM으로 로딩한다는 Class Loader가 어떤 역할을 하고 어떻게 구성되어 있을지 공부해보겠다.

Class loading

“클래스 로딩” 일단 이 단어부터 이해해보도록 하자.

JVM을 구성하는 요소 중 하나인 class loader는 class를 실행하기 위해 class를 컴퓨터가 이해할 수 있는 비트로 변환시키는 역할을 한다. 이런 클래스 로더의 존재로 Java 프로그램을 실행시킬 때 파일과 파일 시스템에 대한 정보를 전부 알고 있지 않아도 되는 것이다.

Static class loading

Static으로 선언된 class들은 컴파일 시점에 class definition과 instantiation이 모두 완료된다.

  • class의 멤버 변수와 메서드에 접근하기 위해 사용하는 class 객체를 생성하는 것이다.
class TestClass {
	public static void main(String args[]) {
		TestClass tc = new TestClass();
	}
}

Dynamic class loading

런타임 시점에 class loader 함수를 실행하는 기술이다.(이 문장을 봤을 때 class loader는 클래스를 찾아오는 역할을 하는 것인가? 생각하게 된다.) Dynamic class loading은 컴파일 시 클래스 이름을 알 수 없을 때 수행된다.

Class.forName (String className);

Class.forName은 JDBC에서 Driver 클래스를 JVM에 로드하기 위해 사용되고 호출 시, className에 해당하는 Class Object를 반환한다. (자세한 내용은 다음에 더 공부해보는 것으로 하고 우선 잘 정리된 글을 공유한다.)

클래스가 동적으로 로드되면 class.NewInstance() 메서드로 로드된 클래스의 인스턴스를 반환한다. 따라서 모델은 필요에 따라 클래스를 가져오기 때문에 클래스를 실행하기 전에 컬렉션의 모든 클래스 이름을 알고 있을 필요없다.

Class loading에 대해 알아보았는데 이제 Class Loader가 어떤 것을 할지 짐작가는가?

ClassLoader

ClassLoader는 Object Class를 상속하였다. ClassLoader의 다양한 메서드들은 공식 문서를 참고하길 바란다.

public abstract class ClassLoader extends Object

Dynamic class Loading(동적 클래스 로딩)이라는 단어를 이해하면 클래스 파일을 가져오는 기능을 기능이 필요할 것 같다. ClassLoader는 흩어져 있는 .class 확장자를 가진 클래스 파일들을 찾아 JVM의 메모리에 올려주는 역할을 한다.

java -verbose:class 옵션을 통해 로드되는 클래스들을 확인할 수 있다.

ClassLoader 과정

ClassLoader는 컴파일된 .class 파일을 탑재하는 Loading, 클래스 파일을 사용하기 위해 검증 및 기본값으로 초기화하는 Linking, static field의 값들을 정의한 값으로 초기화하는 Initializing 과정을 거친다.

Loading

.class 파일을 읽고 내용에 따라 적절한 바이너리 데이터를 만들고 메서드 영역에 저장한다. JVM 스펙과 Java Version을 확인한다.

클래스 로더는 부모-자식의 계층(Hierarchical) 구조로 이루어져 있다. 즉 부모 클래스 로더에서 자식 클래스 로더를 갖는다. 계층 구조는 Java8 버전과 Java9 버전으로 나뉘는데 이 글에서는 Java8 기준으로 작성하겠다. (Java9도 함께 작성되어 있는 글을 첨부한다.) 필요에 따라 개발자가 커스텀하여 클래스로더를 구현할 수도 있다.

다음은 기본 클래스 로더이다.

  • Bootstrap ClassLoader
  • Extensions ClassLoader
  • Application ClassLoader

Bootstrap ClassLoader

Java 클래스는 Java.lang.ClassLoader 인스턴스에 의해 로드된다. 그럼 ClassLoader를 로드하는 것은 무엇일까? 바로 Bootstrap 클래스 로더로, Java 실행초기에 작동된다.

+) 컴퓨터 용어에서 Bootstrap은 한 번 시작하면 다른 외부의 도움없이 스스로 진행하는 행위를 말한다고 한다.(부츠 신을 때 편하게 신을 수 있도록 도움주는 고리에서 나왔다고 하니 떠올려보자!)

기본 클래스 로더 중 최상위 클래스로더로, jre/lib/rt.jar 에 담긴 JDK 클래스 파일을 로딩한다. (jre/lib/rt.jar 파일은 JVM이 돌아가기 위한 기본 API를 담고 있다. )

또한 자바가 아닌 운영체제에 따른 네이티브 언어로 작성되어 있다. 따라서 다음 Java 코드에서 null을 반환한다.

public class Test {
    public static void main(String[] args) {
        System.out.println(ArrayList.class.getClassLoader());  // null
    }
}

Extensions ClassLoader

(Java9부터는 Platform ClassLoader로 이름이 바뀌었으며, Java8에 비해 사용 범위가 확장되었다.)

<JAVA_HOME>\jre\lib\ext 에 위치한 JDK Extension 라이브러리들을 JVM에 탑재한다.

Application ClassLoader

(Java9 이후로 System ClassLoader로 이름이 바뀌었다.)

Classpath에 있는 클래스들을 탑재한다. 개발자들이 자바 코드로 작성한 클래스 파일들을 JVM에 탑재하는데, 만약 직접 ClassLoader를 구현한다면 Application ClassLoader에 자식 형태로 사용자 정의 ClassLoader가 구성된다.

ClassLoader의 구현 구조

그럼 ClassLoader는 Class를 어떤 구조로 찾는 것인가?

다음은 부모역할을 하는 ClassLoader를 찾는 과정이다. getParent() 메서드는 상위 파일을 찾는 메서드이다.

public class Test {
    public static void main(String[] args) {
        MyTest myTest = new MyTest("test");
        System.out.println("Application ClassLoader = " + MyTest.class.getClassLoader());
        System.out.println("Extension ClassLoader = " + MyTest.class.getClassLoader().getParent());
        System.out.println("Bootstrap ClassLoader = " + MyTest.class.getClassLoader().getParent().getParent());
    }
}

class MyTest {
    String testName;

    public MyTest(String testName) {
        this.testName = testName;
    }
}

실행결과

실행결과를 통해 계층구조를 확인할 수 있다.

/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home/bin/java -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=64814:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/mywnajsldkf/Desktop/ongoing/study/java-study/out/production/java-study:/Users/mywnajsldkf/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar:/Users/mywnajsldkf/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib/1.6.10/kotlin-stdlib-1.6.10.jar:/Users/mywnajsldkf/.m2/repository/org/jetbrains/annotations/13.0/annotations-13.0.jar:/Users/mywnajsldkf/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.10/kotlin-stdlib-common-1.6.10.jar:/Users/mywnajsldkf/.m2/repository/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar etc.Test
Application ClassLoader = jdk.internal.loader.ClassLoaders$AppClassLoader@2c13da15
Extension ClassLoader = jdk.internal.loader.ClassLoaders$PlatformClassLoader@60f82f98
Bootstrap ClassLoader = null

Process finished with exit code 0

java.lang.ClassLoader.loadClass() 메서드를 통해 class 정의를 runtime 시점에 불러온다. Class는 Fully Qualified Name으로 class를 찾는다.

+) Fully Qualified Name은 클래스가 속한 패키지면을 모두 포함한 이름을 말한다. (ex. java.lang.String s )

만약 class를 찾을 수 없다면 부모 클래스 로더에게 넘겨준다. 부모 클래스 로더에서도 찾을 수 없다면 java.net.URLClassLoader.findClass() 메서드를 호출해 파일 시스템에서 클래스를 찾는다. 만약 마지막까지 Class를 찾을 수 없다면 java.lang.NoClassDefFoundError 또는 java.lang.ClassNotFoundException 이 발생하는 것이다.

Linking

로드된 클래스 파일을 검증하고 사용할 수 있도록 준비한다. Linking은 Verification → Preparation → Resolution 단계로 이루어져 있다.

1. Verification

Java는 JVM에 의해 해석되기 때문에 가상 머신에 올라온 .class 파일 형식이 유효한지, 적절한 포맷인지, 유효한 컴파일러에 생성되었는지 알 수 없다. Verification에서는 이를 검증한다. JVM의 구동 조건 대로 구현되지 않았을 경우 VerifyError를 던진다.

이 과정은 비용이 커서 시간이 많이 걸릴 수 있다.

수행하는 검사들은 다음과 같다.

  • 심볼 테이블(symbol table)이 일관되고 올바른 형식인지 검사
  • 접근 지정자에 따른 접근 범위에서 메서드에 접근하고 있는지 검사
  • 메서드의 매개변수 수와 자료형이 올바른지 검사
  • final 메서드와 클래스가 오버라이드 되지는 않았는지 검사
  • 변수를 읽기 전에 초기화되었는지 검사
  • 변수가 올바른 타입의 값인지 검사

2. Preparation

JVM에서 사용되는 자료구조나 static storage를 위한 메모리를 할당한다. 이때 메모리가 부족하면 java.lang.OutOfMemoryError가 발생한다. 이 단계에서 생성된 static field는 기본값으로 할당되고 Initialization 단계에서 원래의 값이 할당된다.

class Test {
	private static int a = 10; 
	private static long b;
}

int형 정적 변수 a는 4바이트의 메모리 공간을 할당하고 기본값이 0으로 초기화된다. long형 정적 변수 b는 8바이트의 메모리 공간을 할당하고 기본값이 0으로 초기화된다.

3. Resolution

Symbolic Reference 값을 JVM의 메모리 구성 요소인 Method Area의 런타임 환경 풀을 통하여 Direct Reference 라는 실제 메모리 주소 값으로 바꾸는 것을 말한다.

  • Symbolic Reference: 참조하는 특정 메모리 주소를 참조하는 것이 아닌 대상의 이름만 지칭하는 것을 말한다.
  • Direct Reference: 필요한 데이터를 포함하는 메모리 주소를 직접 가리키는 것을 말한다.

Initialization

클래스 파일의 코드를 읽는 과정이다. Linking 과정을 통해 확보한 메모리 영역에 class와 interface의 값들을 지정한 값으로 초기화 및 초기화 메서드를 실행한다.

Summary
Classloader는 Runtime 시, 흩어져 있는 .class 확장자를 가진 클래스 파일을 찾아 JVM의 메모리에 올려주기 위해 사용된다.
ClassLoader는 Loading, Linking, Initializing 과정으로 사용된다.
기본 클래스 로더에는 Bootstrap ClassLoader, Extensions ClassLoader, Application ClassLoader가 있다. Linking 과정은 Verification -> Preparation -> Resolution 단계로 진행된다.

Reference

profile
https://mywnajsldkf.tistory.com -> 이사 중

0개의 댓글