-
[Java] 클래스 로드와 클래스 초기화 (StaticInnerClass, InnerClass, MemoryLeak)프로그래밍 언어/Java 2022. 11. 6. 14:21
코드리뷰를 받던 중, "왜 내부클래스를 static 으로 선언하셨어요?" 라는 질문을 받게 되었다.
학생때부터 어떤 이유 때문에 습관적으로 써왔던 것이지만 기억이 안나던 나는 어버버 했다.
그 후 검색을 하여 메모리누수 때문이었다는게 기억이 났고, 다시 리뷰어분과 말씀을 나눴다.
그리고 다시 이런 질문을 받았다.
"static 도 메모리에 올라가고, non-static 도 메모리에 올라가는건 마찬가지 아닌가요? 인스턴스가 초기화 되지 않으면 메모리에 안올라가는건 non-static 이지 않나요?"
클래스 로드와 클래스 초기화는 다르지 않나? 다른게 맞나..?
static 은 클래스뼈대만 stack 영역에 올라가고, 인스턴스 초기화에만 heap 에 올라가는게 아닌가?
non-static 은 인스턴스 초기화 시, heap 에 클래스뼈대정보 + 인스턴스 정보가 올라가는게 아닌가?
non-static 이더라도 heap 에 인스턴스 정보만 올라가고, stack 에 클래스 뼈대만 정보를 올리는게 재사용성이 좋을 것 같은데
JVM 관련 개발자들은 그렇게 개발하지 않았을까?
추측만 무성하고 뭐 하나 자신감있게 제대로 말할 수 없는 나 자신을 보면서 회의감이 들어 다시 정리해보기로 했다.클래스 로드와 클래스 초기화는 다른 용어이다.
클래스 로드는 클래스를 메모리에 로드(클래스 초기화)할 때의 필요한 정보들을 세팅하는 단계이다.
클래스 로드에 사용하는 메모리영역과 클래스 초기화에 사용하는 메모리영역 또한 다르다.class 정보는 JVM 8 버전 이상에서 MetaSpace 영역에 저장된다.
* MetaSpace 는 OS 영역의 NativeMemory 이다.
MetaSpace는 Java의 Classloader가 현재까지 로드한 Class들의 Metadata가 저장되는 공간이다.
static 이던 non-static 이던 로드가 된 Class는 MetaSpace 에 저장된다.
클래스 뼈대정보만 저장이 되므로, 해당 클래스가 인스턴스화를 몇개를 하던지 메모리에는 1개의 정보만 저장이 된다.
아래 스크린샷은 그 증거이다.// Ping9OuterClass.java public class Ping9OuterClass { static{ System.out.println(">> Ping9OuterClass called!"); } public NonStaticInner getNonStaticClass(){ return new NonStaticInner(); } class NonStaticInner { } static class StaticInner{ } }
1. Static InnerClass
2. NonStatic InnerClass
3. Do Nothing
클래스 로드
static class 면 클래스로딩 시점에 클래스가 로드가 되는가?
답은 "아니다" 이다.
static class 이던 inner class 이던 참조가 되는 시점에 클래스가 로드 된다.검증 예제 1
VM option 에
-verbose:class
옵션을 주고 실행하면 로드된 클래스들을 확인할 수 있다.
아래의 예제코드를 돌려보면 로드된 클래스는 OtherOuterClass 와 Ping9OuterClass 둘 뿐이다.
StaticInner 와 NonStaticInner 는 로드되지 않았다.
또한 클래스가 로드만 됬을 뿐 클래스 초기화가 아니기 때문에 "Ping9OuterClass called!" 는 출력되지 않는다.// OtherOuterClass.java public class OtherOuterClass { public static void main(String[] args) { System.out.println(Ping9OuterClass.class); } } // Ping9OuterClass.java public class Ping9OuterClass { static{ System.out.println(">> Ping9OuterClass called!"); } public NonStaticInner getNonStaticClass(){ return new NonStaticInner(); } class NonStaticInner { } static class StaticInner{ } }
검증 예제 2
디버그 모드로 한 라인씩 실행해본다.
아래의 스크린샷과 같이 참조되는 시점에 클래스가 로드됨을 볼 수 있다.
클래스 초기화1 - OuterClass
이번에는 Ping9OuterClass 인스턴스를 생성한다.
클래스가 초기화 되며 "Ping9OuterClass called!" 가 출력되지만
여전히 inner class 들은 로드가 되지 않는다.// OtherOuterClass.java public class OtherOuterClass { public static void main(String[] args) { System.out.println(Ping9OuterClass.class.getClassLoader()); Ping9OuterClass cl = new test.Ping9OuterClass(); } } // Ping9OuterClass.java public class Ping9OuterClass { static{ System.out.println(">> Ping9OuterClass called!"); } public NonStaticInner getNonStaticClass(){ return new NonStaticInner(); } class NonStaticInner { } static class StaticInner{ } }
클래스 초기화2 - Nested Static Class
이번에는 nested static class 만 따로 인스턴스화 한다.
역시 Ping9OuterClass 는 로드도 되지않고, 당연히 JVM 메모리(Java Heap)에도 없다.클래스 초기화3 - Inner Class
이번에는 Inner class 만 따로 인스턴스화 한다.
참조가 일어나는 Ping9OuterClass 와 NonStaticInner 만 클래스 로드 된다.
또한, 메모리에도 NonStaticInnerClass 특성상 OuterClass 도 같이 인스턴스화 된다.
이것이 바로 memory leak 의 원인이다.심지어 InnerClass 갯수만큼 OuterClass 도 같은 갯수로 인스턴스화 된다.
마치며
클래스가 로드 되면 NativeMemory 영역인 MetaSpace(OS) 에 저장이되고,
클래스가 인스턴스화 되면 JVM 메모리영역(Java Heap)에 저장이 된다.
NonStatic Inner Class 이던 Static Inner Class 이던 MetaSpace 에 클래스정보가 저장된다.
NonStatic Inner Class 는 클래스가 인스턴스화 되면 OuterClass 도 같이 인스턴스화 된다.
메모리누수방지를 위해 Static InnerClass 를 사용하자!+ 추가정보
인스턴스 생성, non-final 정적변수 사용, 정적 메소드 호출 시점에 클래스는 로드 되며
해당 클래스의 모든 static 블럭은 초기화 된다.
또한 여러 쓰레드에서 동시에 사용하더라도 1번만 로드 된다.non-final static 변수 사용시 클래스 로드 & static 블럭 초기화
public class Ping9OuterClass { public static final int a = 0; public static int b = 0; static{ System.out.println(">> Ping9OuterClass called!"); } } public class MainClass { public static void main(String[] args) { System.out.println("BEFORE a==="); System.out.println(a); System.out.println("a==="); System.out.println("BEFORE b==="); System.out.println(b); System.out.println("b==="); }
Reference
728x90'프로그래밍 언어 > Java' 카테고리의 다른 글
[Java] Static Block 실행시점과 싱글톤(Singleton)에서의 활용 (0) 2022.12.28 [Java] multipartFile 이미지 리사이징 예제코드 (0) 2022.11.13 [Java/Kotlin] Static nested class 와 Inner Class 중 무엇을 써야할까? (0) 2022.11.06 [Java] 바이트코드 조작하기 (JavaAgent, JVM, Jacoco) (0) 2022.06.06 [Java] JVM 이 무엇인지, 어떻게 작동하는지 대충 훑어보자! (feat. 클래스로더) (1) 2022.05.29