JVM을 시작하기 전에... JVM을 안고 있는 JRE와 JDK에 대해서 알아보기
JRE (Java Runtime environment)
자바로 만들어진 프로그램을 구현하기 위한 환경을 구성해주는 도구. 다이어그램에서와 같이 클래스 라이브러리, 기타 라이브러리, JVM이 여기 들어가있다. Java를 개발할 필요는 없는데, 실행은 시켜줘야 하는 경우에 필요하다.
JDK (Java Development Kit)
JRE의 내용을 고스란히 안고있고 거기에 디버거, 도큐멘테이션, 디셈블러, 컴파일러(javac)가 들어가 있다. JDK는 개발, 컴파일링, 자바프로그램을 실행시킬 때 필요하다.
JVM이란?
자바 이전의 C언어를 비롯한 대부분의 언어로 만들어진 프로그램들은 바로 OS(윈도우나 리눅스 같은 운영체제)에 종속되어 있었다. 프로그램이 실행되기 위해서는 OS가 제어하고 있는 시스템의 리소스의 일부인 메모리(RAM: 주 기억장치)를 제어할 수 있어야 하기 때문이다.
하지만 java는 프로그램이 바로 OS로 접근하는 대신 자바가상머신인 JVM을 만들고 Java Byte Code를 OS가 이해할 수 있도록 해석해주는 역할을 부여한다. 그러면 JVM이 OS에게서 메모리 사용권한을 할당받은 뒤 프로그램을 호출하여 실행하게 만들었다. 하여 자바 프로그램은 OS에게서 독립되었지만 다르게 보면 프로그램 대신 JVM이 OS에 종속적되어 있게 된다. 즉 java파일은 OS에 상관없이 작성이 가능하지만 JVM은 OS에 맞춰 설치해야한다. 또한 아무래도 JVM이 바이트코드를 해석하는 과정을 거치기 때문에 일반프로그램보다 속도면에서 느리다는 단점을 가졌었으나... JIT 컴파일러를 구현해 이점을 많이 극복하게 된다. 자바 컴파일러는 .java 파일을 .class라는 자바 바이트 코드로 변환시켜주는 역할을 하는데 여기서 이 바이트 코드는 JVM위에서 OS에 상관없이 실행된다. 따라서 자바 소스 파일(*.java)과 바이트 코드 파일(*.class)은 OS와 상관 없이 모든 JVM에서 동일한 실행 결과를 보장한다는 것이 자바의 제일 큰 장점이다.
자바 컴파일러(Java compiler)
자바 컴파일러는 자바를 가지고 작성한 자바 소스 코드를 자바 가상 머신이 이해할 수 있는 자바 바이트 코드로 변환한다. 자바 컴파일러는 자바를 설치하면 javac.exe라는 실행 파일 형태로 설치되는데 'javac'라는 명령어로 .java 에서 .class 파일로 변환을(컴파일러를 실행) 시킬 수 있다.
주의할 점
- 소스 파일 이름 "HelloWorld.java은"소스 파일 ...에서 클래스 이름과 일치해야 한다. 대 소문자 중 하나라도 일치하지 않으면 컴파일 오류가 발생한다.
- 바이트 코드 파일명 "HelloWorld.class"는 클래스 명에 대응한다. "HelloWorld.class"의 이름을 바꾸려면 실행하려고 할 때 오류가 발생한다.
- java를 사용하여 Java 응용 프로그램을 실행하는 경우 당연하지만 바이트 코드 파일 이름이 아닌 클래스 이름을 제공한다.
- .java에 한글이 포함되어 있을 시 utf-8로 인코딩('-encoding utf-8')을 해줘야 오류가 나지 않는다.
- 패키지안에 .java파일을 생성했다면 패키지의 디렉토리 위치까지 적어줘야 컴파일 오류가 나지 않는다. 예를들어 패키지가 package com.concept; 라고 생성되어 있다면 javac com/concept/HelloWorld.java(java 파일명) 처럼 적어줘야 한다.
자바 바이트 코드(Java bytecode)
자바 바이트 코드(Java bytecode)란 자바 가상 머신이 이해할 수 있는 언어로 변환된 자바 소스 코드를 의미한다. 자바 컴파일러에 의해 변환되는 코드의 명령어는 비로소 JVM위에서 실행될 수 있다. 자바 바이트 코드의 확장자는 .class로 JVM만 설치되어 있으면, 어떤 운영체제에서라도 실행될 수 있다.
JVM의 구성요소
프로그램이 실행되면, JVM은 OS으로부터 이 프로그램이 필요로 하는 메모리를 할당받고, JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다. .java파일은 자바 컴파일러를 거쳐 .class 파일로, .class 파일은 JVM으로 넘어온다. JVM을 구성하는 요소는 아래와 같이 클래스 로더, Runtime Data Area(할당받은 메모리 공간), 실행엔진으로 나눠진다.
Class Loader
런타임 시점에 JVM 안으로 클래스(.class파일)를 로딩하게 하고 링크를 통해 배치하는 작업을 수행하는 모듈. 즉, 클래스파일을 Runtime Data Area(메모리)에 적재해주는 기능을 가진다. 런타임 시점에 동적으로 클래스를 로드하고 jar파일 내에 저장된 클래스들을 jvm 내로 탑재하고 사용하지 않는 클래스들은 메모리에서 삭제한다. 이 시스템은 3단계로 나누어져 있다.
- 로딩 : 클래스파일을 읽어서 바이트코드-> 바이너리 코드로 변환하여 메소드 영역에 저장하며, 저장하는 데이터는 아래와 같다. 로딩이끝나면 해당 클래스 타입의 객체를 생성하여 "힙"영역에 저장한다.
1) Fully-Quailified Class Name( 클래스 로더, 클래스 패키지 경로, 패키지 이름, 클래스 이름을 모두 포함한 값)
2) 클래스, 인터페이스, 이늄(이 셋을 구분하여 저장)
3) 메소드와 변수 - 링크 : Class 파일이 유요한지 검사(Verify)하고, static 변수와 기본 값에 필요한 메모리를 준비(Prepare)한다. 마지막으로 심복릭 메모리 레퍼런스를 실제 메모리 레퍼런스로 교체(Resolve)하는데 사용 전인 이 때 교체가 될 수도 있고 나중에 진짜 사용이 일어날 때 동적으로 교체될 수도 있다.
- 초기화 : static 변수를 초기화한다.
Memory (Runtime Data Area)
JVM이 프로그램을 수행하기 위해 OS로 부터 별도로 할당 받은 메모리 공간으로 5개 영역으로 나누어진다. 크게 나누면 전체 공유자원 (메소드,힙)와 쓰레드 단위자원(스택, PC, 네이티브 메소드 스택)으로 분류된다.
- 메소드: 모든 쓰레드가 공유하는 메모리 영역. GC의 관리 대상에 포함된다.
클래스, 인터페이스, 메소드, 필드, Static 변수, 일반 변수 등의 바이트 코드를 보관. - 힙: 프로그램 상에서 런타임시 동적으로 할당하여 사용하는 영역. 객체를 저장하는 가상 메모리 공간으로 new 연산자로 생성된 객체와 배열을 저장한다. GC의 관리 대상에 포함된다.
- 스택: 인스턴스 및 지역 변수의 참조 주소들을 저장한다. 스택은 쓰레드의 수행정보를 프레임을 통해 저장하게 된다. 스택은 쓰레드가 시작될 때 생성되며 각 쓰레드 별로 생성이 되기 때문에 다른 쓰레드는 다른 스택에 접근할 수 없다. 메소드가 호출 되면 메소드와 메소드 정보가 스텍에 쌓이고 메소드 호출이 종료될 때 스택 포인트에서 제거 된다. 메소드 정보는 해당 메소드의 매개변수, 지역변수, 임시변수 그리고 인스턴스 및 지역변수들의 참조주소를 저장한다. 메소드 종료시 메모리 공간은 사라진다.
- PC Registers: 쓰레드가 시작될 때 생성되며 쓰레드마다 가지고 있는 Program Counter이다. 쓰레드가 어떤 부분을 어떠한 명령으로 실행해야할 지에 대한 기록을 하는 부분으로 현재 수행 중인 JVM 명령의 주소를 갖는다. 이 주소는 Native Pointer 일 수도, Method Bytecode일 수도 있다.
- 네이티브 메소드 스택: 네이티브 메소드를 호출할 때 사용하는 별도의 스택. 네이티브 메소드는 java가 아닌 c나 c++같은 언어로 구현된 메소드들을 수행하기 위한 스택이다. Java Native Interface를(JNI)를 통해 바이트 코드로 전환하여 네이티브 메소드 스택에 저장되는데 이는 JVM 내부에 영향을 주지 않기 위해서이다.
Execution Engine
Byte Code를 실행 가능하게 해석해주는 기능. 말 그대로 클래스 실행 엔진이다. 바이트코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경하는데 이 때 아래의 두 가지 방식을 사용해서 변경한다.
- Interpreter(인터프리터) : 자바 바이트 코드를 명령어 단위로 (한 줄씩) 해석하고 실행한다. 최초 JVM에서는 인터프리터 방식만 지원해서 속도가 느리다는 단점이 있었지만 JIT 컴파일러 방식을 통해 이 점을 보완하게 된다.
- JIT Compiler(Just In Time Compiler) : 기본적으로 JVM은 인터프리터 방식으로 실행된다. 그러다 일정한 기준이 넘어가면 JIT Compiler 방식으로 실행된다. 왜냐하면 JIT는 바이트코드를 어셈블리어 같은 네이티브코드로 바꿔서 실행하기 때문에 속도면에서는 빠르지만, 이 변환시에 비용이 발생하게 되기 때문이다. 또한 같은 이유 때문에 JIT가 실행되고 나면 다시 인터프리터 방식으로 돌아가지 않는다. JVM은 내부적으로 해당 메소드가 얼마나 자주 수행되는지 체크하고, 일정 기준이 넘을 때에만 JIT 컴파일러 방식을 채용한다.
- GC (Garbage Collector) : 실행하는 방식이라기 보다는 JVM에 있는 쓰레기들을 컬렉터하는 모듈이다. 더 이상 참조되지 않는 가비지컬렉터가 알아서 모아 메모리를 정리해준다. 하지만 기계이기 때문에 한계가 존재하며, 언제 이 가비지컬렉터가 실행될지 개발자는 정확하게 알 수없다. 때문에 개발자 또한 코드를 짤 때 메모리 관리에 신경을 써야한다.
*가비지 컬렉션 참고
참고
tcpschool.com/java/java_intro_programming
medium.com/@lazysoul/jvm-%EC%9D%B4%EB%9E%80-c142b01571f2
huelet.tistory.com/entry/JVM-%EB%A9%94%EB%AA%A8%EB%A6%AC%EA%B5%AC%EC%A1%B0
www.javamadesoeasy.com/2015/06/jdk-java-development-kit-jre-java.html
'Backend > Study Halle - Java' 카테고리의 다른 글
온라인 자바 스터디 4주차 - 제어문 (0) | 2020.12.16 |
---|---|
온라인 자바 스터디 3주차 - 연산자 (0) | 2020.12.12 |
온라인 자바 스터디 2주차 - (2) 타입의 변환, var 그리고 배열 (0) | 2020.12.05 |
온라인 자바 스터디 2주차 - (1) 자바의 변수와 데이터 타입 (0) | 2020.12.03 |