개발/java

JVM 클래스 로드

냥덕_ 2024. 8. 27. 23:27

클래스 로더란?

클래스 로더는 컴파일 된 자바의 class 파일을 동적으로 로드하고 JVM 내 Runtime Data Area에 배치하는 작업을 수행한다.

클래스 로더에서 class 파일을 로딩하는 순서는 아래 세단계로 이루어진다.

  • Loading : 클래스 파일을 가져와서 메모리에 로드
  • Linking : 검증
  • Initialization : 변수 초기화

이때 로딩 기능은 한번에 메모리에 올리는 것이 아니라 어플리케이션 내에 필요한 경우에 동적으로 메모리에 적재한다.

우리는 클래스나 클래스 내의 static 멤버들은 실행과 동시에 메모리에 올라간다고 착각하기 쉽다.
하지만 생각해보면 언제 사용할지도 모르는데 모두 올리는 건 비효율적이라는 것을 알 수 있다.

결과적으로 JVM은 모든 클래스를 메모리에 올리는 것이 아니라 클래스 멤버를 호출하게 될 경우 즉 필요한 경우에 로드를 하게 된다.

1. 클래스 로드 테스트

public class Outer {
    static String v = ">Outer 클래스 내 static 필드";

    static final String VALUE = ">Outer 클래스의 static final";

    static class Holder {
        static String v2 = ">Inner 클래스 내 static";

        public Holder() {
            System.out.println("> Holder Constructor init");
        }
    }

    class Inner {
        static {
            System.out.println(">Inner init 1");
        }

        public Inner() {
            System.out.println("> Inner Init 2");
        }
    }
}

//#1
public class ClassLoaderTest {
    public static void main(String[] args) {
        new Outer();
    }
}

//#2
public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Outer.v);
    }
}

//#3
public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Outer.VALUE);
    }
}

Outer 클래스 내부에 static inner 클래스를 정의해놓고 ClassLoaderTest java verbose를 활용해 JVM 내에 로드된 클래스들을 보자!

#1을 보면 new Outer()를 통해 인스턴스화하기 때문에 ClassLoaderTest, Outer만이 로드되고 static inner 클래스의 경우는 로드되지 않는다.
#2에서 Outer 내부의 static 변수를 접근할 경우에는 인스턴스화를 하지 않아도 클래스가 로드된다.
#3의 경우 정적 static 변수를 접근하며 이때는 JVM 내의 Method 영역에 저장되기 때문에 인스턴스화 하지 않고 해당 영역에서 접근하게 된다.

java -classpath C:\dev\design-pattern\out\production\design-pattern -verbose:class singleton.classload.ClassLoaderTest

결과

[0.125s][info][class,load] singleton.classload.Outer source: file:/C:/dev/design-pattern/out/production/design-pattern/
[0.123s][info][class,load] singleton.classload.ClassLoaderTest source: file:/C:/dev/design-pattern/out/production/design-pattern/

2. 내부 클래스 호출

Outer 객체 내부의 Inner 객체를 인스턴스화 하며 생성할 경우 아래처럼 당연히 3개의 클래스가 로드된걸 알 수 있다.

public class ClassLoaderTest {
    public static void main(String[] args) {
        new Outer().new Inner();
    }
}
[0.069s][info][class,load] singleton.classload.ClassLoaderTest source: file:/C:/dev/design-pattern/out/production/design-pattern/
[0.071s][info][class,load] singleton.classload.Outer$Inner source: file:/C:/dev/design-pattern/out/production/design-pattern/
>Inner init 1
[0.072s][info][class,load] singleton.classload.Outer source: file:/C:/dev/design-pattern/out/production/design-pattern/
> Inner Init 2

3. static 내부 클래스 호출

  • static inner 클래스의 경우는 외부 클래스를 생성하지 않고 바로 인스턴스화가 가능하다.
  • 쉽게 말해 static class의 경우 Outer를 로드하지 않고 해당 inner 클래스만 로드가 가능하다.
//#!
public class ClassLoaderTest {
  public static void main(String[] args) {
      new Outer.Holder();
  }
}

//#2
public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println(Holder.v2);
    }
}
결과
#1
[0.093s][info][class,load] singleton.classload.ClassLoaderTest source: file:/C:/dev/design-pattern/out/production/design-pattern/
[0.094s][info][class,load] singleton.classload.Outer$Holder source: file:/C:/dev/design-pattern/out/production/design-pattern/
> Holder Constructor init

#2
[0.081s][info][class,load] singleton.classload.ClassLoaderTest source: file:/C:/dev/design-pattern/out/production/design-pattern/
[0.081s][info][class,load] singleton.classload.Outer$Holder source: file:/C:/dev/design-pattern/out/production/design-pattern/
>Inner 클래스 내 static

로드와 초기화

초기화란 객체를 선언하고 값을 최초로 할당하는것을 의미한다. 클래스 초기화와 로드는 거의 동시에 일어난다고 보면 된다.

초기화 순서(참고)

초기화 순서는 크게 클래스 초기화와 인스턴스 초기화 두가지 측면에서 볼 수 있다.

  • 클래스 초기화는 클래스가 처음 로드될때 최초 한번 초기화 하는것을 의미
    • 초기화 동작 자체는 스레드 세이프
  • 인스턴스 초기화는 새로운 인스턴스가 생성될 때마다 각각의 인스턴스 초기화를 의미

인스턴스를 생성한다고 가정한 순서이다.

  1. 클래스 변수 기본값 할당
  2. 클래스 변수 명시적 초기화(ex : static int v1 = 10)
  3. 클래스 초기화 블럭
  4. 인스턴스 변수 기본값 할당
  5. 인스턴스 명시적 초기화
  6. 인스턴스 초기화 블럭
  7. 생성자
public class ClassInit { 
    static int v1; int v2; 
    { 
        System.out.println("4. instance init!!"); 
        System.out.println("5. v1 : " + v1 + " v2 : " + v2); 
    } 
    static { 
        System.out.println("1. v1 === " + v1); 
        System.out.println("2. load init!!"); 
        v1 = 10; System.out.println("3. v1 === " + v1); 
    } 
    public ClassInit() { 
        System.out.println("6. call constructor!!"); 
    } 
}
v1 === 0
load init!!
v1 === 10
instance init!!
v1 : 10 v2 : 0
call constructor!!

로드와 초기화 분리 해보기

로드와 초기화가 동시에 진행된다고는 하지만 엄연히 메모리 적재 과정은 나눠짐 이를 reflection API를 활용하여 확인 가능하다.

public class ClassLoaderTest {
    public static void main(String[] args)
            throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        System.out.println("1.load!!!!!!!!!!");
        Class<? extends Outer> outerClass = Outer.class;

        System.out.println("===============================================");

        System.out.println("init!!!!!!!");
        Outer outer=outerClass.getDeclaredConstructor().newInstance();
        System.out.println(outer.tmp);
    }
}
1.load!!!!!!!!!!
[0.076s][info][class,load] singleton.classload.Outer source: file:/C:/dev/design-pattern/out/production/design-pattern/
===============================================
init!!!!!!!
1

참고
클래스 로드
초기화

'개발 > java' 카테고리의 다른 글

[JAVA]객체지향과 객체지향 프로그래밍(OOP)  (0) 2023.07.31
[JAVA]클래스, 객체, 인스턴스  (0) 2023.07.20
[JAVA]Call By Reference, Call By Value  (0) 2023.07.14
[JAVA]JVM  (0) 2023.07.06
[JAVA]String  (0) 2023.07.04