#학습할 것
- JVM이란 무엇인가
- 컴파일하는 방법
- 실행하는 방법
- 바이트코드란 무엇인가
- JIT컴파일러란 무엇이며 어떻게 동작하는가
- JVM 구성요소
- JDK와 JRE의 차이
#JVM
우리가 JAVA 언어를 사용하는 이유를 설명할때면 빠지지 않는 것이 바로 JVM이라는 가상 머신입니다.
JAVA VIRTUAL MACHINE의 약자인 JVM은 간단히 이야기해 우리가 어떠한 환경에서든 자바 언어를 활용하여 개발할 수 있도록 도와주는 고마운 친구입니다.(자바 언어가 유명해진 이유이기도 합니다.)
JVM은 우리가 어떤 프로그램을 자바언어로 개발했을 때 각종 하드웨어(CPU)나 OS(CPU)에서 작동할 수 있도록 도와주는 이동통로라고 보면 좋을 것같습니다. 따라서 자바는 자바 이전의 일반적인 언어와는 구조에서 차이가 있습니다.
사용자가 자바프로그램(.java)파일을 컴파일해서 보내게 되면 자바 컴파일러가 jvm이 읽을 수 있는 자바바이트코드로 자바프로그램 파일을 변형시켜줍니다. 그렇게 JVM에 들어가게되면 각 OS에 맞게 커스터마이징되어있는 JVM들이 프로그램을 실행 시킵니다.
자바언어와 기계어 사이의 자바바이트코드언어는 JRE위에서 동작합니다.
JRE는 JAVA RUNTIME ENVIROMENT의 약자이며 자바프로그램을 실행하기 위한 최소 단위의 자바 실행환경입니다. 이 JRE에 JVM과 API가 있습니다.
현재는 JVM에 대해 공부하고 있기 때문에 JRE는 자바 실행 환경이고 그 안에 JVM이 있다는 것 정도만 알고 넘어가겠습니다.
JVM은 크게 네가지 영역으로 나눌 수 있습니다.
Class Loader, Garbage Collector, Execution Engine, RunTime Data Area 이중 가장 마지막의 Run Time Data Area에 대해 자세히 알아보고 나머지 세개는 먼저 간략히 보고 넘어가겠습니다.
- Garbage Collector
Runtime Data Area 중 Heap 영역에서 더 이상 쓸모가 없어진 객체들을 제거해주는 역할을 합니다. 자동으로 실행되기 때문에 적확한 타이밍은 알기 어렵습니다. 임의로 GC를 실행시키는 것은 비효율적입니다. 또한 GC 쓰레드를 사용하는 동안에는 다른 모든 쓰레드가 일시 정지되기 때문에 GC의 실행은 JVM에게 맡기는 것이 좋습니다.
특히, Full GC가 일어나는 수초간 모든 Thread가 정지한다면 심각한 장애로 이어질 수 있다. - Class Loader
자바 프로그램 파일이 입력되고 자바 컴파일러가 자바 바이트코드로 변환을 시켜 jvm에게 할당하게 됩니다. 그 다음 jvm안에서는 Class Loader가 OS에게 메모리를 할당 받은 Runtime Data Area로 적재시키는 역할을 합니다. - Execution Engine
Class Loader가 Runtime Data Area에 적재시킨 .class 파일들을 하나의 명령단위로 읽어서 컴퓨터가 이해할 수 있는 기계어로 번역하고 명령을 실행합니다. - Runtime Data Area
JVM메모리 영역으로 OS로부터 별도로 메모리 공간을 할당 받고 JAVA 어플리케이션을 실행할 때 사용됩니다.
총 5가지로 구분됩니다. Method Area, Heap Area, Stack Area, PC register, Native Method Stack.
#Runtime Data Area
PC Register | JVM stack area | Native Method stack | Heap | Method Area |
좌측 3개가 Thread별로 생성되고 우측 2개가 모든 Thread가 공유한다.
- Method Area(Static Area)
Method Area(Static Area)는 자바 어플리케이션이 실행되면 클래스 별로 클래스에서 필요한 패키지 클래스 , 런타임 상수풀, 인터페이스, 상수, static 변수, final 변수, 클래스 멤버 편수 등 필드 데이터, 생성자를 포함한 모든 메서드 정보등을 위해 한번 로드 되면 메모리에 상주하고 있는 영역입니다. 따라서 모든 Thread가 공유가능 합니다. 예를 들어 java.lang 패키지 안에 String클래스, Math클래스 등을 자바 프로그램 안에서는 언제든 객체 생성 없이 사용이 가능한 것입니다.(static 변수 및 메서드 도 포함)
Class Loader가 적재한 클래스(또는 인터페이스)에 대한 메타데이터 정보가 저장되며 Method Area에 등록된 class만이 Heap Area에 생성될 수 있습니다. - Heap Area
Heap Area는 메서드 안에서 사용되는 객체들을 위한 영역입니다. Garbage Collector가 활동하는 영역이기도 합니다. new 생성자를 통해 생성된 객체, 배열, immutal 객체 등의 값이 저장됩니다. 이 영역에서 생성된 객체들을 JVM Stack Area의 변수나 다른 객체의 필드에서 참조 가능합니다. 따라서 참조되지 않고 더 이상 쓸모 없는 객체들은 Garbage Collector에 의해 해당 영역에서 제거됩니다.
생성에 필요한 인스턴스와 배열의 메타 정보는 Heap 내의 Method Area에서 얻어온다.
- Stack Area
Stack Area는 스레드마다 하나씩 존재하며 쓰레드가 실행될 때 할당 됩니다. 내부에는 메소드에서 직접 사용할 지역 변수, 파라미터, 리턴 값, 참조 변수일 경우 주소 값 들이 저장됩니다. 만약 프로그램에서 매소드가 호툴되면 메소드와 메소드 정보는 차곡차곡 Stack에 쌓이면서(push) 내부 과정을 실행합니다. 메소드 호출이 종료되면 해당 메소드는 Stack Area에서 제거됩니다.
하나의 쓰레드가 시작되는 자바프로그램을 보면 Stack에 가장 아래 main 메소드가 실행되고 그 위에 메인 메서드에서 호출한 메서드 그리고 그 안에서 호출하는 메서드 등의 순서로 차곡 차곡 쌓이게되고 가장 마지막에 들어온 것부터 나오게 됩니다. 컴퓨터는 FIFO(FIRST IN FIRST OUT) 구조를 따른 다고 생각했는데 STACK의 개념에 대해 배워 좋은 것 같습니다. 가장 먼저 들어온 것이 가장 나중에 나간다. - PC register
PC register은 스레드가 생성될 때마다 각 스레드의 현재 실행되고 있는 영역 주소와 해당 명령어(program counter)를 저장하는 영역입니다. 이 덕분에 다수의 스레드들이(멀티스레드) 명령의 흐름을 잃지 않고 함수가 순차적으로 실행될 수 있습니다. - Native Method Stack
Native Method Stack은 자바 외 다른 언어로 작성된 네이티브 코드를 수행하기 위한 메모리 영역입니다. java 이외의 다른 언어에서 제공되는 Method의 정보가 저장됩니다.
#Heap Area(Garbage Collector의 놀이터)
힙영역은 가비지 컬렉터의 놀이터입니다. heap area가 이렇게 세세하게 나눠진 이유는 garbage collector의 효율적 활용을 위해서 입니다. garbage collector는 minor GC 와 major GC입니다. 우선 new 생성자를 통해 생성된 객체는 Eden 영역에 올라오게 됩니다. 여러 객체가 new를 통해서 Eden영역에 올라오는데 이 영역이 가득차게되면 GC가 활성화됩니다. Eden에서 참조되는 않는 객체를 먼저 제거한 후 참조되어 있는 객체는 survivor0으로 이동시킵니다. 이 과정이 다시 반복되고 new로 생성된 객체 중 살아남은 객체는 survivor1으로 이동됩니다. 이에 더해 s0에 있는 객체 중 참조가 유지되고있는 객체들을 s1으로 이동시키고 age +1을 합니다. 이런 과정을 진행시키는 GC를 Minor GC라고 합니다. Minor GC가 자신의 역할을 반복하게되면 살아 남은 객체들의 age는 적정수준으로 올라오게 됩니다. 이렇게 오래 살아남은 객체들은 old영역으로 옮겨집니다. 이때 Major GC가 Old영역의 모든 객체를 검사하고 참조되지 않은 모든 객체를 한번에 모아 제거합니다. 이를 Full GC라고도 부르며 Full Garbage Collector가 실행되는 동안에는 이 쓰레드를 제외한 모든 쓰레드가 중지되어야 합니다.
public class Test{
//클래스 필드
public static int ONE = 1;
public static String TWO = "TWO";
public static String FOUR;
//상수
public static final int FIVE = 10;
//정적블럭
static{
FOUR = "안녕하세요";
}
//인스턴스 필드
private int three;
public Test(int three){
this.three = three;
}
//클래스 메서드
public int multiply(int a, int b){
return a*b;
}
public int getThree(){
return three;
}
}
//main Thread
public static void main(String args[]){
int one = Test.ONE;
String two = Test.Two;
int one_sum_two = Test.sum(1,2);
Test test = new Test(3);
int three = test.getThree();
int two_multiply_three = test.multiply(2,3);
System.out.println("one : " + one);
System.out.println(" two : " + two);
System.out.println("one_sum_two : " + one_sum_two);
System.out.println("three : " + three);
System.out.println("two_multiply_three : " + two_multiply_three);
System.out.println("FOUR : " + Test.FOUR);
System.out.println("FIVE : " + Test.FIVE);
}
//출력
one : 1
two : TWO
one_sum_two : 3
three : 3
two_multiply_three : 6
FOUR : 안녕하세요
FIVE : 10
#Class Loader에 의한 Test.class 파일의 로드 과정
첫번째로 컴파일 시점이 아닌 runtime 시점에서 모든 과정은 시작됩니다. 직접 만든 java 어플리케이션을 실행하는 도중(main 메서드 실행중) 위 Test.class 파일을 사용하는 부분을 처음 만나는 순간 Class Loader에 의해 Test.class 파일이 Loading, Linking, Initialization 과정을 겪습니다.
Loading
BootStrap ClassLoader, Extention ClassLoader, Application ClassLoader 3개의 클래스 로더를 통해 Test.class 파일이 JVM안으로 로드 됩니다. Class Loading을 요청 받았을 때 BootStrap ClassLoader > Extension ClassLoader > Application ClassLoader 순서로 우선권을 가지며 BootStrap ClassLoader에 없다면 Extension ClassLoader을 찾고 Extension ClassLoader에 없다면 최종적으로 Application ClassLoader에서 찾는다고 생각하시면 됩니다. Test.class 파일은 Application ClassLoader가 찾아 Loading 합니다.
- BootStrap : 가장 최상위 ClassLoader로 java.lang.Object같은 핵심 시스템 Class를 로딩합니다.$JAVA_HOME/jre/lib 위치에 있는 JAR파일이 그 대상입니다. 위를 변경하기 위해서는 -Xbootclasspath 옵션을 통해 변경가능합니다.
- Extension ClassLoader : $JAVA_HOME/jre/lib/ext에 위치한 JAR파일을 로딩합니다. 사용자의 classpath를 수정하지 않고 다양한 보안 확장프로그램 같은 새로운 확장 프로그램을 로드할 수 있스니다.
- Application ClassLoader : $CLASSPATH에 위치한 JAR파일을 로딩합니다. 직접 만든 어플리케이션의 JAR파일의 classpath라고 보시면 됩니다.
Linking
- Verify : 로드된 Test.class파일들의 에러를 확인합니다.
- Prepare : Test.class파일들을 훑으면서 static 키워드가 붙은 멤버들에 대해 메모리만 할당하고 기본값으로 세팅합니다.(아직 정확하게 값을 넣기 전입니다.) 즉 public static int ONE = 0;으로 세팅합니다.
- Resolve : Test.class 파일들을 훑으면서 지금은 없지만 symbolic reference(Test.TWO와 같이 객체 이름으로 참조하는 것)들을 Method Area에서 실제 주소값을 찾고 그 주소값으로 참조 객체들을 세팅합니다.
Initialization
최종 단계로 모든 정적 변수는 원래 값으로 할당되고 정적 블록이 실행됩니다.
public static int ONE =1 값으로 할당하고, static 블록 내부 FOUR ="안녕하세요"를 실행합니다. 이때 Method Area로 Test.class에 있는 모든 정보들이 Method Area 안 독립적인 Test.class 영역으로 배치됩니다.
Field Information
Class 멤버변수의 이름 및 데이터 타입, 접근 제어자에 대한 정보를 저장
Method Information
Class 멤버메서드의 이름, 리턴타입, 매개변수, 접근제어자에 대한 정보
Type Infomation
Type의 속성이 Class인지 Interface인지여부 , 패키지명, Supper Class에 대한 정보
Constant Pool
class에서 사용된 상수가 저장되는 영역, Symbolic reference 객체도 여기에 저장된다.
Static Variable
static 변수들에 대한 정보들이 저장된다.
main 메서드 안에서 Test test = new Test(3);을 만났다.
Method Area에서 객체정보를 읽어 Heap메모리 안에 독립적으로 생성합니다.
#컴파일하는 방법
메모장 또는 여러 에디터에서 Hello.java파일을 생성한다. javac(cmd에서 직접 자바 컴파일러를 불러서 실행시킨다.)
하지만 나의 경우에는 오류가 났다.
javac 는 내부 또는 외부 명령 환경변수
환경변수 설정이 되어 있지 않아서 이런 오류가 발생했다. 윈도우 검색에서 시스템 환경 변수를 검색하면 바로 환경변수를 설정하러 들어 갈 수 있다. 잠깐, 여기서 왜 환경변수를 설정해주는지에 대해 알아보겠다. 컴퓨터의 운영체제가 환경변수에 설정된 것들을 바탕으로 사용자가 원하는 것들을 실행시킬 수 있는데 운영체제가 한번에 잘 찾을 수 있게 미리 경로를 설정해 놓는 것을 환경변수라고 이야기한다. java를 사용하려면 운영체제가 java실행파일을 찾아야한다. java 실행 파일은 java -> jdk - bin 에 존재한다.
1. 환경변수를 찾아간다.
2. 사용자 변수에서 JAVA_HOME 네임으로 지정하고 값을 JDK 경로를 붙여 놓는다.
3. 시스템 변수 PATH를 더블클릭해서 %JAVA_HOME%\bin을 새로 입력한다.
모든 경로를 다 생성하고 나서 cmd를 재시작하고 javac를 입력하면 제대로 작동이 된다.
오류를 해결하고 다시 컴파일을 실행했을 때는 너무 잘 실행됬다.
위처럼 javac Hello.java를 입력하면 Hello.class 파일이 생성됩니다. (java 컴파일러야 Hello.java 파일 컴파일 해줘!)
인터프리터와 jit컴파일러 비교하기
#실행하는 법
간단하다 java한테 Hello라는 파일 읽어달라고 명령하면 된다. 단, 주소를 잘 입력해 주어야한다.
주의할점
* 자바에서 모든 코드는 반드시 클래스 안에 존재해야한다.
* 모든 클래스가 main메서드를 가지고 있어야 하는 것은 아니지만, 하나의 java 어플리케이션에는 main 메서드를 포함한 클래스가 반드시 하나는 있어야한다.
* 예외적으로 애플릿이나 서블릿은 main 메서드가 없어도 되나, 대신 유사한 역할을 하는 다른 메서드가 존재한다.
javac에 대한 옵션들 조사하기
#바이트코드란?
특정 하드웨어가 아닌 가상 컴퓨터에서 돌아가는 실행프로그램을 위한 이진 표현법이다. 하드웨어가 아닌 소프트웨어에 의해 처리되기 때문에, 보통 기계어보다 더 추상적이다. 바이트코드 프로그램은 보통 한번에 하나의 명령어를 읽은 후 실행한다. 이러한 형태의 바이트코드 인터프리터는 높은 이식성을 갖는다. 또 다른 형태로서 실시간 번역기 또는 저스트 인 타임 (just in time,JIT) 컴파일러라 불리는 시스템은 실행 중에 필요에 따라서 바이트 코드를 기계어로 번역한다. 보통 자바(jvm)와 c#(.net) 은 바이트코드형태로 컴파일 된후 실행 전 JIT컴파일에 의해 기계 코드로 번역된다.
#JIT(JUST -IN-TIME COMILATION)또는 동적번역
프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다.
전통적인 입장에서 컴퓨터 프로그램을 만드는 방법은 두가지가 있다. 인터프리트 방식과 정적 컴파일 방식이다.
인터프리트 방식 : 프로그램이 실행하는 중에 프로그래밍 언어를 읽어가며 해당 기능에 대응하는 기계어 코드를 실행
정적 컴파일 : 실행하기 전에 프로그램 코드를 기계어로 번역한다.
JIT컴파일러는 이 두가지 방식을 혼합한 방식이다. 인터프리트방식으로 기계어 코드를 생성하면서 해당 코드를 캐싱하여, 같은 함수를 여러번 불릴 때 매번 기계어 코드를 생성하는 것을 방지한다.
단점 : 초기 구동 후 얼마간은 소스코드(혹은 바이트코드)를 컴파일하는 데에 시간과 메모리를 소모하기 때문에 정적 컴파일된 프로그램에 비해 초기 실행 속도와 메모리 사용량에서 손해를 본다는 것으로, 특히 실행 시간이 매우 짧은 경우에는 애써 컴파일 된 코드를 제대로 사용하기도 전에 프로그램이 끝나는 배보다 배꼽이 더 큰 상황이 벌어지기도 한다.
JIT 컴파일러를 사용하는 JVM은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고 일정 정도를 넘을 때만 컴파일을 수행한다.
#JRE와 JDK의 차이점 (java 9버전 부터는 jdk에 jre가 포함되어있다)
JRE란 위에서 배웠다 싶이 JAVA RUNTIME ENVIROMENT(자바 실행 환경)이다. JRE는 JVM, 자바 클래스 라이브러리, 자바명령 및 기타 인프라를 포함한 컴파일된 자바프로그램을 실행하는데 필요한 페키지이다. 기본적으로 JAVA 관련 파일이 있는 디렉터리이다.
포함되어 있는 폴더와 파일
- bin/ : java 실행 프로그램이 포함되어 있다. JVM을 시작하는 java(window의 경우 javaw)가 포함되어 있다. 또한, keytool 및 policytool과 같은 다른 유틸리티도 있다.
- conf/ : 사용자가 편집할 수 있는 구성 파일(configuration files)가 있다.
- lib/ : lib에는 여러가지 supporting 파일이 있다. 예를 들어 일부.jar 구성 파일, 속성 파일, 글꼴, 번역, 인증서 등 java의 모든 trimming들이 있다. 가장 중요한 것은 모듈인데, java 표준 라이브러리의 .class 파일을 포함하는 모듈이 있다.
- 정해진 수준에서 Java 표준 라이브러리는 네이티브 코드를 호출해야 한다. 이를 위해 JRE에는 시스템 별 네이티브 바이너리 코드를 지원하는 .dill(Window), dylib(macOS), so(Linux) 파일이 bin/ 또는 lib/아래에 포함되어 있다.
JDK(JAVA DEVELOPMENT KIT)란 JAVA를 사용하기 위해 필요한 모든 기능을 갖춘 JAVA용 Software Development Kit이다.
JRE에 있는 모든 것뿐만 아니라 컴파일러(JAVAC)와 JDB, JAVADOC과 같은 도구도 있다. 즉, JDK는 프로그램을 생성하고 컴파일 할 수 있다.
JDK 또한 디렉터리의 세트이다. JRE의 상위집합이다.(아래의 것들을 추가한)
- bin/은 개발도구로 확대되었다. 그중 가장 중요한 것은 .jar,javadoc,jshell을 포함한 javac이다.
- 표준 라이브러리용 JMOD파일을 보유하는 jmods/가 추가되었다. 이러한 파일은 표준 라이브러리를 jlink와 함께 사용하는 것을 허용한다.
정리하자면, JDK는 JRE를 표한하고 있다.
일반적으로 컴퓨터에서 JAVA 프로그램을 실행하는데만 포커스를 둔다면, JRE만 설치하면 된다. 반면에 JAVA프로그래밍을 할 계획이라면 JDK를 설치해야한다.
JAVA 프로그래밍을 수행할 계획이 없더라도 JDK를 설치해야하는 경우가 있다. 예를 들어 JSP를 사용하여 웹 어플리케이션을 배포하는 경우 기술적으로는 애플리케이션 서버 내에서 JAVA프로그램을 실행하는 것이기 때문에 JDK가 필요하다. 애플리케이션 서버는 JSP를 JAVA 서블릿으로 변환하고 JDK를 사용하여 servlet을 컴파일해야 하기 때문이다.
컴파일 타임과 런타임을 구분할 줄 알아야한다. 에러는 컴파일 타임에 나는 것이 좋다.
출처 : https://honbabzone.com/java/java-jvm/#step-3-heap-area
JVM( Java Virtual Machine )이란
JAVA를 공부하는 데 있어 기본이 되는 JVM이 무엇인지 학습하고, JVM의 메모리 구조와 Garbage collector, Execution Engine, Class Loader에 대한 기본적인 설명 등 JVM이 어떻게 돌아가는지에 대한 기초를 잡는
honbabzone.com
'about. What I learned > about.Study by myself' 카테고리의 다른 글
3주차 자바스터디 (0) | 2021.08.24 |
---|---|
2주차 자바 스터디 (0) | 2021.08.16 |
메일 보내기 (0) | 2021.07.23 |
::before, after 와 무한 스크롤 페이징 (0) | 2021.07.23 |
Tomcat 비정상 종료시 발생하는 에러에 대하여 (0) | 2021.07.17 |