
DB Connetion 관련 코드를 짜던 도중 아래 Code에서 Class.forname()이 정확하게 어떤 역할을 하며
DriverManager가 어떻게 org.sqlite.JDBC 라이브러를 가져오는지 알지 못한채 코드를 작성해 온 것 같아 이에 대해 알아보기로 했다.
Connection conn = null;
try {
Class.forName("org.sqlite.JDBC");
conn = DriverManager.getConnection("jdbc:sqlite:" + file.getCanonicalPath());
... 중략
Class Loader가 어떤 역할을 하는지 알기 위해 JVM의 동작 과정에 대해 살펴보자

동작과정
개발자가 작성한 소스코드(.java)를 JDK의 javac.exe를 사용해 자바 바이트코드(.class)로 변환한다.
Class Loader 를 통해 필요한 클래스 파일들을 로딩, 링크하여 Run Time Data Area에 할당한다.
Runtime Data Area는 Application이 실행될 때 데이터들이 저장되는 메모리 공간이며(Method Area, Heap, Stack, PC Register, Native Method Stack) 5개의 공간으로 구성되었다.
Run Time Data Area 영역에 할당 된 바이트 코드들은 JVM의 Execute Engine에 의해 해석되고 실행된다, 여기서 알수 부분은 Java는 Compiler와 Interpreter를 둘 다 혼합하여 사용하는 Hybrid 언어이다.
JVM 동작과정에서 설명한 Class Loader를 좀 더 자세히 설명하면 아래와 같다.
Class loaders are responsible for loading Java classes dynamically to the JVM (Java Virtual Machine) during runtime. They’re also part of the JRE (Java Runtime Environment). Therefore, the JVM doesn’t need to know about the underlying files or file systems in order to run Java programs thanks to class loaders.
Furthermore, these Java classes aren’t loaded into memory all at once, but rather when they’re required by an application. This is where class loaders come into the picture. They’re responsible for loading classes into memory.
참조 : https://www.baeldung.com/java-classloaders
1. Loading
.class 파일들을 바이너리코드로 변환하고 해당 데이터들을 Runtime Data Area의 Method Area에 저장한다 해당 Area는 모든 Thread에서 공유하는 공간이다.
Method Area에 저장되는 Data들은 아래와 같다.
*Constant Pool Detail*
public class ConstantPool {
public static void main(String[] args) {
String msg = "Hello Word!";
System.out.println(msg);
}
}
//해당 코드에 대한 Constant Pool은 아래와 같다.
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = String #8 // Hello Word!
#8 = Utf8 Hello Word!
#9 = Fieldref #10.#11 // java/lang/~[System.out:Ljava/io/PrintStream]()~;
#10 = Class #12 // java/lang/System
#11 = NameAndType #13:#14 // out:Ljava/io/PrintStream;
#12 = Utf8 java/lang/System
#13 = Utf8 out
#14 = Utf8 Ljava/io/PrintStream;
#15 = Methodref #16.#17 // java/io/PrintStream.println:(Ljava/lang/String;)V
#16 = Class #18 // java/io/PrintStream
#17 = NameAndType #19:#20 // println:(Ljava/lang/String;)V
#18 = Utf8 java/io/PrintStream
#19 = Utf8 println
#20 = Utf8 (Ljava/lang/String;)V
#21 = Class #22 // ClassLoader/ConstantPool
#22 = Utf8 ClassLoader/ConstantPool
#23 = Utf8 Code
#24 = Utf8 LineNumberTable
#25 = Utf8 main
#26 = Utf8 ([Ljava/lang/String;)V
#27 = Utf8 SourceFile
#28 = Utf8 ~[ConstantPool.java](http://constantpool.java/)~
Loading은 3가지의 ClassLoader들에 의해 진행되며 아래와 같은 방식으로 Application에 필요한 Class를 찾는다.
Class Loader들은 계층구조이며 위임형 로드 요청(Delegate Load Request) 방식을 사용한다,
Class를 찾을 때 최상위 계층인 BootStrap Class Loader에서 요청을 보내며 해당 계층에서 Class를 찾지 못한다면 다음 계층으로 요청을 위임하며 Class를 찾는다.
모든 계층에서 Class를 찾지못한다면 ClssNotFound Exception을 반환한다.
각 Class Loader의 Class를 찾는 범위는 정해져 있으며 상위 계층 Class Loader에서 찾지 못한 Class는 하위 계층 Class Loader를 통해 찾지 못한다.
Class Loader들은 unload 기능이 없어 한번 load한 class들을 삭제 할 수 없다.
2. Linking
3. Initialization
위 ClassLoad에 대하여 언급 할 때 JVM의 Runtime 시점에 Class를 load한다고 언급하였다. 이러한 작동 방법을 동적로딩이라고 부르며 Java에서는 해당 동적로딩을 위해 두가지 방법을 사용한다.
1. 로드 타임 동적 로딩(Load-time Dynamic Loading)
Class를 loading 할 떄 해당 Class와 관련된 Class들을 한번에 같이 loading한다.
아래 코드를 예시로 들면 Main.class를 loading하면서 해당 Class를 실행하기 위한 String Class, System Class를 같이 loading한다.
public class Main {
public static void main(String[] args) {
System.out.println(“hello”);
}
}
2. 런타임 동적 로딩(Run-time Dynamic Loading)
Class 내부에서 다른 Class가 필요한 특정 시점에 loading하는 방법이다.
아래 코드에서는 Class.forName() 메서드를 통해 Connection이 필요한 순간에만 런타임 동적 로딩으로 org.sqlite.JDBC Class를 loading한다.
Connection conn = null;
try {
Class.forName("org.sqlite.JDBC")
conn = DriverManager.getConnection("jdbc:sqlite:" + file.getCanonicalPath());
... 중략
위 ClassLoading 3단계에서 언급한것처럼 org.sqlite.JDBC를 loading하면서 org.sqlite.JDBC에 선언된 아래 코드의 static block이 ClassLoader에 의해 초기화 된다.
이 과정에서 DriverManager에 실제 JDBC 객체가 할당되고 이를 통해
위 코드에서처럼 따로 new 연산자를 통해 JDBC 객체를 생성 해 주지 않아도 DriverManager.getConnection() 메서드를 통해 Connection을 생성 할 수 있었던 것이다.
public class JDBC implements java.sql.Driver {
static {
try {
DriverManager.registerDriver(new JDBC());
} catch (SQLException e) {
e.printStackTrace();
}
}
}