이너클래스 선언시 위와같이 static 으로 선언해 달라는 IDEA의 잔소리를 한번쯤 봤을것입니다.
Nested class (내부 클래스) 에 대해 간략히 알아보는 시간을 가지며 왜 IDEA 는 내부 클래스 선언시
위와같은 잔소리를 하는것인지 알아보겠습니다.
내부 클래스를 사용하는 이유는 ORACLE-java Tutorial 에 아주 간결하게 명시 되어있습니다.
From the Java Tutorial:
번역기 돌리실 여러분들을 위해 제가 좀 거들어 드리자면
// A 가 B 를 의존하고있음. 이말은 즉슨 A 라는 클래스는 B라는 클래스 없이 작동하지 않음.
public class A{
private B b;
private String useB;
public A(B b){
this.b=b;
}
public void useB(){
this.useB = b.whatever();
}
}
1.인스턴스 클래스
public class Outer{
// 인스턴스 클래스
public class Inner{
}
}
2.스테틱 클래스
public class Outer{
// 스테틱 클래스
static public class Inner{
}
}
public class A{
// 메소드 내부에 생성된 클래스
public void localClass(){
class B{
public String abc;
}
B b = new B();
b.abc = "localClass";
}
}
public interface A{
void anonymous();
}
public class Main{
public satic void main(String[]args){
// 익명클래스
A a = new A(){
@override
void anonymous(){
System.out.println("익명클래스");
};
}
}
}
위의 4가지 Innerclass 종류 중 이번 포스팅의 뜨거운 감자인 인스턴스 클래스와 스테틱 클래스는 어떤것이 다른지 자세히 살펴보겠습니다.
public class Outer{
public class Inner{
}
}
public
스텝바이 스텝으로 따라가 보겠습니다.
public class OuterClass {
int o = 10;
class Inner {
int i = 20;
}
}
현재 Inner 라는 클래스는 인스턴스 클래스로 선언이 되어있습니다. 이를 클래스 파일로 컴파일 후 바이트 코드를 확인 해보면
위 사진처럼 InnerClass 가 OuterClass 를 참조하고 있음을 알 수 있습니다. (this$0 -> OuterClass 를 참조하고있는 바이트 코드에서만 보여 Hidden 변수)
위 차이점 덕에 IDE 애플리케이션에서 내부에서 외부를 참조하지 않을경우 static 클래스로 선언 하라는 잔소를
했음을 알 수 있습니다. 이또한 차근차근 짚어나가 보겠습니다.
@Test
void leak(){
ArrayList<EnclosedClass> al = new ArrayList<>();
int counter = 0;
while (20>counter)
{
al.add(new EnclosingClass(100000000).getEnclosedClassObject());
System.out.println(counter++);
}
}
public class EnclosingClass {
private int[] data;
public EnclosingClass(int size)
{data = new int[size];}
class EnclosedClass{}
EnclosedClass getEnclosedClassObject()
{
return new EnclosedClass();
}
}
위같은 결과가 나온 이유는 GC 가 정상적으로 Unreachable 한 데이터를 수거해가지 못했기때문에 생긴 문제 입니다.
위 코드에서 ArrayList 는 InnerClass 에 대한 데이터만 저장하기 때문에 외부 OuterClass 즉
new int[100000000] 의 데이터를 갖고 있는 클래스는 힙영역에서 GC 의 대상이 되어 수거가 되었어야 합니다.
하지만 내부클래스에서 외부클래스를 참조하고 있는 관계가 형성되어 GC 는 엄청난 데이터를 머금고 있는 외부 클래스를 GC의 대상으로 보지 않고 힙영역에 계속 쌓게 됩니다.
결론-> Heap 영역 퍼버벙~
정상적으로 외부 클래스가 더이상 참조 되지 않았을때 GC 의 소거 대상이 되기 위해선 둘의 참조관계를 끊어야합니다.
- 방법은 위에 명시되어있듯이 static 만 선언해주면 됩니다.
public class OuterClass {
int o = 10;
static class Inner {
int i = 20;
}
}
위 코드를 컴파일 했을경우 아래 .class 바이트 코드는 아래와 같습니다.
static 키워드만 붙였을 뿐인데 this$0 의 히든변수가 사라졌고 더이상 외부 클래스를 참조하지 않습니다.
위에 메모리 Leak 문제를 야기 시켰던 코드 내부 클래스에 static 클래스로 선언한후 다시 돌려보면
@Test
void leak(){
ArrayList<EnclosedClass> al = new ArrayList<>();
int counter = 0;
while (20>counter)
{
al.add(new EnclosingClass(100000000).getEnclosedClassObject());
System.out.println(counter++);
}
}
public class EnclosingClass {
private int[] data;
public EnclosingClass(int size)
{data = new int[size];}
static class EnclosedClass{}
EnclosedClass getEnclosedClassObject()
{
return new EnclosedClass();
}
}
더이상 메모리 Leak 문제를 야기시키지 않습니다. 외부에 어마어마한 데이터를 머금고 있는 인스턴스가 GC 의 대상이 되었기 때문입니다.
내부클래스가 외부클래스를 참조하지 않는다면 인스턴스 클래스보다는 static 클래스로 선언하자.
reference:
https://www.infoworld.com/article/3526554/avoid-memory-leaks-in-inner-classes.html
https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
https://tecoble.techcourse.co.kr/post/2020-11-05-nested-class/