* 이 포스트는 학습 과정에서 그 내용을 기록한 글이기에 부정확한 정보가 포함될 수 있습니다. 따라서 해당 글은 참고용으로만 봐주시고 틀린 부분이 있다면 알려주시면 감사하겠습니다.
❗ 이번 포스트는 인프런 강의 중 토비 님의 '토비의 스프링 부트 - 이해와 원리'를 수강하고 배운 내용을 정리하여 작성하였습니다. 그렇기에 조금 더 내용을 깊게 알기 원하시면 직접 강의를 수강하시길 추천합니다. 또한 해당 내용은 토비 님의 유튜브 채널 링크 https://www.youtube.com/watch?v=2G41JMLh05U를 같이 보시면 좋습니다.
❗ 이번 포스팅은 지난 글인 https://fadet-coding.tistory.com/26 에서 이어집니다.
❗ 포스트를 보실때 미리 알면 도움이 될 지식 : 자바(JVM 메모리 파트), 스프링부트(스프링빈과 어노테이션)
* 잘 모르시는 기술은 로그인 필요 없이 이 곳에서 AI에게 물어보세요!
이번 포스트는 사실 스프링 부트를 학습하면서 공부한 내용이지만 내용상 저번에 작성했던 JAVA 관련 포스팅과 긴밀하게 이어져서 JAVA 카테고리로 정했습니다.
지난 포스트에서 static class에 대해서 짧게 언급을 했었는데, 자바를 처음 공부할 때의 글이기도 하고 복습할 겸 다시 읽어보니 좀 부정확한 표현도 있더라고요. 그래서 개념도 다시 정리하고 강의 내용도 복습할 겸 그리고 이전 포스트에서 간단히 설명하고 넘어간 내용에 대해 추가 포스팅하려고 합니다.
저번에 Nested Class(이후로 중첩 클래스라고 표기)를 얘기하면서 중첩 클래스는 되도록 static class로 만드는 것을 권장한다고 넘어갔었는데, 이 말은 따지고 보면 정확하지 않습니다.
또한 자바를 배우신분들은 중첩 클래스가 일반 중첩클래스(inner nested class)와 스태틱 중첩클래스(static nested class)로 나뉜다고 알고 계실 것이고 저도 그렇게 알고 있습니다. 하지만 단순히 이렇게 이분법적으로 개념에 접근하면 다소 부정확하게 알고 넘어가실 것이라고 생각합니다. 이에 대해서는 지금 결론짓지 않고 쭉 내용을 정리한 다음 포스트 후반부에 결론을 짓겠습니다.
지난 포스트에서 제가 모든 Outer Class(.java파일을 구성하는)를 사실상 (static) Class로 이해하는게 낫다는 언급을 했었습니다. 사실 입문서 어느걸봐도 (static) Class에 대한 내용은 없습니다. 또 제가 말한 것처럼 논리적으로 이해할때 편하다 뿐이지 실제로는 Outer class 앞에 static이 붙을 수 없습니다. 그런데 왜 제가 이런 얘기를 꺼냈을까요? 다음 문단부터는 그에 대한 자세한 얘기를 해보겠습니다.
Nested Class(중첩 클래스)를 포함하는 가장 상위 클래스를 보통 Outer Class, Top-level Class로 표현하는데 여기선 그냥 짧으니 Outer Class로 칭하겠습니다. 여기서 지난번 포스트에 작성했던 얘기를 꺼내보자면 우리는 static nested class에 대해선 배운 적이 있습니다만 static outer class에 대해선 들어본 적도 배운 적도 없습니다.
class Outer { // static class Outer X
static class Inner // static class Inner O
}
그래서 왜 outer class엔 static이 붙을 수 없는가에 대해 찾아보았고 이에 대해 가장 많은 공감을 얻은 답변을 stackoverflow에서 찾아보았더니 아래와 같았습니다.
All top-level classes are, by definition, static.(모든 상위 클래스들은 정의상으론 정적입니다.)
한마디로 모든 상위 클래스 앞에는 (static)이 붙어 있단 소립니다. 하지만 찾아보다 보니 공식적으론 정확하게 사실과 일치하지는 않습니다. 위의 문장이 stackoverflow에서 가장 많은 투표를 받은 답변이고 아래는 그다음 투표수를 받은 답변입니다.
Simply put, a top-level type declaration cannot be static, because the Java Language Specification(JLS) doesn'tsay that it can be. The JLS says this explicitly about the static keyword as a modifier of top-level classes:
답변 내용은 자바 공식 문서는 top level(outer) 클래스, 지역 클래스, 익명 클래스는 static 키워드를 붙일 수 없도록 제한할 뿐이라고 되어있습니다. 해당 답변을 더 찾아 읽어보시면 JAVA에서 규정된 static이 붙을 수 있는 요소들을 나열하고 끝에 정의상으로 정적이라는 이전 답변은 사실이 아님을 말합니다.
stackoverflow를 더 살펴보고 다른 글들도 찾아보며 내린 결론은 후자의 답변인 공식 문서가 더 맞지 않을까였습니다. 아니 공식 문서가 그렇게 하라는데 이게 맞겠죠? 그런데 좀 찝찝합니다. 그리고 또 왜 제가 맞다고 생각하지도 않는 답변을 먼저 소개해드렸을까요?
결국엔 저도 궁금했던 거예요. '아니 도대체 왜 공식 문서에서 그렇게 소개하지도 않은 내용을 그렇게 많은 개발자들이 공감할까?'. 실제로 저 답변 말고도 많은 답변들이 저것과 거의 같은 뉘앙스였습니다. 또 제가 처음 배울 때도 그렇게 알고 넘어갔었거든요. 그래서 궁금해졌습니다. 공식 문서상에는 기술되어있지 않지만 많은 개발자들이 말하는 대로 outer class가 정말 암시적으로 static과 같이 작동하는지 말이죠. 이것을 이해하기 위해선 JVM의 작동 방식에 대해서 좀 알아야 할 필요가 있습니다.
* 이 글을 작성하며 더 찾아봤는데 Java SE 19 Specification에 outer class도 암시적으로 static이라는 문구가 있다고 합니다만 사실 두 답변이 서로 배치되는 말을 하는 건 아니었기도 하고 제 글의 결론 자체도 두 답변 모두 인정하기 때문에 더 언급하지 않고 여기서 넘어가겠습니다. 제가 참고한 질문은 https://stackoverflow.com/questions/7370808/why-cant-a-top-level-class-be-static-in-java입니다.
실제로 intelliJ같은 IDE상에서 class 앞에 static을 붙여보면 'Modifier 'static' not allowed here'라며 컴파일이 불가능합니다. 만약 논리적인 개념이 아닌 실제 문법상으로도 (static) class가 맞다면 컴파일이 가능하거나 static이 비활성화되어야 할겁니다.(인터페이스의 경우 멤버 메서드에 생략이 가능한 public abstract를 붙이면 자동 비활성화됩니다.)
JVM의 메모리 관리

이번 포스트에선 JVM이 어떻게 동작하는지 상세하게 설명하지는 않겠습니다. 따라서 더 자세히 알고 싶으시다거나 아예 모르신다면 참조 링크를 이용해 주세요.
# Class Loader
일단 이 글을 보신다면 우리가 *. java 파일에 JAVA 언어로 코드를 짜면 컴파일러는 이를 *. class로 된 바이트코드 파일을 생성한다는 것 정도는 아실 겁니다. 여기서 우리가 알아야 할 첫 단계는 Class Loader입니다. 클래스 로더는 .class로 된 바이트코드를 엮어서 Runtime Data Area로 load 합니다.
이 이후에 적재된 바이트코드를 engine이 실행하는데 이 과정에서 인터프리터와 JIT 컴파일러가 혼합되어 쓰이고 이 이후 주로 Runtime Data Area의 Heap 메모리 영역을 청소하는 Garbage Collector가 관여합니다. 간단하게 한 줄로 넘어갔지만 이 부분도 잘 모르신다면 링크를 통해 더 자세히 공부하시길 추천합니다.
# Runtime Data Area - Method Area
이다음 우리가 알아야 할 두 번째는 Method Area입니다. 이전 글을 보셨거나 메모리 관리에 대해 공부해 보셨다면 Heap이나 Stack 정도는 아실 것이라 생각합니다만 의외로 Method Area에 대해 잘 알고 계신 분들은 많지 않은 것 같더군요,
그런데 그게 어쩌면 당연하긴 합니다. 사실 Method Area는 Java에서 개발자가 대부분 코드로 성능을 개선하고 주로 살펴보는 부분은 아니거든요. 그래서 짧게 언급하고 넘어가겠습니다.
Method Area는 MetaSpace라고도 불리지만 일부에선 Class Area, Static Area로도 불립니다. 이름처럼 이곳엔 클래스와 인터페이스의 타입 정보, 그 메서드, 필드의 정보와 런타임 상수 풀 등을 보관합니다. 한마디로 static 필드는 다 이곳에 보관되는 거죠. 우리가 이전 포스트에서 메모리가 사용될 때 DATA 영역에 static이 저장된다고 들었을텐데 JVM에서 쓰는 명칭은 살짝 다른거죠. 한마디로 우리가 학습할 때 Method Area, DATA Area, Class Area, Static Area는 모두 같은 개념이라고 생각하시면 편합니다. 인스턴스와 객체는 분명 다른 개념이지만 자바를 배울땐 혼용해서 칭하는 것과 비슷하겠죠?

Static
우선 자바를 입문하면 Static은 변수나 메서드 같은 필드 앞에 붙여서 객체를 매번 생성할 필요 없이 하나의 멤버를 어디서나 참조할 수 있도록 하는 접두 키워드고, 이를 잘 이용하면 메모리 관리에도 이점이 있다고들 배웁니다.
그래서 처음 배울 때 책으론 클래스 멤버는 뭘 참조할 수 없고, 인스턴스 멤버는 어쩌고... 하면서 좀 복잡하게 배우기 때문에 이를 좀 간단히 이해해 보자 하고 쓴 게 이전 포스트였습니다. static 멤버나 static 블록처럼 static 키워드가 들어가면 모두 static area에 저장돼서 heap에서 객체를 생성할 필요 없이 언제나 참조할 수 있다라고요. 그러면 우리가 뭐 클래스 멤버 인스턴스 멤버 이렇게 구분할 필요 없이 코드를 짜는데 도움이 될 것 같았거든요.
여기서 아까 얘기를 다시 해보자면 '어차피 static 키워드가 붙은 요소들은 함께 static area에 저장되고 outer class도 사실상 (static)이 생략되어 있다고 생각하면 모두 한 곳에 저장되어 있다고 생각할 수 있어서 편하다.' 이렇게 얘기했었는데, 이 말을 들으니 뭔가 생각나는 게 있지 않으세요?
맞습니다. 많은 개발자들이 얘기하던 정의상으론 outer class는 이미 static이다라는 답변이 제가 말한 것과 정확히 일치합니다. 어쩌면 당연한 말이었을지도 모릅니다. 의미상 static이란건 dynamic의 반대말이고 동적으로 객체를 생성하며 할당하지 않고 클래스 로더 과정에서 참조할 수 있는 요소들을 만드는 과정은 전부 static으로 생각하면 되니까요.
이를 자바 공식 문서상에선 hierarchy를 명확히 해야 했거나 OOP 언어적 특성을 확실히 하기 위해서 outer class에 static을 붙이는 걸 제한했다고 생각하지만 이 규정만 위배되지 않는다면 논리적으로 이해하는데 문제 될 것이 없습니다. 그렇기에 많은 개발자들이 outer class는 이미 static이므로 또 static 키워드를 붙일 이유가 없다고 말한 겁니다.
Outer Class와 Static Nested class
왜 static outer class란 존재하지 않는가?에 대한 가장 간단한 답은 oracle이 말한 outer class에 static이 붙는 걸 제한한다라는 문법일겁니다. 하지만 실제 문법에 위배되지 않는다면 논리적인 구조도는 개념 이해에 큰 도움이됩니다.
더 나아가 이렇게 이해한다면 처음 화두로 꺼낸 이야기에 대해서도 쉽게 이해가 되실 겁니다. 'nested class는 static으로 만들 것을 권장한다.' 이것은 IDE에서도 경고 메시지로 출력되는 내용입니다. 이 개념을 암기식으로 'nested class의 종류가 static과 non-staic 두 가지로 구분되고 각각 용례는 이렇다. ~' 이렇게 외우시면 꽤나 헷갈리실 겁니다.
하지만 static nested class를 중첩 클래스로 생각하지 않고 아예 같은 static이 붙은 (static) outer class와 큰 차이가 없다고 생각하면 정말 쉽게 넘어갈 수 있는 부분입니다. 단지 코드상 위치가 다른 것뿐이고 앞선 둘을 그냥 같은 class로 생각하여 실질적으로 nested class는 nested inner(non-static) class(이후 이너클래스) 하나만 있다고 생각하면 코드를 짤 때 전혀 헷갈리지 않습니다.
이게 단순히 말만 번지르르하게 맞춘 것이 아니라 메모리를 이용해 직접 코드상으로 실행해도 그렇습니다. 코드를 예시로 보여드리면 아래와 같이 non-static인 nested class인 Inner를 호출해 보면
class Outer {
Outer() { System.out.println("> Outer 생성자 초기화"); }
// non-static nested class
class Inner {
Inner() { System.out.println("> Inner 생성자 초기화"); }
}
}
public class Main {
public static void main(String[] args) {
new Outer().new Inner(); // 내부 클래스 인스턴스화
}
}

코드 상으로도 알 수 있지만 위와 같이 outer class를 먼저 로드하고 로드만 하는 게 아니라 인스턴스화시키기 때문에 우리가 흔히 아는 메모리 누수가 발생할 위험이 있습니다. 하지만 아래와 같이 static nested class인 Hoder를 호출해 보면
class Outer {
// static nested class
static class Holder {
static String value = "> Holder 클래스의 static 필드 입니다.";
static final String VALUE = "> Holder 클래스의 static final 필드 입니다.";
Holder() { System.out.println("> Holder 생성자 초기화"); }
}
}
public class Main {
public static void main(String[] args) {
new Outer.Holder(); // static 내부 클래스 인스턴스화
}
}

위와 같이 Outer는 인스턴스화되지 않고 오직 Holder만 인스턴스화되는 것을 볼 수 있습니다.
물론 동적으로 클래스 정보를 담는 클래스로더 시점에서 static nested class가 있다면 Method area에 그게 포함된 class를 로드하긴 합니다만 실무에서 일하는 지인에게 물어본 바로는 그 정도는 Heap 관리를 못해서 GC 등으로 인해 생기는 메모리 누수에 비하면 미미한 정도라고 하기도 하고 제 생각에도 크게 영향을 끼칠 것 같진 않습니다. 이 부분에 대해서는 더 자세히 아시는 분 있으시다면 덧글 달아주시면 감사하겠습니다.
따라서 Outer class와 static nested class는 사실상 동일시해도 큰 문제는 없습니다. 굳이 outer class로 작성하면 되지 왜 static nested class를 쓰냐라고 물어보신다면 우리가 패키지 같은 폴더를 사용하는 이유와 같다고 말씀드릴 것 같습니다. 또한 특정 outer class에서만 사용되는 static nested class가 있다면 코드 가독성 면에서도 좋고요. 그리고 간단히 코드를 짤 때 짧은 클래스를 매번 만들었다 지웠다 하기도 귀찮을 때 있잖아요? 그때 간단하게 사용할 수도 있고요.
스프링 빈과 nested class
❗ 앞으로의 내용은 스프링 부트의 기본적인 내용을 숙지하신 분이 아니라면 이해하기 어려울 수 있습니다. 해당 부분을 보고 싶은데 스프링 부트를 잘 모르신다면 인프런에서 토비 님의 '토비의 스프링 부트 - 이해와 원리' 강의를 들어보시거나 스프링에 대한 기초를 학습하고 오시는 걸 추천드립니다.
포스팅하려던 주 골자는 모두 얘기했고 지금부터는 순수 자바 환경이 아닌 스프링 환경에서의 nested class 얘기를 하려고 합니다. 이 부분은 사실 토비 님의 유튜브 영상으로 모두 설명이 되어 있어서 영상과 똑같은 얘기일 텐데 글로 짧게 풀어보겠습니다. 링크는 https://www.youtube.com/watch?v=2G41JMLh05U 입니다.
위에서 살펴본 바로 순수 자바 환경에서 static이 아닌 nested class, 즉 이너클래스는 반드시 인스턴스화될 때 이너클래스가 포함된 outer class가 먼저 인스턴스화되고 이루어졌습니다. 이 점을 알고 스프링을 학습하다 보면 다음과 같은 문제를 만나게 됩니다.
스프링 부트에서 자바 클래스를 스프링 빈으로 등록하는 방법엔 @Component 어노테이션을 클래스 상단에 붙여준다는 것은 아실 겁니다. 만약 그렇다면 아래와 같은 코드에선 Inner class가 스프링 빈으로 등록돼서 콘솔창에 println메서드가 실행될까요?
@SpringBootApplication
public class OuterClass() {
// non static nested class
@Component
class InnerClass {
}
}
public static void main(String[] args) {
ConfigurableApplicationContext ac = SpringApplication.run(SpringBeanTest.class, args);
System.out.println(ac.getBean(OuterClass.class));
}
지금까지 글을 읽어오셨다면 이 코드는 실행 후 예외를 던져야 맞습니다. 왜냐하면 우리는 InnerClass를 인스턴스화하기 전에 먼저 OuterClass를 인스턴스화하는 과정을 빠트렸으니까요. 하지만 유감스럽게도 위의 코드는 잘만 실행됩니다. 그렇다면 그 이유를 아시겠나요? 스프링 부트가 마법사라서?
물론 자세히 이 과정을 자세히 설명하고 싶지만 이 부분은 오늘 포스트의 주 이야깃거리가 아니기도 하고 소개드린 강의와 유튜브 영상으로 자세히 나와있기도 하니 간단히 설명하고 넘어가겠습니다.
이유는 저 코드에서 OuterClass는 이미 인스턴스화되었기 때문입니다. 그런 코드가 안 보이신다고요? 잘 안 보이실 겁니다. 바로 OuterClass위에 붙은 @SpringBootApplication 때문이니까요.
@SpringBootApplication에 대해 찾아보면 아래와 같습니다.

스프링을 학습하시다 보면 메타 애노테이션에 대해서 배우실 텐데요. 쉽게 말해서 애노테이션위에 붙는 애노테이션을 말하는 건데 그게 붙으면 동일한 효과를 얻는다고 보시면 편합니다. @SpringBootApplication에 붙은 @SpringBootConfiguration이라는 메타 애노테이션이 보이시나요? 이 애노테이션을 따라가다 보면 @Component 애노테이션이 붙어 있습니다. 따라서 OuterClass도 사실상 @Component가 붙어 스프링빈으로 이미 등록이 된 것입니다.
그렇기에 InnerClass를 스프링빈으로 등록하기 전에 컨테이너는 앞서 스프링빈으로 등록된 오브젝트를 뒤져 Outer클래스가 있는지 확인하고 이게 이미 인스턴스화되었기 때문에 InnerClass도 문제없이 스프링빈으로 등록된 것입니다.
이번 글은 서두에서 얘기한 것처럼 스프링 부트 강의를 듣던 중 개념을 복습하기 위해서 작성했습니다. 글을 작성하다 보니 GC 등 Execution처럼 JVM의 세부 작동 원리와 그 과정에서 메모리 성능 개선 등에 대한 글도 찾아봤는데 나중에 기회가 되면 관련 부분도 포스팅해 보도록 하겠습니다.
refer
토비의 스프링 부트 - 이해와 원리 - 인프런 | 강의
스프링 부트의 핵심 기능을 직접 만들어보면서 스프링 부트의 동작 원리를 이해하고, 이를 통해 스프링 부트를 잘 학습하고 사용하는 방법을 배우는 강의입니다. 스프링 부트가 사용하는 스프
www.inflearn.com
☕ JVM 내부 구조 & 메모리 영역 💯 총정리
저번 포스팅에서는 JRE / JDK / JVM에 대해서 간략하게 알아보는 시간을 가졌다면, 이번 포스팅에서는 JVM의 내부 구조에 대해 좀 더 자세하게 알아보도록 할 예정이다. JVM(자바 가상 머신)은 자바 언
inpa.tistory.com
☕ 클래스는 언제 메모리에 로딩 & 초기화 되는가 ❓
JVM의 클래스 로더 (Class Loader) 자바의 클래스들이 언제 어디서 메모리에 올라가고 클래스 멤버들이 초기화되는지, 원리를 알기위해선 우선 JVM(자바 가상 머신)의 클래스 로더(Class Loader)의 진행
inpa.tistory.com
https://www.youtube.com/watch?v=2G41JMLh05U
'JAVA' 카테고리의 다른 글
JAVA_3_Static(+메모리) (0) | 2022.04.14 |
---|---|
JAVA_2_몬티홀 문제_2(코드로 접근) (0) | 2022.02.06 |
JAVA_2_몬티홀 문제_1(일반적인 접근) (0) | 2022.02.06 |
JAVA_1_프로그래밍 입문 언어?(자바와 파이썬) (0) | 2022.02.05 |