본문 바로가기

Java/Questions

중첩클래스와 정적클래스 (Static과 "메모리에 올린다"의 의미)

중첩클래스를 공부하던 중 의문이 생겼다.

먼저 중첩클래스의 바깥 클래스A 가 인스턴스 클래스, 내부 클래스B 또한 인스턴스 클래스인 경우 내부 클래스에 접근하는 방법은 다음과 같다.

 

A a = new A(); A.B b = a.new B();

혹은

A.B b =  new A().new B();

 

바깥 인스턴스클래스인 A를 먼저 객체생성해야, 이 참조를 통해 안쪽에 있는 인스턴스클래스B에 접근 가능하고 객체생성할 수 있다는 논리이기 때문에 쉽게 납득이 갔다.


그러나

외부A는 인스턴스, 내부 클래스B가 정적 클래스일 때 내부클래스에 접근하는 방법에서 의문이 생겼다.

 

A.B b = A.new B();

 

 1. 중첩클래스를 싸고 있는 바깥 클래스가 여전히 인스턴스 클래스인데, 어떻게 바깥 클래스를 객체생성하지 않고 내부클래스 B에 접근하지? A는 객체생성하되, B는 객체생성 하지 않아도 접근 가능해야하는 게 아닌가? 따라서 A.B b = new A().B;가 되어야 하지않는가?

 

    → static의 핵심은 static @를 감싸고 있는 것이 무엇이든지간에 객체생성없이(독립적으로) 클래스명으로  바로접근이 가능하다는 점을 잊고 있었다.             즉 A의 객체 생성이 필요없이 클래스명 A.B로 사용이 가능하다는 얘기였다.

 


 

 

2. 왜 정적클래스인데도 new해야 하는건지?
여전히 의문이 남았다. 그렇다면 A.B.methodB();가 되어야하는 게 아닌가?  이것이 평소 methodB가 정적으로 선언되었을 때 methodB를 사용한 방법이잖아. new하지 않아도 컴파일 시 알아서 메모리 생성이 되어야 하지 않나하는 생각이었다. 

 

  → 힌트는 정적 메소드나 정적 필드가 아닌 클래스 자체가 '정적클래스'는 점이다.  클래스는 정적, 동적에 관계없이 클래스만 선언되었을 때는 설계도에 불과하다. 정적 클래스를 포함한 모든 클래스가 실제로 사용되기 위해서는 객체로 생성(new, 인스턴스화)되어야한다. 

 

참고로, 자바에서 정적클래스는 내부클래스로만 선언이 가능하다, 즉 정적 내부클래스란 굉장히 특수한 경우이다. 정적 내부클래스 안의 인스턴스 멤버에 접근하기 위해서는 클래스의 인스턴스화가 필요하다. (다만, 정적 내부클래스 안의 정적 멤버 접근은 인스턴스화 없이 가능하다. A.B.methodB();)

따라서 정적 내부클래스의 객체화 및 올바른 사용 방법은 다음과 같이 된다.

 

A.B b = A.new B();

b.methodB();

//여기서 methodB는 인스턴스메소드

 

 


 

 

3. 아니, 클래스는 이미 메모리에 올라가 있지 않나?라는 의문이 또 꼬리를 물 수 있다. 메모리에 올라갔는데 왜 인스턴스화를 해야해? 메모리에 올라갔다 = 인스턴스화되었다 같은 말 아닌가? 결론부터 얘기하면 항상은 아니다.

 

의문점을 풀기 위해 일단 자바의 소스작성부터 메모리 생성 및 실행 순서를 다시 살펴본다.

 

1. 파일 확장명이 .java인 텍스트 파일을 생성하고 소스를 작성 (이클립스에서는 소스 작성)
2. 컴파일러(javac.exe)로 바이트코드파일(.class) 생성 (이클립스에서는 저장) ------------아직 메모리할당 X
3. JVM 구동 명령어(java.exe)로 실행 (이클립스에서는 실행) : 바이트 코드를 검증 > 올바를 때 메모리로 로드, 기계어로 번역
4. main() 메소드를 찾아 실행

지금까지 static은 컴파일 시에 메모리에 올라간다고 배워왔지만, 사실 그 이후이다.
정확히는 JVM이 구동되어 클래스로더가 클래스와 구성요소(여기선 static 멤버)를 메모리 영역 중 Class Area(= Static Area, Method Area)에 적재할 때 메모리에 올라가는 것이다. 

즉, 메인 메소드가 실행되기 전에 static이 메모리에 올라가는 것은 사실이다.
다시 의문이 남는다. 그렇다면 static이 메모리에 올라가는 시점과 실행되는 시점이 같다는 말은 어떻게 봐야할까? 
지금까지 이 말을 오해하여 static이 실행되는 시점(예를 들어 static method가 실행되는 시점)에 실제로 메모리에 적재된다고 해석하고 "그래서 static이 메모리에 올라가는 진짜 시점이 도대체 언제냐"란 미궁에 끊임없이 빠졌었다.

지금의 내 해석은 메인 메소드가 실행되기 이전에 static이 메모리에 할당되어 실행될 준비를 마친다는 것이다.
실제 실행 시점은(콘솔에 보여지는 순서를 기준으로) static method이든 static field이든 필요로 하여 사용할 때 가장 먼저 static block이 있다면 실행되고, 이후 static이 포함된 해당 명령을 실행한다.

 

여기서 메모리에 올라갔다는 말은 JVM이 구동될 때 메소드영역(클래스영역)에 클래스가 올라간 것을 의미한다. 컴파일 시에 클래스로더가 메모리에 올린다는 것은 Class 객체를 생성한다는 것을 의미한다.그리고 Class객체는 우리가 아는 일반 객체(인스턴스)와 다르다.
Static으로 선언된 멤버들은 컴파일 시에 메모리에 올라가 Class객체에 저장되어있다.
이로인해 (인스턴스)클래스명.멤버명으로 종속 클래스를 인스턴스화하지 않아도 Static 멤버를 외부에서 바로 사용이 가능하다. 

 

하지만 우리가 일반적으로 클래스를 사용하기 위해 메모리에 올려야한다고 말하는 것은, 힙메모리에 올리는 것(객체화,인스턴스화)을 의미한다.다시말해 정적클래스를 사용하기 위해서는 정적클래스를 인스턴스화해야한다. Static을 인스턴스화한다니 어색하게 들릴 수 있지만, 정적 클래스는 내부클래스로만 존재하기 때문에 지금까지 봐왔던 경우와는 전혀 다르다는 걸 생각해야 한다.

 

나는 인스턴스 클래스 안에 선언된 정적 멤버의 사용과, 정적 클래스 안에 선언된 인스턴스 멤버의 사용을 혼동하고 있었다.

 

"클래스로더가 메모리(메소드 영역)에 올린다"와

"정적멤버에 접근하기 위해 해당 클래스를 인스턴스화하지 않아도 된다(힙메모리에 올리지 않는다)"는 함께 성립될 수 있다는 걸 이제야 이해했다.

 

 

아래는 static을 이해하는 데 도움이 되었던 글이다.

 

더보기

때에 따라서 모든 인스턴스가 같은 값을 공유하게 하고 싶을 때가 있다. 이런 경우 해당 변수를 클래스의 멤버로 만들면 된다. 인스턴스 변수와 마찬가지로 class 내부에 위치 하지만, static 키워드를 멤버 앞에 붙이면 클래스의 멤버가 된다. 즉, 해당 클래스 소속의 변수가 된다. 물론, 메소드도 마찬가지이다. 

 

 static이 실행되는 시점은 클래스가 메모리상에 올라갈 때이다. 즉, 우리가 프로그램을 실행하면 필요한 클래스가 jvm 메모리상에 로딩되는 과정을 거친다. 그리고 한번 로딩된 클래스는 특별한 일이 발생하지 않는 이상 메모리상에서 객체를 생성할 수 있도록 메모리에 상주한다. static은 이 시점에 메모리에 올라가면서 필요한 동작을 처리한다. 결론적으로 static은 객체의 생성과는 관계없이 클래스가 로딩되는 시점에 단 한번만 필요한 동작을 처리하기 위해 사용한다. 이 때, jvm 의 메소드 영역(클래스 영역)에 클래스의 정보들이 올라가게 된다.

출처: https://ict-nroo.tistory.com/19 [개발자의 기록습관]

 

더보기

[Java의 비밀] .class는 어떻게 하여 실행되는 것일까?

 

Java 프로그램을 작성하여 컴파일을 하면 항상 .class라는 파일이 생성된다. 먼저 .class 파일은 어떻게 메모리에 올라갈까? 많은 사람들의 생각과 달리 .class 파일은 메모리에 바로 올라갈 수 있는 파일이 아니다. 다시 말하면 이것은 Java byte 코드이지, C 언어에서 컴파일하고 난 후의 결과물과는 다르다는 것이다. 따라서, .class라는 Java byte 코드를 메모리에 올리는 역할을 하는 것이 필요한데 이런 역할을 하는 것이 ClassLoader이다.

ClassLoader는 어떻게 .class 파일을 메모리에 올리는지 알아 보도록 하자. .class를 ClassLoader가 메모리에 올라가는 Class 객체로 만드는 것이다. 여기서 주의할 점은 Class 객체는 일반객체가 아님을 알아 두어야 한다.

아래 코드를 한번 살펴보자.

class Test {

    static String str=”a”;

    String s = “b”;

 

위의 Test 클래스를 다음과 같이 사용할 수 있을 것이다.

Test a = new Test();

위에서 new Test()가 바로 일반 객체가 된다. 즉, 메모리 내에서 Class 객체를 매개로 하여 일반 객체를 생성하게 되는 것이다. 우리가 일반적으로 알고 있는 static 변수는 어디에 있는 것일까?

바로 Class 객체에 존재한다. 그래서 우리가 static으로 선언하면 Global 변수로 사용할 수 있는 것이다. 즉, 일반 객체는 Garbage Collection의 대상이 되지만 Class 객체는 Garbage Collection의 대상이 되지 않기 때문에 그러하다.

m.blog.naver.com/PostView.nhn?blogId=asura71&logNo=30021992124&proxyReferer=https:%2F%2Fwww.google.com%2F