Today I Learn

  1. 자바의 구동방식/Class Loader
  2. Nested Class


자바의 구동방식

자바는 왜 JVM(Java Virtual Machine)에 의해서 구동될까?🤔 운영체제에 관계없이 자바 프로그램을 독립적으로 실행하기 위해서다. JVM이 설치되어 있다면 바이트코드는 어떤 운영체제에서도 실행할 수 있다.


개발자가 .java 확장자로 끝나는 파일을 작성하고 컴파일러를 통해 .class 확장자로 끝나는 바이트 코드로 1차 컴파일한다.(javac 명령어를 사용하면 컴파일 된다) .class 파일은 어떤 운영체제에서 실행할 지 모르기 때문에 프로세서의 고유한 코드가 포함되어 있지 않고 JVM의 기계어인 바이트 코드가 포함되어 있다. 이 후에 JVM을 통해서 구동될 운영체제 리소스에 맞게 2차 컴파일을 한다. 2차 컴파일을 하면 실행코드가 생성된다.




Class Loader

클래스 로더는 JVM의 일부이고 Runtime(실행)하는 시점에 .class 파일을 메모리(Runtime Data Areas)에 동적으로 올려놓는 역할을 한다.

  • 로딩: .class 파일을 JVM 메모리에 로드한다. 그런데 클래스나 클래스 내의 static 멤버 전부를 메모리에 한번에 올리는 것은 비효율적이다. 그래서 그때마다 필요한 클래스를 메모리에 올려 효율적으로 관리한다. 특정 클래스에 static 멤버들이 있더라도 그것을 사용하지 않는 경우에 그 클래스는 로드되지 않는다.

  • 링크: .class 파일을 사용하기 위해 검증하는 과정이다.

  • 초기화: 클래스 초기화는 static 블록(클래스가 로딩되고 클래스 변수가 준비된 후, 자동으로 실행되는 블록)과 static 멤버 변수에 값을 할당하는 것을 의미한다. new 생성자로 클래스를 인스턴스화 해야 클래스가 초기화 되는 것이 아니다. 클래스 초기화는 클래스 로드 시점과 거의 동시에 일어난다. 클래스 초기화는 오직 한번만 수행된다. 클래스 로딩이 최초로 수행될 때, 한번만 초기화하고 그 이후에는 초기화를 스킵한다. 만약 멀티 쓰레드 환경에서 여러개의 쓰레드가 클래스를 동시에 인스턴스화 해도 클래스 초기화는 오직 한번만 수행된다. (이 블로그에서 클래스 로딩 및 초기화에 대해 코드레벨에서 쉽고 자세하게 알려준다)


    Nested Class

public class Outer {
    static class StaticNested {
    }

    class Inner {
    }
}

자바에서는 클래스 안에 클래스가 들어갈 수 있다. Nested 클래스를 이용하면 코드를 간단하게 표현할 수 있다. 자바 기반의 UI 처리나 사용자 입력, 외부 이벤트 처리를 하는 곳에서 가장 많이 사용된다. Nested 클래스는 Static Nested 클래스/Inner 클래스(non-static)로 나뉜다. 이 둘의 차이는 static으로 선언이 됐는지 여부이다. Static Nested 클래스는 한 곳에서만 사용되는 클래스를 논리적으로 묶어서 처리할 때 필요하고 Inner 클래스는 내부 구현을 감추고 싶을 때 사용한다.(캡슐화가 필요할 때) Inner 클래스는 다시 Local 클래스/Anonymous 클래스로 나뉜다.

외부 클래스에서 Static Nested 클래스나 Inner 클래스의 인스턴스 변수로 접근하는 것이 가능할까? 가능하다. private으로 선언 된 변수에도 접근 가능하다.




Static Nested 클래스

public class Outer {
    static class StaticNested {
    }
}

public class Main {
    public static class main(String[] args) {
        // Static Nested 클래스 객체 생성
        Outer.StaticNested staticNested = new Outer.StaticNested();
    }
}

외부 클래스를 컴파일하면 Static 클래스는 자동으로 컴파일 된다. Static 클래스의 객체는 주석을 표시해둔 라인의 코드처럼 만들면 된다. Static 클래스에서는 외부 클래스의 static 변수들만 참조할 수 있다. static 클래스에서 외부 클래스의 non-static한 변수를 참조할 순 없기 때문이다. (아마도 static 클래스에 외부 클래스에 대한 외부참조가 없기 때문이지 않을까..?)

Outer.StaticNested sn1 = new Outer.StaticNested();
Outer.StaticNested sn2 = new Outer.StaticNested();

if (sn1 == sn2) {
    System.out.println("static으로 선언 된 클래스로 객체를 만들면 메모리에 하나만 올라간다.");
} else {
    System.out.println("놉. 다른 클래스로 객체 만드는 것과 똑같이 서로 다른 참조다.");    // 출력됨
}

static 키워드가 붙은 메소드나 변수는 많이 봤지만 class에 static이 붙은 모양은 처음 봤다. 그래서 나는 이 클래스로 객체를 만들면 메모리에 하나만 올라가는건가...? 싶었다. static 변수가 메모리에 하나만 올라가는 것처럼 말이다. 근데 아니였다. 클래스에 static이 붙으면 인스턴스를 생성하는 방법이 달라지는 것 뿐이고, 다른 일반 클래스들과 똑같이 동작한다.




Inner 클래스

public class Outer {
    class Inner {
    }
}

public class Main {
    public static class main(String[] args) {
        // Inner 클래스 객체 생성
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
    }
}

Inner 클래스의 객체를 생성하기 전에 반드시 외부 클래스의 객체를 만들어야 한다. 이 외부 클래스의 객체를 통해서 Inner 클래스의 객체를 생성할 수 있다. Inner 클래스는 외부 클래스의 모든 변수를 참조할 수 있다. private으로 선언 된 변수에도 접근 가능하다.




Static Nested 클래스를 사용하자

Inner 클래스는 외부 클래스로 객체를 생성한 후, 그 객체를 통해 Inner 클래스의 객체를 생성할 수 있다. 그래서 Inner 클래스 객체는 자신을 만들어준 외부 클래스 객체에 대한 외부 참조를 갖게 된다.(이 외부 참조는 숨겨져 있다) 그렇기 때문에 Inner 클래스 객체는 외부 클래스의 인스턴스 멤버들에 접근할 수 있는 것이다. 그러나 Inner 클래스의 객체가 가지고 있는 외부 참조는 메모리 누수라는 치명적인 단점이 있다. 만약 외부 클래스가 필요 없어지고 Inner 클래스만 남아있을 경우, 필요 없어진 외부 클래스는 GC의 대상이 되어 메모리에서 제거되어야겠지만 외부 참조로 내부 클래스와 외부 클래스가 연결 되어 있어 메모리에서 제거가 안 된다.

반면에 Static 클래스는 외부 참조를 하지 않는다. Static 클래스는 외부 클래스 없이도 객체 생성이 가능하다. 그래서 Static 클래스는 외부 클래스의 인스턴스 변수에 접근하지 못하는 것이다. Nested 클래스를 사용해야 한다면 외부 참조를 내포하지 않는 Static Nested 클래스를 사용해야한다.




📚 참고
자바의신
클래스는 언제 메모리에 로딩 & 초기화 되는가
내부 클래스는 static 으로 선언 안하면 큰일 난다
내부(inner) class와 내부(inner) static class 차이
JVM / 자바 프로그램의 실행흐름
About the Java Technology