클래스는 언제 로딩될까?

August 30, 2022

클래스 로딩

  • 클래스 로더가 .class파일을 찾고 JVM 메모리에 올리는 작업을 의미한다.
  • ⭐️ JVM은 실행될 때 모든 클래스를 메모리에 올려 놓지 않는다.
  • 필요할 때 마다 클래스를 메모리에 올려 효율적으로 관리하기 위함이다.

그럼 언제 클래스를 로딩할까?

verbose

TestCase 0 : 아무것도 호출하지 않음

class Main { public static void main(String[] args){ } } class Single{ public Single() { } public static int a; public static final int b = 0; public static void test(){ } public static class INNER_INSTANCE{ public static Single INSTANCE; } }

testcase0

  • Main클래스만 로딩 되었고 Single클래스는 로딩되지 않음

TestCase 1 : 인스턴스 생성

class Main { public static void main(String[] args){ Single single = new Single(); } } class Single{ public Single() { } public static int a; public static final int b = 0; public static void test(){ } public static class INNER_INSTANCE{ public static Single INSTANCE; } }

testcase1

  • Main이 실행되고 Single클래스가 실행되었다.

TestCase 2 : final키워드가 없는 정적 변수 호출

class Main { public static void main(String[] args){ System.out.println(Single.a); } } class Single{ public Single() { } public static int a; public static final int b = 0; public static void test(){ } public static class INNER_INSTANCE{ public static Single INSTANCE; } }

testcase2

  • Main이 실행되고 Single클래스가 실행되었다.
  • public static int a변수가 출력 되었다.

TestCase 3 : final키워드가 있는 정적 변수 호출

class Main { public static void main(String[] args){ System.out.println(Single.b); } } class Single{ public Single() { } public static int a; public static final int b = 1000; public static void test(){ } public static class INNER_INSTANCE{ public static Single INSTANCE; } }

testcase3

  • ⭐️ Main클래스가 실행되고 Single클래스가 실행되지 않았지만 public static final int b가 출력되었다.

TestCase 4 : final키워드가 없는 정적 메서드 호출

class Main { public static void main(String[] args){ Single.test(); } } class Single{ public Single() { } public static int a; public static final int b = 1000; public static void test(){ System.out.println("static method 호출"); } public static class INNER_INSTANCE{ public static Single INSTANCE; } }

testcase4

TestCase 5 : final키워드가 있는 정적 메서드 호출

class Main { public static void main(String[] args){ Single.test(); } } class Single{ public Single() { } public static int a; public static final int b = 1000; public final static void test(){ System.out.println("final static method 호출"); } public static class INNER_INSTANCE{ public static Single INSTANCE; } }

testcase5

TestCase 6 : 정적 내부 클래스의 정적 변수 호출

class Main { public static void main(String[] args){ System.out.println(Single.INNER_INSTANCE.INSTANCE); } } class Single{ public Single() { } public static int a; public static final int b = 1000; public final static void test(){ System.out.println("final static method 호출"); } public static class INNER_INSTANCE{ public static Single INSTANCE; } }

testcase6

클래스는 한 번만 로딩됨을 보장한다.

Java 프로그래밍 언어는 다중 스레드이기 때문에 클래스 또는 인터페이스의 초기화는 신중한 동기화가 필요합니다.

다른 스레드가 동시에 동일한 클래스 또는 인터페이스를 초기화하려고 할 수 있기 때문입니다.

클래스 또는 인터페이스의 초기화가 해당 클래스 또는 인터페이스 초기화의 일부로 재귀적으로 요청될 수도 있습니다.

예를 들어, 클래스 A 의 변수 이니셜라이저 는 관련 없는 클래스 B 의 메서드를 호출할 수 있으며, 이는 차례로 클래스 A 의 메서드를 호출할 수 있습니다..

Java Virtual Machine의 구현은 다음 절차를 사용하여 동기화 및 재귀 초기화를 처리하는 역할을 합니다.

  • 출처 - Detailed Initialization Procedure
  • 다음 코드는 10개의 스레드가 동시에 클래스의 인스턴스를 생성한다
  • 결과를 보면 10개의 스레드가 동시에 클래스 로딩을 시도해도 클래스 로딩은 한번만 수행되고, 그때 한번 초기화를 수행한다.
  • 이후 인스턴스를 10개 생성한다
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("생성자 호출"); } }

multiThreadTest

클래스 로딩 정리 📌

  • Single클래스가 로딩 될 때
    • 해당 클래스의 인스턴스를 생성했을 때
    • final키워드가 없는 정적 변수를 호출했을 때
    • 해당 클래스의 정적 메서드를 호출했을 때
  • 🚩
    • final키워드가 있는 정적 변수를 호출해도 왜 Single클래스는 생성이 되지 않을까?
    • 외부 클래스가 로딩 되어도 내부 클래스는 생성되지 않는다.
    • 내부 클래스가 로딩 되어도 외부 클래스는 생성되지 않는다.

위의 특성을 이용하여 싱글 톤 인스턴스 생성해보기 LazyHolder

  • 실패 사례
class Main { public static void main(String args[]) { ExecutorService service = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { service.submit(() -> { SingleTon.getInstance(); }); } service.shutdown(); } } class SingleTon { private SingleTon() { System.out.println("싱글톤 인스턴스 생성"); } public static SingleTon instance; public static SingleTon getInstance() { if(instance == null){ System.out.println("인스턴스 생성"); return new SingleTon(); } return instance; } }

singleTonTest

  • LazyHolder 라는 방법으로 싱글톤 인스턴스를 생성하는데 가장 권장되는 방법중 하나이다.
    • (다른 하나는 enum 사용)
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Main { public static void main(String args[]) { ExecutorService service = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { service.submit(() -> { SingleTon.getInstance(); }); } service.shutdown(); } } class SingleTon { private SingleTon() { System.out.println("싱글톤 인스턴스 생성"); } public static SingleTon getInstance() { return LazyHolder.INSTANCE; } // 클래스는 한 번만 로딩됨을 보장한다. private static class LazyHolder { private static final SingleTon INSTANCE = new SingleTon(); } }

lazyHolderTest


클래스 초기화 Initialization 단계

초기화가 언제 발생되나?

  • 클래스 또는 인터페이스 T 는 다음 중 하나가 처음 발생하기 직전에 초기화된다
    • T 는 클래스이고 T 의 인스턴스가 생성 될 때
    • Tstatic 메소드가 호출 될 때
    • Tstatic 변수가 할당 될 때
    • Tfinal이 아닌 static 변수가 사용될 떄
  • 클래스가 초기화되면 해당 수퍼클래스가 초기화되고(이전에 초기화되지 않은 경우)
  • 기본 메서드를 선언하는 수퍼인터페이스(이전에 초기화되지 않은 경우)도 초기화된다
  • 인터페이스의 초기화는 그 자체로 그 슈퍼인터페이스의 초기화를 일으키지 않는다.
  • 필드 에 대한 참조 static은 하위 클래스, 하위 인터페이스 또는 인터페이스를 구현하는 클래스의 이름을 통해 참조될 수 있지만 실제로 필드를 선언하는 클래스 또는 인터페이스만 초기화한다.
  • Class클래스 및 패키지 에서 특정 메서드를 호출 java.lang.reflect하면 클래스 또는 인터페이스도 초기화된다
  • 클래스 또는 인터페이스는 다른 상황에서 초기화되지 않는다.

✋ 위의 클래스 로드 시점과 같다. 클래스가 로드되면 초기화도 바로 진행된다.

부모클래스는 자식클래스보다 먼저 초기화된다.

class Super { static { System.out.println("Super Init"); } } class One { static { System.out.println("One Init"); } } class Two extends Super { static { System.out.println("Two Init"); } } class Main { public static void main(String[] args) { One o = null; Two t = new Two(); } } Super Init Two Init

부모 인터페이스는 자식 인터페이스가 호출하여도 초기화 하지 않는다.

interface A { int A = 1; int AA = Main.out("AA", 2); } interface B extends A { int B = Main.out("B", 3); int BB = Main.out("BB", 4); } interface C extends B { int C = Main.out("C", 5); int CC = Main.out("CC", 6); } class Main { public static void main(String[] args) { System.out.println("B.A : " + B.A); System.out.println("C.B : " + C.B); System.out.println("C.C : " + C.C); } static int out(String s, int i) { System.out.println(s + " = " + i); return i; } } B.A : 1 B = 3 BB = 4 C.B : 3 C = 5 CC = 6 C.C : 5

초기화 진행 순서

class Main { public static void main(String[] args) { new Single(); } } class Single { static int A = 0; static int B = 0; public Single() { System.out.println("3. 생성자"); } static { System.out.println("A : " + A); System.out.println("B : " + B); A = 10; B = 20; System.out.println("1. 정적 블록"); } public static Temp temp = new Temp(); public static void StaticClass(){ System.out.println("X. 정적 메서드"); } public static class InnerClass{ static { System.out.println("X. 내부 클래스"); } } } class Temp { public Temp () { System.out.println("2. 정적 변수"); } } A : 0 B : 0 1. 정적 블록 2. 정적 변수 3. 생성자

정현준

Loading script...