백기선 개발자님의 더 자바, "코드를 조작하는 다양한 방법"을 스터디 해보려고 합니다.
첫번째는 JVM을 이해하기 입니다.
1. 자바, JVM, JDK, JRE
Java를 실행하기 위해선 JVM(Java Virtual Machine) 자바 가상 머신이 필요합니다.
JVM은 바이트코드를 인터프리터 및 JIT 컴파일러로 OS에 따라 특화된 코드로 변환하여 실행하게 됩니다.
인터프리터 및 JIT 컴파일러는 추후 설명하도록 하겠습니다.
JVM을 통해 바이트코드를 읽어 변환 하기 때문에 꼭 자바 언어가 아니더라도 바이트코드를 사용하는 언어는 모두 변환이 가능합니다
EX) Kotlin, Scala, JRuby, ... 등등
여기서 OS의 따라 내부 해석 방식이 달라져서 JVM은 특정 플랫폼(OS)에 종속적입니다.
JVM은 오라클 뿐만 아니라 아마존 등등 여러 회사에서 만들어 냅니다.
예를 들어 설명하자면 그래픽 카드가 가장 좋은 예입니다.
NVIDIA가 표준을 만들면 하위 이엠텍, 갤럭시, ... 이 표준을 가지고 만들어 내듯이
오라클이 표준 JVM을 만들면 하위 아마존, AZUL, ... 이 표준을 기준삼아 JVM을 만들어냅니다.
이러한 이유때문에 Java가 오라클에서 유료화가 된다고 해도 다른 곳에서 무료로 배포하기 때문에 유료화의 의미가 퇴색됩니다.
그렇다면 JVM만 있으면 JAVA를 실행할 수 있는가?
JAVA 파일은 실행 할 수 있지만 이걸로 개발하기엔 어렵습니다.
우리가 자바 어플리케이션을 실행하기 위해서 JRE(Java Runtime Environment)가 필요한데 이것은 JVM + 라이브러리가 추가된 배포판입니다.
자바 런타임 환경에서 사용하는 프로퍼티 세팅이나 파일을 가지고 있습니다.
하지만 JRE에서 개발 관련 도구 예를 들어서 javadoc, javap 등 개발 관련 도구를 제공하지는 않습니다.
그래서 개발을 할 때에는 JDK(Java Development Kit)이 필요합니다.
이것은 JRE + 개발 툴이며 개발할 때 필요한 것들이 들어가있습니다.
개발 할 때 사용하는 자바 언어는 플랫폼에 독립적이고 자바 11부터 JRE를 따로 제공하지 않고 JDK만 제공합니다.
2. JVM 구조
JVM 크게 클래스 로더, 메모, 실행엔진, 네이티브로 이루어져 있습니다.
클래스 로더
- .class 파일에서 바이트 코드를 읽고 메모리에 저장합니다.
- 로딩 : 클래스 읽기
- 링크 : 래퍼런스 연결
- 초기화 : static 값들 초기화 및 변수에 할당 -> 개발 할 때 static 값들이 이 때 초기화 됩니다.
메모리
- 메소드 : 메소드 영역에는 클래스 수준의 정보(클래스 이르 , 부모 클래스 이름, 메소드, 변수)를 저장. 다른 곳에서 공유, 참조 가능한
자원입니다. - 힙 : 힙 영역에서는 객체를 저장. 다른 곳에서 공유, 참조 가능한 자원입니다.
- 스택 : 쓰레드 마다 런타임 스택을 만들고 그 안에 메소들 호출을 스택프레임이라 부르는 블럭을 쌓습니다.
쓰레드를 종료하면 런타임 스택도 종료됩니다. - PC(Program COunter) 레지스터 : 쓰레드 마다 쓰레드 내 현재 실행할 스택 프레임을 가리키는 포인터가 생성됩니다.
스택과 PC레지스터는 쓰레드에 국한되어 있으며 메소드를 호출하여 스택프레임을 쌓고 PC레지스터가 거기에 주소값을 가리키는 포인터를 생성하는 것입니다.
실행 엔진
- 인터프리터 : 바이트코드를 한줄 씩 실행
- JIT 컴파일러 : 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러로 반복 되는 코드를 모두 네이티브 코드로 변경해둡니다.
- 그 이후에는 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용합니다.
Java가 빨라진 이유입니다. 인터프리터가 한줄 씩 읽던걸 JIT이 그것을 생략해 버리니까 빨리진거죠 - GC(Garbage Collector): 더이상 참조되지 않는 객체를 모아서 정리합니다.
GC는 많이 사용된다고 합니다 나중에 GC에 대해서 한번 포스팅해보도록 하겠습니다.
JNI, 네이티브 메소드 라이브러리
JNI(Java Native Interface), 네이티브 메소드 라이브러리는
자바에서 C, C++등 어셈블리로 작성된 코드를 사용하려고 할 때 이용한다고 합니다 JNI -> 함수 , 네이티브 메소드 라이브러리 -> 라이브러리.
직접 코드를 작성하는 일은 많이 없지만 이것을 이용하는 경우가 많다고 합니다.
3. 클래스 로더
- 로딩, 링크 초기화 순으로 진행
- 로딩
- 클래스 로더가 클래스 파일을 읽고 바이너리 데이터를 만들고 메소드 영역에 저장합니다(힙 영역).
- 메소드 영역에 저장 되는 데이터는FCQN , 클래스 , 인터페이스, 이늄, 메소드와 변수입니다.
- 링크
- 링크는 총 세단계로 나뉘어져 있는데 Verify, Prepare, Reolve(Optional) 세 단계로 나뉘어져 있습니다.
- Verify : 클래스 파일 형식이 유효한지 체크합니다.
- Prepare : 클래스 변수와 기본값에 필요한 메모리를 준비하는 과정입니다.
- Resolve : 심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체합니다. (이 과정은 optional)
- 초기화
- Static 변수의 값을 할당한다. (static 블럭이 있다면 이때 실행된다.)
- 종류
- 클래스 로더는 계층 구조로 이뤄져 있으면 기본적으로 세가지 클래스 로더가 제공된다
- 부트 스트랩 클래스 로더 : JAVA_HOME\lib에 있는 코어 자바 API를 제공한다.
- 플랫폼 클래스로더 : JAVA_HOME\lib\ext 폴더 또는 java.ext.dirs 시스템 변수에 해당하는 위치에 있는 클래스를 읽는다.
- 애플리케이션 클래스로더 : 애플리케이션 클래스패스(애플리케이션 실행할 때 주는 -classpath 옵션 또는 java.class.path 환경 변수의 값에 해당하는 위치)에서 클래스를 읽는다.
public class App {
public static void main(String[] args) {
//클래스 로더는 계층 구조를 가지고 있다
System.out.println(App.class.getClassLoader()); //클래스 로더
System.out.println(App.class.getClassLoader().getParent()); //클래스 로더의 부모
System.out.println(App.class.getClassLoader().getParent().getParent()); //클래스 로더의 부모의 부모
}
}
해당 사진 처럼 애플리케이션 클래스 로더 상위에 플랫폼로더가 있고 그 부모는 안찍히는데 그 이유는 네이티브 코드로 이루어져있기 때문에 찍히지 않는다.
클래스 로더에서 로딩을 할 때 먼저 부트 스트랩 로더가 실행 되고 클래스를 로딩하지 못하면 플랫폼 클래스로더 그 다음엔 애플리케이션 로더가 실행되는데
이 때 애플리케이션 로더에서도 찾지 못하게 되면 ClassNotFoundException이 발생하게 된다.
출처
백기선 더 자바, 코드를 조작하는 다양한 방법
https://www.inflearn.com/course/the-java-code-manipulation/dashboard
더 자바, 코드를 조작하는 다양한 방법 - 인프런 | 강의
여러분이 사용하고 있는 많은 자바 라이브러리와 프레임워크가 "어떻게" 이런 기능을 제공할 지 궁금한적 있으신가요? 이번 강좌를 통해 자바가 제공하는 다양한 코드 또는 객체를 조작하는 방
www.inflearn.com
https://sas-study.tistory.com/262
JVM 메모리 구조 및 아키텍쳐(클래스 로더, 실행엔진 등)
모든 자바 개발자들은 JRE(Java Runtime Environment)에 의해 자바 바이트코드가 실행된다고 알고있습니다. 하지만 많은 개발자은 JRE가 JVM(Java Virtual Machine)의 구현이라는 것은 알지 못하는 것 같습니다.
sas-study.tistory.com
'Java > 더 자바, "코드를 조작하는 다양한 방법"' 카테고리의 다른 글
애노테이션 프로세서 Annotation Processor (0) | 2023.02.01 |
---|---|
다이나믹 프록시, Dynamic Proxy (0) | 2023.01.19 |
프록시 Proxy 프록시 패턴 (0) | 2023.01.15 |
리플렉션 Reflection (0) | 2023.01.11 |