클래스 로더가 .class 파일을 찾고 JVM에 메모리에 올려놓는것을 의미합니다.
JVM은 실행될때 모든 클래스를 메모리에 올려놓지 않습니다. 그때 마다 필요한 클래스를 메모리에 올려 효율적으로 관리하기 위함입니다.
-verbose:class
옵션을 사용하면 클래스 로딩을 디버그 할 수 있습니다.
2개의 클래스 작성 Main, Single
// Main.java
class Main {
public static void main(String[] args) {
}
}
class Single {
// 1. 생성자
public Single() {}
// 2. 정적 변수 - final X
public static int a;
// 3. 정적 변수 - final O
public static final int b = 0;
// 4. 정적 메서드
public static void getInstance() {
}
// 5. 정적 내부 클래스
public static class Holder {
public static Single INSTANCE;
}
}
컴파일 하고, 실행합니다.
javac Main.java
java -verbose:class Main
Main 클래스는 로딩되었지만 Single 클래스는 로딩되지 않았습니다.
class Main {
public static void main(String[] args) {
// 생성자로 클래스의 인스턴스 생성
new Single();
}
}
class Single {
// 1. 생성자
public Single() {}
// 2. 정적 변수 - final X
public static int a;
// 3. 정적 변수 - final O
public static final int b = 0;
// 4. 정적 메서드
public static void getInstance() {
}
// 5. 정적 내부 클래스
public static class Holder {
public static Single INSTANCE;
}
}
javac Main.java
java -verbose:class Main
Single 클래스가 로드되었습니다.
class Main {
public static void main(String[] args) {
// final 지시어가 없는 정적 변수 호출
System.out.println(Single.a);
}
}
class Single {
// 1. 생성자
public Single() {}
// 2. 정적 변수 - final X
public static int a;
// 3. 정적 변수 - final O
public static final int b = 0;
// 4. 정적 메서드
public static void getInstance() {
}
// 5. 정적 내부 클래스
public static class Holder {
public static Single INSTANCE;
}
}
javac Main.java
java -verbose:class Main
Single 클래스가 로드되었습니다.
class Main {
public static void main(String[] args) {
// final 지시어가 붙은 상수 정적 변수 호출
System.out.println(Single.b);
}
}
class Single {
// 1. 생성자
public Single() {}
// 2. 정적 변수 - final X
public static int a;
// 3. 정적 변수 - final O
public static final int b = 0;
// 4. 정적 메서드
public static void getInstance() {
}
// 5. 정적 내부 클래스
public static class Holder {
public static Single INSTANCE;
}
}
javac Main.java
java -verbose:class Main
Single 클래스가 로드되지 않았습니다.
class Main {
public static void main(String[] args) {
// 정적 메소드 호출
Single.getInstance();
}
}
class Single {
// 1. 생성자
public Single() {}
// 2. 정적 변수 - final X
public static int a;
// 3. 정적 변수 - final O
public static final int b = 0;
// 4. 정적 메서드
public static void getInstance() {
}
// 5. 정적 내부 클래스
public static class Holder {
public static Single INSTANCE;
}
}
javac Main.java
java -verbose:class Main
Single 클래스가 로딩되었습니다.
class Main {
public static void main(String[] args) {
System.out.println(Single.Holder.INSTANCE);
}
}
class Single {
// 1. 생성자
public Single() {}
// 2. 정적 변수 - final X
public static int a;
// 3. 정적 변수 - final O
public static final int b = 0;
// 4. 정적 메서드
public static void getInstance() {
}
// 5. 정적 내부 클래스
public static class Holder {
public static Single INSTANCE;
}
}
javac Main.java
java -verbose:class Main
어머나 Single 클래스 대신 Single 내부의 Holder 클래스만 로드되었습니다.
외부 클래스가 로딩 - 내부의 클래스 로딩 X
내부 클래스만 로딩 - 외부의 클래스는 로딩 X
클래스 초기화는 static 블록과 static 멤버 변수의 값을 할당하는 것을 의미합니다.
내부의 클래스는 초기화 대상 x
사실 위의 클래스 로드 시점과 같습니다. 클래스가 로드되면 초기화도 바로 진행됩니다.
class Main {
public static void main(String[] args) {
new Single();
}
}
class Single {
static {
System.out.println("1. 정적 블록");
}
public static Temp temp = new Temp();
public Single() {
System.out.println("3. 생성자");
}
}
class Temp {
public Temp () {
System.out.println("2. 정적 변수");
}
}
JLS(Java Language Specification)에 따르면 JVM에 클래스가 로딩되고 초기화될때는 순차적으로 동작함을 보장합니다.
멀티 스레드 환경에서 여러개의 스레드가 클래스를 동시에 로딩하려고 오직 한개의 클래스만 로딩됩니다.
다음 코드는 10개의 스레드가 동시에 클래스의 인스턴스를 생성합니다.
결과를 보면 10개의 스레드가 동시에 클래스 로딩을 시도해도
클래스 로딩은 한번만 수행되고, 그때 한번 초기화를 수행
합니다. 이후 인스턴스를 10개 생성합니다.
이 의미는 멀티 스레드 환경에서 스레드 세이프함을 의미합니다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Main {
public static void main(String args[]) {
// 1. 스레드 풀 생성
ExecutorService service = Executors.newCachedThreadPool();
// 2. 반복문을 통해 - 10개의 스레드가 동시에 인스턴스 생성
for (int i = 0; i < 10; i++) {
service.submit(() -> {
new Single();
});
}
// 3. 종료
service.shutdown();
}
}
class Single {
static {
System.out.println("static 블록 호출");
}
public Single() {
System.out.println("생성자 호출");
}
}
javac Main.java
java -verbose:class Main
갑자기 왜 싱글톤이냐면, 클래스 로딩 및 초기화 과정이 스레드 세이프함을 이용하여 싱글톤 인스턴스를 만들 수 있기 때문입니다.
싱글톤은 인스턴스를 오직 1개만 가지는 클래스입니다.
멀티 스레드 환경에서는 여러 스레드가 동시에 인스턴스 생성을 시도할 수 있고, 싱글톤 인스턴스가 여러개 생성될 수 있습니다.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Main {
public static void main(String args[]) {
// 1. 스레드 풀 생성
ExecutorService service = Executors.newCachedThreadPool();
// 2. 반복문을 통해 - 10개의 스레드가 동시에 인스턴스 생성
for (int i = 0; i < 10; i++) {
service.submit(() -> {
Single.getInstance();
});
}
// 3. 종료
service.shutdown();
}
}
class Single {
private Single() {
System.out.println("싱글톤 인스턴스 생성");
}
private static Single instance;
public static Single getInstance() {
// 여러 스레드가 동시에 if문을 통과하는 경우
if(instance == null) {
// 여러개의 인스턴스가 만들어질 수 있음
instance = new Single();
}
return instance;
}
}
무려 10개의 스레드가 생성되었습니다.
클래스 로딩 및 초기화는 10개의 스레드가 동시에 시도해도 오직 단 한번만 수행됩니다.
이때 수행되는 초기화 과정에서 클래스의 인스턴스를 딱 한번만 생성되도록 합니다.
아래의 방법은 LazyHolder 라는 방법으로 싱글톤 인스턴스를 생성하는데 가장 권장되는 방법중 하나입니다.(다른 하나는 enum 사용)
장점
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Main {
public static void main(String args[]) {
// 1. 스레드 풀 생성
ExecutorService service = Executors.newCachedThreadPool();
// 2. 반복문을 통해 - 10개의 스레드가 동시에 인스턴스 생성
for (int i = 0; i < 10; i++) {
service.submit(() -> {
SingleTon.getInstance();
});
}
// 3. 종료
service.shutdown();
}
}
// 싱글톤 클래스
class SingleTon {
// private 생성자 new 를 통한 인스턴스 생성 방지
private SingleTon() {
System.out.println("싱글톤 인스턴스 생성");
}
// 정적 메서드를 통해 내부 클래스 로딩
public static SingleTon getInstance() {
return LazyHolder.INSTANCE;
}
// 내부 클래스가 로딩될때 초기화 수행 - 싱글톤 인스턴스 생성
private static class LazyHolder {
private static final SingleTon INSTANCE = new SingleTon();
}
}
인스턴스가 오직 한개만 생성되었습니다.
외부 클래스 로딩 - 내부 클래스 로딩 X
내부 클래스 로딩 - 외부 클래스 로딩 X
클래스가 로딩될때 초기화도 수행
클리스 로딩 및 초기화 과정은 스레드 세이프함, 여러 스레드가 동시에 시도해도, 오직 한번만 수행
클래스 로딩 및 초기화 과정이 스레드 세이프함을 이용하는 방법
장점
내용 중에 싱글톤 기법으로 LazyHolder 를 소개해 주셨는데, 이는 위키피디아 예제 코드에 사용된 클래스의 이름입니다. 실제 기법의 이름은 Initialization-on-demand holder idiom 입니다. 참고해 주세요.