▼ Why ?
이번에 '싱글톤 패턴' 에 대해 공부하다가 싱글톤 패턴을 구현하는 기법들에 대해서도 공부하게 됐다. 그 여러 기법들 중 'LazyHolder' 기법은 워낙 중요한 기법이기도 하고 클래스 로더 매커니즘과 클래스가 로드되는 시점을 활용한 기법이기 때문에, 그 부분들을 이해할 필요성을 느껴서 공부하게 되었다.
▼ What ?
우선 JVM(Java Virtual Machine)의 클래스 로더(Class Loader)가 어떤 방식으로 작업을 수행하는지를 공부해보고, 이를 통해 자바(Java)의 클래스들이 어떻게 메모리에 올라가고 클래스 멤버들이 언제 초기화되는지를 알아보려고 한다.
▼ 내부(Inner) 클래스를 static으로 선언해주는 이유
내부 클래스를 static으로 선언해주는 이유는 ?
- 결론을 먼저 말하자면, 메모리를 효율적으로 사용하고 외부(outer) 클래스가 'GC(Garbage Collector' 의 대상에서 제외되는 경우를 방지하기 위함이다 !
내부 클래스를 static으로 선언해주지 않는다면..?
- 내부(Inner) 클래스는 외부(outer) 클래스의 인스턴스에 대한 '외부 참조' 를 반드시 갖게 된다. 따라서 내부 클래스가 외부의 멤버를 사용하지 않아도, 숨겨진(hidden) 외부 참조가 생겨난다.
➜ 쉽게 말하면, 일단 아래의 'Inner_Class' 처럼 static으로 선언되지 않은 내부(inner) 클래스를 "비정적(non-static) 멤버 클래스" 라고 한다. 이런 비정적(non-static) 멤버 클래스의 인스턴스는 외부(outer) 클래스의 인스턴스와 '내부적으로 연결'되어 있다고 할 수 있다.
➜ 그렇기 때문에 아래의 코드처럼 내부 클래스에서 외부 클래스의 메서드를 'this' 로 호출 가능한 것이다.
( 단순하게 생각하면, 내부 클래스가 클래스 기능을 할 수 있다고 해도 결국 외부 클래스의 멤버인데, 멤버가 자신을 담고 있는 클래스와 연결되어있지 않다는 사실이 더 이상한 것이다. ) - 외부(outer) 클래스의 인스턴스에 대한 '외부 참조'가 왜 문제가 될까 ?
예를 들어, Main에서 내부(innter) 클래스의 객체를 여러 번 생성하고 배열에 저장해줄 상황이 있다고 가정해보자.
- 내부 클래스의 객체를 생성해주려면 외부 클래스를 '인스턴스화(instantiate)', 즉 외부 클래스를 new 연산자를 이용해 객체를 생성해줘야 한다.
- 그렇게 생성한 외부(outer) 클래스의 인스턴스 변수로 내부 클래스의 객체를 생성하고 반환해주는 'getInnerObject' 메서드를 호출하여 원하는 만큼 내부 클래스의 객체를 생성하여 저장했다고 해보자.
- 그럼 메서드 호출을 위한 용도만으로 생성된 외부(outer) 클래스의 객체는 이제 필요가 없어져 할당된 메모리가 'GC' 에 의해 수거, 즉 힙 메모리에서 삭제되어야 하는 것이 일반적인 메모리 관리의 흐름인데, 수거되지 않고 메모리에 그대로 남아있게 된다 !
- 그 이유가 바로 내부 클래스에서 외부 클래스를 참조, 즉 앞서 말한 '외부 참조' 현상 때문이다.
- 만약, 외부(outer) 클래스의 객체가 메모리를 엄청나게 많이 차지하고 있는 경우라면 메모리가 터져 'OutOfMemoryError' 가 발생하게 된다. (메모리 누수)
- 내부 클래스의 객체를 생성해주려면 외부 클래스를 '인스턴스화(instantiate)', 즉 외부 클래스를 new 연산자를 이용해 객체를 생성해줘야 한다.
public class Outer_Class {
int field = 10;
int getField() {
return field;
}
class Inner_Class {
int inner_field = 20;
int getOuterfield() {
return Outer_Class.this.getField(); // 숨은 외부 참조가 있기 때문에 가능
}
}
}
내부 클래스를 static으로 지정해주는 경우는 ?
- 내부(inner) 클래스가 외부(outer) 클래스의 멤버를 가져와 사용하는 경우가 아니라면 내부 클래스는 반드시 static으로 선언해주자 !
▼ Why ?
이번에 '싱글톤 패턴' 에 대해 공부하다가 싱글톤 패턴을 구현하는 기법들에 대해서도 공부하게 됐다. 그 여러 기법들 중 'LazyHolder' 기법은 워낙 중요한 기법이기도 하고 클래스 로더 매커니즘과 클래스가 로드되는 시점을 활용한 기법이기 때문에, 그 부분들을 이해할 필요성을 느껴서 공부하게 되었다.
▼ What ?
우선 JVM(Java Virtual Machine)의 클래스 로더(Class Loader)가 어떤 방식으로 작업을 수행하는지를 공부해보고, 이를 통해 자바(Java)의 클래스들이 어떻게 메모리에 올라가고 클래스 멤버들이 언제 초기화되는지를 알아보려고 한다.
▼ 내부(Inner) 클래스를 static으로 선언해주는 이유
내부 클래스를 static으로 선언해주는 이유는 ?
- 결론을 먼저 말하자면, 메모리를 효율적으로 사용하고 외부(outer) 클래스가 'GC(Garbage Collector' 의 대상에서 제외되는 경우를 방지하기 위함이다 !
내부 클래스를 static으로 선언해주지 않는다면..?
- 내부(Inner) 클래스는 외부(outer) 클래스의 인스턴스에 대한 '외부 참조' 를 반드시 갖게 된다. 따라서 내부 클래스가 외부의 멤버를 사용하지 않아도, 숨겨진(hidden) 외부 참조가 생겨난다.
➜ 쉽게 말하면, 일단 아래의 'Inner_Class' 처럼 static으로 선언되지 않은 내부(inner) 클래스를 "비정적(non-static) 멤버 클래스" 라고 한다. 이런 비정적(non-static) 멤버 클래스의 인스턴스는 외부(outer) 클래스의 인스턴스와 '내부적으로 연결'되어 있다고 할 수 있다.
➜ 그렇기 때문에 아래의 코드처럼 내부 클래스에서 외부 클래스의 메서드를 'this' 로 호출 가능한 것이다.
( 단순하게 생각하면, 내부 클래스가 클래스 기능을 할 수 있다고 해도 결국 외부 클래스의 멤버인데, 멤버가 자신을 담고 있는 클래스와 연결되어있지 않다는 사실이 더 이상한 것이다. ) - 외부(outer) 클래스의 인스턴스에 대한 '외부 참조'가 왜 문제가 될까 ?
예를 들어, Main에서 내부(innter) 클래스의 객체를 여러 번 생성하고 배열에 저장해줄 상황이 있다고 가정해보자.
- 내부 클래스의 객체를 생성해주려면 외부 클래스를 '인스턴스화(instantiate)', 즉 외부 클래스를 new 연산자를 이용해 객체를 생성해줘야 한다.
- 그렇게 생성한 외부(outer) 클래스의 인스턴스 변수로 내부 클래스의 객체를 생성하고 반환해주는 'getInnerObject' 메서드를 호출하여 원하는 만큼 내부 클래스의 객체를 생성하여 저장했다고 해보자.
- 그럼 메서드 호출을 위한 용도만으로 생성된 외부(outer) 클래스의 객체는 이제 필요가 없어져 할당된 메모리가 'GC' 에 의해 수거, 즉 힙 메모리에서 삭제되어야 하는 것이 일반적인 메모리 관리의 흐름인데, 수거되지 않고 메모리에 그대로 남아있게 된다 !
- 그 이유가 바로 내부 클래스에서 외부 클래스를 참조, 즉 앞서 말한 '외부 참조' 현상 때문이다.
- 만약, 외부(outer) 클래스의 객체가 메모리를 엄청나게 많이 차지하고 있는 경우라면 메모리가 터져 'OutOfMemoryError' 가 발생하게 된다. (메모리 누수)
- 내부 클래스의 객체를 생성해주려면 외부 클래스를 '인스턴스화(instantiate)', 즉 외부 클래스를 new 연산자를 이용해 객체를 생성해줘야 한다.
public class Outer_Class {
int field = 10;
int getField() {
return field;
}
class Inner_Class {
int inner_field = 20;
int getOuterfield() {
return Outer_Class.this.getField(); // 숨은 외부 참조가 있기 때문에 가능
}
}
}
내부 클래스를 static으로 지정해주는 경우는 ?
- 내부(inner) 클래스가 외부(outer) 클래스의 멤버를 가져와 사용하는 경우가 아니라면 내부 클래스는 반드시 static으로 선언해주자 !