티스토리 뷰
jvm에 대해 검색하면 자주 보이는 부분이 heap메모리 영역과 runtime data area 에 대한 글들을 많이 볼 수 있다.
나는 우리가 작성하는 소스코드가 메모리에 어떻게 올라가고 어떻게 동작하는지 궁금하여 JVM에 관한 책들을 읽게 되었다.
간략하게 말하자면 우리가 작성한 자바 소스코드는 먼저 자바컴파일러에 의해 컴파일 되어 JVM이 실행할수있는 class 파일로 변환되고
클래스 로더가 JVM으로 클래스를 로드 한 후 로딩, 링킹 과정을 거쳐 runtime data area에 배치한다.
JVM에서 Class파일의 실행은 Class Loader를 통해 로딩되면서 시작되기 때문에, Class Loader에 대한 이야기를 먼저 정리해보려고 한다.
🔸Class Loader
Java의 특성 중 하나는 Runtime시에 동적으로 Class를 읽어온다는 것이다.
모든 Class는 참조되는 순간 동적으로 Load 및 Link가 이루어진다. 이러한 방식을 Dynamic Loading 이라고 한다.
JVM에서 Dynamic Loading을 담당하는 주체는 바로 Class Loader이다.
즉, Class Loader란 JVM내로 Class를 Load하고 Link를 통해 적절히 배치하는 일련의 작업을 수행하는 모듈이라고 정의할 수 있다.
💡Dynamic Loading
Load가 어느 시점에 수행되느냐에 따라 Load Time Dynamic Loading 과 Runtime Dynamic Loading으로 구분된다.
- Load Time Dynamic Loading
- 하나의 Class를 Loading하는 과정에서 이에 관련된 Class들을 한꺼번에 Loading 하는 방식
- Runtime Dynamic Loading
- 소스 코드에서 객체를 참조하는 순간에 동적으로 Loading 하는 방식(Reflection이라는 기술의 기본이 되어 Spring과 같은 Framework에도 널리 사용되고 있다.)
아까 Class Loader란 JVM내로 Class를 Load하고 Link를 통해 적절히 배치하는 일련의 작업을 수행 한다고 하였는데, JVM은 동일한 Class를 중복해서 Loading 하지 않는다. 그렇기 때문에 해당 Class가 JVM에 이미 Load 되어 있는지 확인하는 과정이 필요하게 되는데, 이 과정에서 Class를 구별하는 방법은 Class의 이름이다.
Class의 이름이라고 하면 보통 Full Qualified Name(Package Name과 Class Name을 점(.)으로 연결한 이름)을 의미한다.
그러나 Class Loader가 Class를 인식하는 것은 여기에 Class를 Load한 Class Loader의 이름까지 포함한다. Loader Name + Package Name + Class Name 까지 동일해야 같은 Class로 인식한다.
Class Loader가 Class를 Load할 때 JVM에 있는 모든 Class를 찾아다니며 이름을 확인하는 것이 아니라 Namespace라는 것을 사용한다.
💡Namespace
Class Loader는 Namespace를 각각 하나씩 가지고 있어 자신이 Load한 Class의 Full Qualified Name을 저장해 놓는다. 자신의 Namespace에 이름이 없다면, 같은 Class라도 Class Loader만 다르다면 중복해서 Load가 가능하다는 것을 의미하기도 한다.
🤔 그럼 언제 Class Loader가 자신의 Namespace를 검색할까?
바로 Symbolic Reference 를 수행할 때 이다. 객체의 이름으로 참조를 할 때마다 Class Loader는 해당 Class를 Load 해야 할 지의 여부를 결정하기 위해 Namespace를 참조하게 된다. 그런데 Java는 자신이 참조하는 Class를 Load할 때 반드시 참조하는 Class와 참조되는 Class가 동일한 Class Loader를 사용해야만 한다는 규칙이 있다.
💡Symbolic Reference
자바에서 심볼릭 레퍼런스(symbolic reference)는 클래스 이름이나 인터페이스 이름으로 클래스나 인터페이스를 참조하는 방식을 말한다. 기본 자료형(primitive data type)을 제외한 모든 타입은 심볼릭 레퍼런스를 통해 참조된다.
- 심볼릭 레퍼런스 예시 ( String 클래스를 심볼릭 레퍼런스로 참조하는 예 )
String str = "Hello, world!";
이 코드에서 str은 String 클래스의 인스턴스를 참조한다. 그러나 str은 String 클래스의 주소를 직접 참조하는 것이 아니라, String 클래스의 이름인 "java.lang.String"으로만 참조한다.
실행 시점에 JVM은 "java.lang.String"을 통해 String 클래스를 찾고, 그 주소를 str에 연결한다.
다른 예시, 다음 코드는 Runnable 인터페이스를 심볼릭 레퍼런스로 참조하는 예이다.
Runnable runnable = () -> System.out.println("Hello, world!");
이 코드에서 runnable은 Runnable 인터페이스의 구현체를 참조한다. 하지만 runnable은 Runnable 인터페이스의 주소를 직접 참조하는 것이 아니라, Runnable 인터페이스의 이름인 "java.lang.Runnable"으로만 참조한다.
실행 시점에 JVM은 "java.lang.Runnable"을 통해 Runnable 인터페이스를 찾고, 그 주소를 runnable에 연결한다.
심볼릭 레퍼런스는 다음과 같은 경우에 사용된다.
- 클래스나 인터페이스를 미리 정의하지 않고 사용해야 하는 경우
- 클래스나 인터페이스를 동적으로 로드해야 하는 경우
- 클래스나 인터페이스를 반환해야 하는 경우
심볼릭 레퍼런스를 사용하면 코드의 유연성이 향상되고, 런타임 시점에 클래스나 인터페이스를 동적으로 선택할 수 있다.
💡 메소드 호출은 런타임에 실제로 해결되지만, Symbolic Reference는 컴파일 시간에 사용된다.
Class exHello {
Class c1 = Class.forName(exem.package.jvmclass);
}
Class Loader1, Class Loader2가 있을때, Class Loader2가 exHello를 Load하고 나서 exem.package.jvmclass 를 참조하기 위해 Class Loader1가 아닌 Class Loader2가 Load를 한다는 말 이다.
💡 참조관계가 있는 Class들은 같은 Class Loader를 사용해야 한다 는 원칙이 있기 때문이다.
이 때 Class Loader가 단 하나만 사용되거나 수많은 Class Loader들이 제각각 Load 하는 상황이 발생하지 않도록 Class Loader Delegation Model 의 방법이 사용된다.
Class Loader Delegation Model
: Load의 요청을 위계적으로 전달하는 방식을 의미한다.
JVM내에는 여러 개의 Class Loader가 있으며 Class Loader는 위계 구조, 계층 구조를 가지고 있다. Class Loader Delegation Model은 이러한 위계 구조를 바탕으로 서로에게 임무를 **위임(Delegation)**하는 일련의 방법을 말한다.
Class Loader는 Bootstrap Class Loader / Extension Class Loader / Application Class Loader 등으로 구별 되고 있다.
Bootstrap Class Loader
- 부모를 가지지 않는 가장 상위의 Class Loader 이다.
- JVM 기동 시에 가장 먼저 생성, $JAVA_HOME/jre/lib/rt.jar 를 Load 하는 작업을 수행한다. 그 이후 Object Class를 포함한 Java API들을 Load 하게 된다.
- Native Code로 구현되어 있다.
⇒ JAVA 를 수행하기 위한 Runtime 환경을 구성하는 기초 단계에 포함 된다.
Extention Class Loader
- BootStrap Class Loader 를 부모로 하고 기본 Java API를 제외한 $JAVA_HOME/jre/lib/ext에 위치한 확장 Class들을 Load하는 작업을 수행한다.
🔸Bootstrap Class Loader 와 Extension Class Loader는 JVM을 위한 Class Loader 라고 할 수 있다.
Application Class Loader(= System Class Loader)
- System Class Loader라는 명칭으로 부르기도 한다.
- System Class Loader와 User Defined Class Loader를 합쳐서 부르기도 한다.
- JVM이 아니라 Application User를 위한 Class Loader이다. $CLASSPATH 또는 java.class.path에 위치한 Class 들을 Load 한다.
User Defined Class Loader
Application에서 직접 생성이 가능하다. 보통 WAS나 Framework에서는 User Defined Class Loader를 생성해서 사용하는 경우가 많다. User Defined Class Loader는 System Class Loader의 하위에 있으며 User Defined Class Loader 들 끼리도 계층을 가지게 된다.
Class Loader Delegation Model은 Load의 요청을 위계적으로 전달하는 방식을 의미하는데 이 요청은 위로 위임을 하게 된다.
Class를 탐색하는 것도 이와 유사하다. 일단 Namespace를 검색하여 이미 Load된 Class 라면 이를 반환하고 Load 되어 있지 않으면 Parent 에게 위임을 하게 된다. 만약 Parent Class Load가 이를 완료하지 못하면 자신이 Class를 Load한다.
Class의 Load 과정 정리
- Class Loader가 Load 요청을 받으면 내부적으로 loadClass()라는 Method가 수행된다. loadClass() : Class가 이미 Load되어있으면 반환, 아니면 Parent Class Loader에게 위임
- Parent Class Loader 도 이 Class를 Load 하지 않았다면 findClass() 를 호출한다. findClass() : 요청받은 Class Loader가 직접 파일 시스템을 탐색하여 Class를 Load 한다.
예제
System Class Loader 에게 특정 Class를 Load 하도록 하는 작업 요청했다고 가정.
이 특정 Class는 파일시스템 어딘가에 존재할 것이고 그 디렉토리는 $CLASSPATH 로 지정한 것.
1) 이 경우 System Class Loader는 자신의 Namespace를 찾아보고 Load 한 적이 없으면 자신이 Load를 시도하는 것이 아니라 Extension Class Loader에게 요청을 위임하게 된다.
2) 이 Extension Class Loader도 역시 자신의 Namespace 를 검색하여 Class를 찾지 못할 경우 다시 Load 작업은 Bootstrap Class Loader로 넘어가서 같은 작업을 반복한다.
3) 여기서도 실패하게 되면 $CLASSPATH 를 직접 찾아보고 해당 Class를 Load 하게 되는 것이다.
🔹JVM에서 Default 로 사용하는 Class Loader는 System Class Loader 이며 User Defined Class Loader를 생성할 때 별도의 Parent를 지정하지 않게 되면 System Class Loader가 부모가 된다.
🔹 보통 WAS의 경우에는 User Defined Class Loader를 만들어 이를 주로 사용하게 된다. $WAS_HOME과 같은 특정 디렉토리를 설정하여 검색하도록 되어있다.
💡 Class Loader Delegation Model의 또 한가지 원칙은 Class Loader 자신과 자신의 Parent Class Loader의 정보는 열람이 가능하지만 자신보다 하위에 있거나, 부모가 같더라도 형제처럼 갈라진 Class Loader에게는 위임도 불가능하고 Namespace의 정보를 제공받지 못한다는 것이다.
⇒ 자기 역할이 아니면 완료하지 못하니 상위로 위임. 각자 자기가 맡은 것만 로딩한다.
클래스는 부모부터 로딩. 상속관계이면 하위는 볼 수 없다. 그러면 밑에서 처리한다.
클래스 로더에게 역할을 분배. 그리고 상위 클래스로더는 하위 클래스 로더를 보지 못하니
하위에서 상위에게 일을 위임하고 상위에서 하지 못하는 것은 제일 하위가 다 하는 것이다.
🔸 상속관계의 특징은 자식은 부모를 볼 수 있다. 부모는 자식을 볼 수 없다.
생성자도 항상 부모 생성자부터 처리하고 자식 생성자 처리.
이렇게 하는 것은 부모 클래스를 로딩하고 난 후에 자식 클래스를 로딩해야 정상적인 관계가 만들어진다는 뜻 이다.
위에서부터 다 메모리에 로딩해야 자기가 상위를 보면서 처리한다는 표현이다.
🤨 이러한 특성들을 바탕으로 WAS벤더들은 나름의 Class Loader Tree를 구성하여 사용한다. WAS 라는 것은 J2EE 표준을 따른 구현체 이다. J2EE 안에는 JNDI, JMS등등의 여러 서버들이 구성되어 있기는 하지만, 가장 중요한 것은 Web Container와 EJB Container 라고 생각된다.
'ALL' 카테고리의 다른 글
How to backup a postgres database using java / 자바로 postgres DB 백업하기(pg_dump) (0) | 2024.05.14 |
---|---|
깃 소스트리 fatal: bad config line 1 in file C:/Users/.... (0) | 2023.10.17 |
[Conda-Jupyter] 맥 Conda 가상환경 과 Jupyter Kernel (2) | 2023.10.04 |
Null, NPE 방어에 대해서 (0) | 2023.07.24 |
[Linux] 리눅스 서버 설치, Linux 파일시스템 RIAD-1, RAID-5 (0) | 2023.07.04 |
- Total
- Today
- Yesterday
- 인덱스
- 이정환
- 컨테이너
- 스프링 프로젝트
- 자바의정석
- 스프링의정석
- 데이터베이스
- 남궁성
- spring
- Spark
- Node.js
- 리액트
- EC2
- di
- 스프링
- 코드로 배우는 스프링 웹 프로젝트
- 한입크기로 잘라먹는 리액트
- node
- 객체지향
- React
- 친절한SQL튜닝
- 스프링 빈
- AWS
- JavaScript
- 시큐리티
- 데브캠프
- 자바스크립트
- @Configuration
- MySQL
- security
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |