자바

[Java] 클래스 선언만 하면 메모리에 할당되지 않는다던데...(p.s. JVM 구조)

AnnSEo2105 2021. 10. 5. 15:28

단순 클래스 선언하면 heap 메모리에 할당되지 않고 reference만 생성된다. 

이 생성된 reference는 힙 메모리에 생성될까??

 

답은 context에 따라 다르다라고 한다.

만약 선언이 오브젝트의 한 부분이면 reference를 위한 공간이 필드를 포함한 나머지 오브젝트를 위한 공간과 함께 heap에 할당이 된다.

그렇지 않다면 그냥 reference가 JVM의 stack frame에 할당된다고 한다.

 

 

출처

https://stackoverflow.com/questions/32810120/jvm-allocates-memory-at-declaration-for-primitive-types-but-not-for-non-primiti

 

JVM allocates memory at declaration for primitive types, but not for non-primitive types?

I am from a C/C++ background and very new to java, I am having difficulty in understanding variable declaration and memory allocation in java. when we write, myclass myobject; we declare that my...

stackoverflow.com

 

그러다 JVM의 구조에 대해서 알아야 겠다 싶었다.

사전 지식은 그냥 JVM이 있어 어떤 운영체제든 알아서 실행되게 해준다는 것과 가비지 컬렉터로 메모리 관리를 잘 해준다 정도인데. 당시 설명을 들을 때, 자바가 나오기 전에는 운영체제에 맞게 프로그램이 작성되어야 했던 것을 자바가 나오면서 이 귀찮음(?)을 혁신적으로 없애줬다 라는 이야기가 생각난다. 메모리 관리도 원래 개발자가 관리했었다 정도.

 


JVM을 기술적으로 정의하면 코드를 실행하고 그 코드의 런타임 환경을 제공하는 소프트웨어 프로그램에 대한 사양(Specification)이라고 하고 일반적인 정의로는 자바 프로그램을 실행하며 프로그램 리소스를 관리한다고 한다.

 

JVM의 3가지 측면

1. JVM Specification(사양): 자바 프로그램을 정확하게 실행

2. JVM Implementation(구현): 실제 소프트웨어 프로그램을 도출

3. JVM Instance

스펙이 구현돼서 소프트웨어 제품으로 릴리즈되면, 개발자는 그걸 하나의 프로그램처럼 다운받아 실행할 수 있고 이렇게 다운로드 된 프로그램이 하나의 JVM 인스턴스다. 

 

 

JVM에서 클래스 파일 로드와 실행

- class loader

컴파일된 .class 파일을 context에 맞게 액세스 가능한 서버 같은 것으로 load해야 하는데 이 기능을 class loader가 수행한다. class loader는 class를 메모리에 로드하고 실행 전 준비(linking, initialization)를 해주는 역할을 한다. JVM의 일부로 효율적인 수행을 위해 lazy loading, caching 기법 등이 활용된다. 

 

- execution engine

class loader가 class 로딩 작업을 끝내면 JVM이 이 코드를 실행하는데 이 기능을 execution engine이 수행.

파일, 네트워크, 메모리 리소스를 요구하는 프로그램 실행과 운영체제 가운데 위치해 이런 리소스들을 공급한다.

 

 

 

자바 프로그래밍 언어로 작성한 코드인 .java 파일이 자바 컴파일러를 통해 .class 파일로 변하게 된다.

이 클래스 파일을 클래스 로더가 읽어들인다. 

 

class loader에는 3가지 활동이 있다.

  • Loading
  • Linking
  • Initialization

 

Loading

클래스 로더가 읽어들이면 그에 맞게 binary 데이터가 생성되고 그게 method area에 저장된다.

 

method area에 저장되는 것들

  • The fully qualified name of the loaded class and its immediate parent class.
  • Whether the “.class” file is related to Class or Interface or Enum.
  • Modifier, Variables and Method information etc.

 

클래스 파일이 로딩된 후 JVM은 heap 메모리에 이 파일을 대표하는 클래스 타입 오브젝트를 만든다. 여기서 오브젝트는 java.lang package에 미리 선언된 클래스 타입을 의미한다.

 

 

Linking

verification, preparation, and (선택적으로) resolution을 수행한다.

 

verification: 제대로 컴파일이 됬는지 등을 확인해 .class 파일이 정확한지 확인. ByteCodeVerifier가 실행한다.

preparation: 클래스 변수를 메모리에 할당하고 초기값으로 초기화

resolution: 심볼릭 reference를 직접적인 reference로 대체. 참조된 entity를 찾기 위해 method area를 검색함.

 

Initialization

모든 static 변수와 static block에 정의된 값들이 할당된다. 하향식으로 진행.

 

3가지 class loader가 있는데

  • Bootstrap class loader: 모든 JVM 구현에 이 클래스 로더가 필요한데 이걸로 “JAVA_HOME/jre/lib” 에 있는 core java API 클래스들을 로드한다. 이 경로는 bootstrap path로 유명하다. 
  • Extension class loader: bootstrap class loader의 자식격(?)이며 확장 디렉터리에 있는 것들을 로드하거나 java.ext.dirs 시스템 프로퍼티로 지정된 클래스들을 로드한다. sun.misc.Launcher$ExtClassLoader class로 구현됌.
  • System/Application class loader: extension class loader의 자식격(?)이며 application classpath로부터의 클래스들을 로드한다. java.class.path와 맵핑된 환경 변수를 사용한다. sun.misc.Launcher$AppClassLoader class로 구현됌.

 

 

대충 사진을 보거나 위의 자식격이라는 표현으로 알 수 있듯이 상속 구조가 있다.

 

JVM은 delegation hierarchy 원칙에 따라 클래스를 로딩한다. System class loader는 로딩 요청을 extension class loader에게 위임하고(delegate) extenstion class loader는 bootstrap classloader에게 요청을 위임한다. 만약 boot-strap 경로에서 클래스를 발견하면 클래스는 로딩되지만 요청이 다시 extension class laoder로 보내지고 그게 다시 system class loader로 간다. 여기서 클래스 파일 로딩이 실패하면 뜨는게 run-time exception으로 java.lan.ClassNotFoundException이다. 

 

 

JVM Memory

  1. Method area: 클래스 이름 같은 모든 클래스 수준의 정보, 그 클래스의 부모 클래스 이름(바로 위), 메서드, 변수, static 변수 등이 저장된다. JVM당 method area는 하나만 있고 거기서 리소스를 공유한다.
  2. Heap area: 모든 오브젝트들의 정보가 heap area에 저장되고 이것도 JVM당 한 개고 리소스를 공유함.
  3. Stack area: JVM은 모든 쓰레드 당 1개 씩의 런타임 스택을 만들어 Stack area에 저장한다. 이 스택의 모든 블록은 activation record/stack frame이라고 부르고 여기 메서드 호출(method calls)을 위한 것들이 저장된다. 메서드의 모든 로컬 변수들은 각자에 맞는 프레임에 저장된다. 쓰레드가 종료되고 그 런타임 스택이 더이상 공유되지 않는 리소스라면 JVM이 없애버린다.
  4. PC Registers: 쓰레드의 현재 실행되는 것들의 주소가 저장된다. 각 쓰레드는 별도의 PC 레지스터를 가진다.
  5. Native method stacks: 모든 쓰레드에 대해 별도로 native stack이 만들어지고 여기 native method가 저장된다.

 

 

 

Execution Engine

.class 로 된 바이트코드를 한줄씩 읽어 다양한 메모리에 있는 데이터를 사용한다. 3가지 파트로 분류된다.

  • Interpreter: 한줄씩 읽는 역할. 단점은 한 메서드를 여러 번 호출할 때 매번 interpret 한다는 점이 있다.
  • Just-In-Time Compiler(JIT) : 인터프리터 효율성을 높이기 위해 사용된다. 전체 바이트코드를 컴파일해서 native code로 바꿔서 인터프리터가 메서드 호출을 반복할 때마다 JIT가 direct native code를 제공해서 똑같은 걸 여러 번 해석 안되게끔 해준다. 
  • Garbage Collector: 참조되지 않는 오브젝트 삭제!

 

 

Java Native Interface (JNI) : 

Native Method 라이브러리와 연동해서 실행에 필요한 native libraries(C, C++)를 제공해주는 인터페이스. 

JVM이 C/C++ 라이브러리를 호출하고 하드웨어에 특정한 C/C++ 라이브러리에 의해 호출될 수 있게 해준다.

 

Native Method Libraries : 

실행 엔진에 필요한 native libraries(C, C++)의 collection

 

 

 

출처

https://www.geeksforgeeks.org/jvm-works-jvm-architecture/

 

How JVM Works - JVM Architecture? - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

 

 

 

아 JVM까지 가긴 했는데 당장 내용이 머리에 들어오진 않고 이러이러 하다~ 정도로만 이해했다.

구조가 생각보다 복잡하구나.

이거 해석하면서 한글자씩은 알겠는데 글자를 합치니 이해하기 어려운 현상을 발견했다.그림 좀 그려봐야지