일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- Rxjava Observable
- ANR이란
- 멤버변수
- 2022 플러터 설치
- 큐 자바 코드
- 안드로이드 유닛 테스트 예시
- ar vr 차이
- rxjava hot observable
- 객체
- 스택 큐 차이
- 플러터 설치 2022
- jvm 작동 원리
- rxjava cold observable
- 클래스
- 안드로이드 유닛테스트란
- 안드로이드 유닛 테스트
- android retrofit login
- 서비스 vs 쓰레드
- 안드로이드 os 구조
- android ar 개발
- 서비스 쓰레드 차이
- 안드로이드 레트로핏 사용법
- 안드로이드 라이선스
- 안드로이드 레트로핏 crud
- 안드로이드 라이선스 종류
- 2022 플러터 안드로이드 스튜디오
- 자바 다형성
- rxjava disposable
- 스택 자바 코드
- jvm이란
- Today
- Total
나만을 위한 블로그
[JAVA] JVM의 작동원리 본문
자바라는 프로그래밍 언어는 어떤 플랫폼에서든 소스코드 변경이 없어도 실행할 수 있다.
즉 어떤 CPU나 OS에서든 실행할 수 있단 뜻이다. 그래서인지는 몰라도 웹 어플리케이션 개발에도 사용되고, 안드로이드에서도 코틀린을 밀어주기 이전엔 자바로 기능들을 구현했다.
그런데 이게 어떻게 가능한 것일까? 바로 JVM이 있었기 때문이다.
JVM이란?
JVM은 원래 Java Virtual Machine의 약어로, "자바 가상 머신"이라고 번역된다.
자바는 언어 이름인데 가상 머신은 뭐하는 놈일까? 가상 머신의 사전적 정의는 아래와 같다.
특정한 프로그램이 실행될 수 있도록 컴퓨터에 가상 실행 환경을 만들어 주는 소프트웨어가 설치된 컴퓨터
그럼 이렇게 유추할 수 있다. 자바로 만들어진 프로그램을 실행할 수 있도록 필요한 환경을 만들어주는 컴퓨터를 JVM이라고 말하는 건 아닐까?
아래는 한글, 영어 위키백과에서 JVM으로 검색했을 때 나오는 문서다.
https://ko.wikipedia.org/wiki/%EC%9E%90%EB%B0%94_%EA%B0%80%EC%83%81_%EB%A8%B8%EC%8B%A0
JVM은 자바 바이트코드를 실행할 수 있는 주체다. 일반적으로 인터프리터나 JIT 컴파일 방식으로 다른 컴퓨터 위에서 바이트코드를 실행할 수 있게 구현되나, jop 자바 프로세서처럼 하드웨어와 소프트웨어를 혼합해 구현하는 경우도 있다. 자바 바이트코드는 플랫폼에 독립적이며 모든 JVM은 JVM 규격에 정의된 대로 자바 바이트코드를 실행한다. 따라서 표준 자바 API까지 같은 동작을 하도록 구현한 상태에선 이론적으로 모든 자바 프로그램은 CPU, OS 종류와 무관하게 동일하게 동작할 것을 보장한다
https://en.wikipedia.org/wiki/Java_virtual_machine
JVM은 컴퓨터에서 자바 프로그램과 자바 바이트코드로 컴파일되는 다른 언어로 작성된 프로그램을 실행할 수 있게 해주는 가상 머신이다. JVM은 JVM 구현에 필요한 사항을 공식적으로 설명하는 규격에 의해 상세하게 설명된다. 규격을 갖추면 서로 다른 구현에 걸쳐 자바 프로그램의 상호운용성을 보장하므로, Java Development Kit(JDK)를 쓰는 프로그램 작성자는 기본 하드웨어 플랫폼의 특이성을 걱정할 필요가 없다.
오라클에서 상업적으로 지원되는 자바 릴리스는 OpenJDK 런타임을 기반으로 한다
환경을 만들어주는 게 아니라 코드를 실행하는 주체였다. 그런데 바이트코드는 뭘까?
https://en.wikipedia.org/wiki/Java_bytecode
자바 바이트코드는 JVM의 명령어 집합이다. 자바 개발자는 자바 바이트코드를 인식하거나 이해할 필요가 없다. 그러나 IBM developerWorks 저널에서 제안한 것처럼 바이트코드와 자바 컴파일러에 의해 생산될 가능성이 높은 바이트코드를 이해하는 것은 어셈블리의 지식이 C, C++ 개발자에게 도움되는 것과 같은 방식으로 자바 개발자에게도 도움이 된다
구글에 바이트코드를 검색해보니 2중 for문을 바이트코드로 바꾸는 것에 대한 사진이 있었다.
JVM에 대해 포스팅하는 글인데 저런 외계어들에 대해 이해하려면 안될 것 같은 느낌이 들어, 바이트코드는 먼 미래에 기회가 된다면 포스팅한다.
본론으로 돌아와서, 자바 바이트코드는 간단하게 말해 JVM의 명령어 집합이다.
그리고 JVM은 이 자바 바이트코드로 짜여진 프로그램을 실행하는 기반이 되는 가상 머신이다. 이 JVM은 어떤 아키텍처로 작동하는 걸까?
JVM의 아키텍처
아래 블로그에서 JVM은 3가지 구성요소로 이뤄지며 사진으로 예시를 들었다.
https://velog.io/@jihoson94/JVM-Java-virtual-Machine
위 이미지는 처음 봤을 때 복잡해보인다. 그래서 좀 더 쉽게 표현한 이미지가 있는지 찾아보니 아래 링크에 있었다.
https://steady-snail.tistory.com/67
결국 위 이미지들에서 공통적으로 말하고자 하는 부분은
- Class Loader Sub System
- Runtime Data Areas
- Execution Engine
대략 이 3가지로 나눠지고 추가로 Native Method Interface / Library가 있다. 이 포스팅에선 위의 3가지만 확인해본다.
자바 컴파일러에 의해 바이트코드가 된 클래스 파일이 가장 먼저 만나는 클래스 로더부터 확인해봤다.
Class Loader?
위키백과에서 말하는 Class Loader는 아래와 같다.
https://en.wikipedia.org/wiki/Java_Classloader
클래스 로더는 자바 클래스를 JVM에 동적으로 로드하는 자바 런타임 환경의 일부다. 일반적으로 클래스는 요청 시에만 로드된다. 자바 런타임 시스템은 클래스 로더에 위임되므로 파일 및 파일 시스템을 알 필요가 없다...(중략)...클래스는 명명된 코드 단위로 생각할 수 있다. 클래스 로더는 라이브러리를 찾고, 컨텐츠를 읽고, 라이브러리에 포함된 클래스를 로드하는 역할을 한다. 이 로딩은 일반적으로 프로그램에서 클래스를 호출할 때까지 발생하지 않기 때문에 "요청 시" 수행된다. 주어진 이름을 가진 클래스는 주어진 클래스 로더에 의해 한 번만 로드될 수 있다. 각 자바 클래스는 클래스 로더에 의해 로드돼야 한다...(중략)...JVM이 시작되면 3개의 클래스 로더가 사용된다
1. Bootstrap class loader : <JAVA_HOME>/jre/lib 디렉토리의 핵심 자바 라이브러리를 로드함
2. Extensions class loader : <JAVA_HOME>/jre/lib/ext 또는 java.ext.dirs 시스템 속성에 지정된 기타 디렉토리에 있는 코드를 로드함
3. System class loader : CLASSPATH 환경 변수에 매핑되는 java.class.path의 코드를 로드함
'클래스를 불러오는 사람'으로 해석되듯, 클래스 로더는 자바 컴파일러에 의해 컴파일된 바이트코드(.class 파일)를 Runtime Data Area로 옮기는 역할을 한다.
이 클래스 로더에서는 주로 3가지 활동이 일어난다.
- Loading : 클래스 로더는 ".class" 파일을 읽고 그 파일의 바이너리 데이터를 생성해 메서드 영역에 저장한다
- Linking : 확인(verification), 준비(preparation), 해결(선택 사항)을 수행한다
- Initialization : 모든 static 변수가 코드 및 (존재할 경우) static 블록에 정의된 값으로 할당된다. 이것은 클래스에서 위에서 아래로, 클래스 계층에서 부모에서 자식으로 실행된다
Linking 부분에서 확인, 준비, 해결의 의미는 아래와 같다.
확인(Verification) : .class 파일의 정확성을 보장한다. 이 파일이 올바른 형식으로 지정되고 유효한 컴파일러에서 생성되었는지 여부를 확인한다. 확인에 실패하면 런타임 예외가 발생한다. 이 활동은 ByteCodeVerifier에 의해 수행되며, 이 활동이 완료되면 클래스 파일을 컴파일할 준비가 끝난 것이다.
준비(Preparation) : 클래스 변수에 메모리를 할당하고 메모리를 기본값으로 초기화하는 것
해결(Resolution) : 유형의 기호 참조(symbolic references)를 직접 참조(direct references)로 바꾸는 과정. 참조된 엔터티를 찾기 위해 메서드 영역을 검색해 수행된다
또한 JVM은 Delegation-Hierarchy 원칙에 따라 클래스를 로드한다. 시스템 클래스 로더는 확장 클래스 로더에 로드 요청을 위임하고, 확장 클래스 로더는 부트스트랩 클래스 로더에 요청을 위임한다.
부트스트랩 경로에서 클래스가 발견되면 그 클래스가 로드되고, 그렇지 않으면 요청이 확장 클래스 로더로 재전송된 다음 시스템 클래스 로더로 전송된다.
마지막으로 시스템 클래스 로더가 클래스를 로드하지 못하면 런타임 예외(java.lang.ClassNotFoundException)이 발생한다.
그리고 클래스가 불러와지는 방식에는 로드타임 동적 로딩(load-time dynamic loading)과 런타임 동적 로딩(runtime dynamic loading) 2가지가 있다.
- 로드타임 동적 로딩 : 하나의 클래스를 로딩하는 과정에서 동적으로 필요한(=이와 관련된) 다른 클래스들을 로딩하는 방식.
- 런타임 동적 로딩 : 객체를 참조하는 순간 동적으로 로딩하는 방식. 객체를 사용한 코드 부분에서 Class.forName()이 실행되기 전까진 메인 메서드가 있는 클래스에서 어떤 클래스를 참조하는지 알 수 없다. 그래서 코드를 실행하는 그 순간에 클래스를 로딩하는 동적인 방식으로 클래스를 로드하는데, 이를 런타임 동적 로딩이라 한다.
이 설명만 보면 애매하니 코드로 확인해보자.
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
자바를 처음 배울 때 실행시켜 보는 아주 친숙한 코드다. 여기선 Main이란 클래스에서 String 객체를 메인 메서드의 파라미터로 쓰고 있고(String[] args), 메인 메서드 안에서는 System 객체를 호출하고 있다.
이 경우 Main 클래스가 클래스 로더에 의해 JVM 안으로 불러와질 때 java.lang.String 클래스와 java.lang.System 클래스가 동시에 로딩된다.
이것이 로드타임 동적 로딩 방식이고, 아래는 런타임 동적 로딩의 예시다.
public class Main {
public static void main(String[] args) {
Class c1 = Class.forName(args[0]);
}
}
Main 클래스에서 어떤 클래스를 불러와야 할지는 인수로 넘어온 이후에나 알 수 있다. 즉, Class.forName(args[0])을 호출하는 순간(=코드를 실행하는 순간), args[0]에 해당하는 클래스를 로딩할 수밖에 없다. 이것이 런타임 동적 로딩이다.
이렇게 로딩을 하고 기타 처리가 끝나면 JVM은 클래스들을 런타임 데이터 영역(=JVM의 메모리)으로 넘긴다.
Runtime Data Areas?
이 곳은 JVM이 프로그램을 실행하기 위해 OS한테 할당받는 메모리 영역이다. 이 영역은 목적에 따라서 5개의 영역으로 나눠지는데 각 영역에 대한 설명은 아래와 같다.
- PC Registers : 쓰레드가 생성될 때마다 생기는 공간. 쓰레드가 어떤 명령을 실행할지에 대한 내용을 기록한다. JVM은 CPU 레지스터로 직접 작동하는 게 아니라 스택 기반(Stacks-base)으로 작동하는데, JVM은 CPU에 직접 지시를 내리지 않고 스택에서 피연산자(Operand)를 뽑아내 별도의 메모리 공간에 저장해 연산한다. 이 때의 메모리 공간이 PC Registers다.
- Method Area : 프로그램 실행 도중 클래스가 사용되면 JVM은 그 클래스 파일을 읽고 분석해 클래스의 인스턴스 변수, 메서드 코드 등을 Method Area에 저장한다. 이 때 클래스 변수도 이 영역에 함께 생성된다. 프로그램이 실행되면 모든 코드가 저장돼 있는 상태가 아니다. new 키워드로 객체가 생성되기 이전엔 텍스트일 뿐이다. 이 영역에 저장되는 정보들은 아래와 같다.
- 타입 정보 : 타입은 클래스와 인터페이스를 통칭하는 것이다
- 타입의 전체 이름(패키지명+클래스명)
- 타입에 속한 하위 클래스들의 전체 이름
- 타입이 클래스 또는 인터페이스인지 여부
- 타입의 제어자(public, abstract, final 등)
- 연관된 인터페이스 이름 리스트
- Runtime Constant Pool : 타입의 모든 상수 정보를 갖고 있음
- 타입, 필드, 메서드로의 모든 심볼릭 참조 정보
- Object의 접근 등 모든 참조를 위한 핵심 요소
- Field Information : 인스턴스 변수
- 필드 타입
- 필드 제어자(public, private, protected, static, final, volatile, transient)
- Method Information : 생성자를 포함한 모든 메서드
- 메서드명, 리턴타입, 파라미터 수와 타입, 제어자 등
- Class Variable : 클래스 변수는 static 키워드로 선언된 변수를 말한다. 기본형이 아닌 이 변수는 참조 변수만 저장되고 실제 인스턴스는 힙 영역에 저장돼 있다.
- 타입 정보 : 타입은 클래스와 인터페이스를 통칭하는 것이다
- Heap : 사용자가 관리하는 인스턴스가 생성되는 공간. 객체를 동적 생성하면(=new 키워드 사용) 인스턴스가 힙 영역의 메모리에 할당돼 사용된다. 프로그램은 시작 시 미리 힙 영역을 많이 할당해 놓으며 여기에 인스턴스와 인스턴스 변수가 저장된다. 참조 변수의 경우 포인터가 저장되며, 가비지 컬렉션의 대상이 되는 영역이다.
- JVM Stacks : 쓰레드 제어를 위해 쓰이는 메모리 영역. 쓰레드가 만들어질 때마다 하나씩 만들어지며 한 쓰레드 당 메서드가 호출되는 순간 메모리를 차지한다. 메서드가 호출되면 메서드와 메서드의 정보들은 스택에 쌓이며, 메서드 호출이 종료될 때 Stack Point에서 제거된다. Method 정보는 해당 메서드의 매개변수, 지역변수, 임시변수, 메서드를 호출한 곳 등을 말하고 메서드가 종료되면 메모리 공간이 사라진다.
이렇게 많은 정보들을 저장하는 곳이 Runtime Data Areas다.
Execution Engine?
이름 그대로 클래스 로더를 통해 JVM 안의 Runtime Data Areas에 놓여진 바이트코드를 실행하는 것이다.
바이트코드를 명령어 단위로 읽어 바이트코드를 JVM이 실행할 수 있도록 컴파일해준다. 이 방식은 인터프리터 방식과 JIT(Just In Time) 방식이 있는데 JVM은 기본적으로 모든 코드를 인터프리터 방식으로 바꿔준다. 그러다 반복되는 코드가 나타나면 JIT 방식으로 명령어를 실행한다.
참고한 사이트)
https://velog.io/@hygoogi/JVM-%EA%B5%AC%EC%A1%B0%EC%99%80-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%AC
https://inspirit941.tistory.com/296
https://www.geeksforgeeks.org/jvm-works-jvm-architecture/
https://www.holaxprogramming.com/2013/07/16/java-jvm-runtime-data-area/
https://joomn11.tistory.com/17
'JAVA' 카테고리의 다른 글
[JAVA] 인텔리제이에서 JUnit 환경설정하기 (0) | 2022.01.31 |
---|---|
[JAVA] enum(열거형)이란? (0) | 2021.12.04 |
[JAVA] 버퍼란? BufferedReader/Writer란? + 예제 (0) | 2021.09.04 |
[JAVA] SOLID 원칙이란? - 2 - (0) | 2021.07.24 |
[JAVA] Stream API란? (0) | 2021.07.20 |