Study Halle(백기선)
1주차 과제
22.10월 3일 ~ 8일
목표
자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.
JVM이란 무엇인가
자바 애플리케이션을 실행하기위한 런타임 엔진 역할을 하는 머신이다.
JVM은 누구도 제한 설계도를 만들어 제공하지 않는다. 단지 JVM은 이렇게 저렇게 해야한다는 식의 정의만 존재할뿐이다.
표준화된 정의가 나오면 JVM 벤더들은 이에 맞도록 자신으들의 JVM을 구현한다.
그렇기 떄문에 정통 JVM이라는 것은 없다.
이것이 JVM의 중요한 특징이다.
따라서 JVM은 정의된 Specification을 구현한 하나의 독자적인 Runtime Instance라고 할 수 있다. 여기서 하나의 독자적인 Intstance
라는 것은 하나의 프로세스 형태로 구동한다는 점을 강조한 것이다.
- Java Architecture
JVM의 역할은 우리가 작성한 Java 프로그램, Web Application Server(WAS)등을 구별하지 않고 Java 프로그램의 범주에 들어가는 모든 것덜을 실행시키게 된다.
이것이 Java에서 JVM이 핵심이라고 말하는 이유이기도 함
위의 그림 순서
- JVM은 Class Loader System을 통해 Class 파일들을 JVM으로 로딩
- 로딩된 Class 파일들을 Execution Engine을 통해 해석
- 해석된 프로그램은 Runtime Data Area에 배치되어 실질적인 수행
※ JVM은 필요에 따라 Thread Synchronization과 Garbage Collection 같은 관리 작업을 수행
컴파일 하는 방법
- 컴파일러의 목적
- 오류 발견
- 소스코드에서 오류를 발견하여 실행 시 문제가 없도록 도와준다. 컴파일러는 오류를 찾기 위해 심벌 테이블(symbol table)을 사용한다. 심벌 테이블은 변수 선언부에 명시한 각 변수의 이름과 타입을 모아 놓은 테이블로 선언하지 않는 변수를 사용하지 않았는지, 변수에 다른 종류의 데이터를 저장하지는 않았는지 알 수 있음
- 코드 최적화
- 오류 발견
C언어 컴파일러 같은 경우
자바의 컴파일러는 ?
우선 어휘 분석기에서는 무엇을 하는지 알아보자.
- 심볼 테이블이란 ?
- 컴파일러에서 사용하고 유지 관리하는 데이터 구조로 모든 식별자의 이름과 유형으로 구성됩니다. 식별자를 빠르게 찾아 컴파일러가 원활하게 작동하도록 도와줍니다.
어휘 분석기(Lexical Analysis)
java의 어휘분석기는 예약어(keyword)
, 연산자(operator)
, 리터럴(literal)
, 특수기호(Special symbol)
, 식별자(Identifier)
를 수집한다.
예약어란 ? 자바에 미리 등록되어 있고 의미가 약속되어 있는 명령어이다.
- 자바 예약어
타입 | 예약어 |
---|---|
기본 데이터타입 | boolean, byte, char, short, int, long, float, double |
접근 지정자 | private, protected, public |
클래스 관련 | class, abstract, interface, extends, implements, enum |
메소드 관련 | void, main |
제어문 관련 | if, else, switch, case, default, for, do, while, break, continue |
논리 리터럴 | true, false |
예외 처리 관련 | try, catch, finally, throw, throws |
기타 | transient, volatile, package, import, synchronized, native, final, static, strictfp, assert |
- 연산자
종류 | 산술 연산자 |
---|---|
대입 연산자 | |
증감 연산자 | |
비교 연산자 | |
논리 연산자 | |
비트연산자 | |
삼항 연산자 | |
instanceof 연산자 |
- 리터럴
리터널은 데이터 그 자체를 뜻함. 변수에 넣는 변하지 않는 데이터를 의미한다.
예약어(keyword)
, 연산자(operator)
, 리터럴(literal)
, 특수기호(Special symbol)
, 식별자(Identifier)
전체를 어휘소
라고 한다
모인 어휘소를 하나의 스트림으로 만들어준다. 이것을 토큰 스트림
이라고 한다.
그림과 같이 토근 스트림까지 뽑아내는 과정을 어휘 분석
이라고 한다.
구문 분석(Syntax Analysis)
이제 어휘 분석된 결과를 파싱(Parsing)을 할 차례다.
아래의 그림은 C언어로 된 예제이다. 참고바란다.
아래 그림을 보면 느낌상 구문 분석이 어떻게 돌아가는지 감이 올 것이다.
출처 : https://upload.wikimedia.org/wikipedia/commons/5/5b/Xxx_Scanner_and_parser_example_for_C.gif
의미 분석
의미 분석단계에서는 위의 그림에 구문트리와 심볼 테이블에 있는 정보를 이용해서 소스 코드가 언어 정의에 맞는 지 안맞는지 의미적으로 확인한다. 대표적인 의미분석은 타입 검사, 자동 타입 변환 등이 있다.
- 예를들면
System.out.println(1/0);
String s = 222L;
이런 과정을 통해서 컴파일을 한다고한다… 이걸 정리하는데 컴파일러가 자료 찾기가 가장 빡센거같다..
이유는 C컴파일러와 Java컴파일러가 다른거 같은데 찾는게 힘들었다..
- 기계어란?
기계어는 컴퓨터의 computer’s native language(모국어 ?)이다. 바이너리 코드 형태의 기본 제공 기본 명령어 세트이다. 컴퓨터에 명령을 내리면 이진 코드로 명령을 입력해야한다.
➡️기계 코드 예제
참고:
- 쉽게 배우는 운영체제(한빛 아카데미)
- **The Levels of Programming Languages**
The Levels of Programming Languages
실행하는 방법
실행하는 방법에 대해서 설명하겠다. 스터디이기 때문에 처음부터 차근차근 정리해드리겠다. (초심자의 마음으로)
- OS 환경 : MacOS
우선 터미널 또는 iterm을 킨다. 글쓴이는 iterm으로 하겠다.
- vi 에디터를 사용하여 소스코드를 작성한다.
- 소스 코드 작성
➡️번외:컴파일 하기 전 우선 java HelloWorld.java
를 해보자.
이런 에러가 나온다. `HelloWorld.java를 찾을 수 없거나 로드할 수 없다고 뜬다.
그렇다 안된다. 컴파일이 필요하다. 즉, .class 파일
이 필요하다.
- 소스코드를 컴파일해준다. → 컴파일은
javac
를 이용
- 컴파일을 하게된다면
.class
파일이 생성된다.
- 실행해준다.
출력이 잘 된다.
➡️번외 : .class
파일만 갖고 있다면?? .java
를 구해보자.
- 컴파일된 파일을 소스코드로 바꿔보자.
우선 .class
파일 삭제하고 하겠다.
나온다. 역으로
- 참고 :
javap -s HelloWorld
를 하니깐 Print internal type signatures(내부 타입 시그니쳐 출력) 이라는 기능이 있다.
javap —help
를 한다면 아래와 같이 나온다.
바이트코드란 무엇인가
- 바이트 코드란 자바 가상 머신을 위한 명령어 세트이다. 즉 바이트코드는 본질적으로 Java Virtual Machine에서 실행되는 기계 수준 언어이다.
- C++ 코드의 표현인 어셈블러와 유사하게 작동한다.
- 자바 소스코스가 컴파일 하면 → 자바 바이트코드가 생성된다.
- Java 바이트코드는
.class
파일 형식의 기계어 코드이다. - 자바는 바이트코드의 도움으로 자바에서 플랫폼 독립성을 갖게되었다.
- 코드의 명령어 크기가 1바이트
Java 바이트 코드의 장점
- 이식성이 올라간다. → Write code once, run code anywhere
- 바이트코드의 도움으로 플랫폼에 독립적으로 하게하는 목표에 기여함.
- JVM에 대한 instructions set(명령어 세트)는 시스템마다 다를 수 있지만 모든 시스템에서 바이트코드를 실행할 수 있다.
Java 바이트 코드의 단점
- 바이트 코드를 생성하는데 비용이 추가로 발생한다.
- Java는 플랫폼에 독립적이기 때문에 일부 플랫폼별 기능을 사용하기 어렵다.
- 바이트 코드를 실행하려면 Java 인터프리터 설치가 필요하다.
JIT 컴파일러란 무엇이며 어떻게 동작하는지
Execution Engine은 두 가지 방식으로 Bytecode를 해석한다.
- Interpreter방식
- 장점 : bytecode를 해석하는 시간이 짧다
- 단점 : Interpreter의 결과물을 수행하는 실행 시간은 많이 걸린다.
- JIT(Just-In-Time) Compiler방식
여기서는 JIT에대해서 깊게 알아보자.
JIT(Just-In-Time) Compiler 방식은 말 그대로 적절한 시점에 Compile을 수행하는 것이다.
Load된 Class에 대해 Interpreter 방식으로 동작하다가 반복 수행을 감지하게 되면 적절하게 JIT Compiler가 동작하여 실행속도를 향상시켜준다.
JIT Compiler의 핵심은 Bytecode로부터 Native Code를 생성한 뒤에 실행하는 것이다.
.java
→ Source Code가 Bytecode로 변경 된 후 → JIT Compiler를 거치면 어셈블러 같은 Native Code로 변경이 되어 수행된다.
위와 같은 동작은 실행 속도
가 느린 Interpreter의 단점을 극복한 것이다.
반면 실행시간
이 매우 단축되었지만, Bytecode에서 Native Code로 Compile 되는 시간은 Interpreter보다 길어진다.
JIT Compiler의 전체 실행 시간은 Bytecode를 Native Code로 변경하는 시간과 변경된 Native Code를 실행하는 시간을 합한 것이다.
JIT Compiler의 전체 실행 시간 : Bytecode → NativeCode로 변경하는 시간 + Native Code를 실행하는 시간
Native Code를 실행하는 시간은 매우 빠른데다가, 기본적으로 Memory Cache가 이루어지기 때문에 반복 호출 시 성능이 극대화 된다.
하지만 Native Code로 변경하는 시간에서 약간의 지연이 있기 때문에 반복 수행이 되지 않는다면 Interpreter보다 성능이 떨어질 수도 있다.
- JIT Compiler의 동작방식
JVM에서는 모든 Code에 대해 JIT Compiler를 적용하지는 않는다. Interpreter를 사용하다가 일정한 기준을넘어서게 되면 JIT Compiler를 가동하는 방식(Lazy Fashion)을 선택하고 있다.
위의 그림은 Class A를 수행하는데 JIT Compiler가 동작하게 되는 방식을 그림으로 표현한 것이다.
위그림을 설명하면 Class a는 Method f1( )과 f3( )을 수행한다.
그러나 class a는 Heap에 A라는 Instance로 생성되어 있고 Method Area의 Method Table에는 Method f2( ), f4( )가 있는 것으로 볼 때 class a는 이 Method들을 상속받은 것으로 알 수 있다.
이러한 상황에서 Method f1( )과 f3( )을 반복적으로 수행하게 되면, JIT Compiler는 Mehtod Area에서 Method f1( ), f3( )의 Bytecode를 가져와 Compile을 수행한다.
그 이후 이 결과물은 Native Code를 Cache해 놓은 곳에 저장된다. 그렇게 되면 나중에 Mehtod f1( ), f3( )을 수행할 때 Cache된 Native Code를 이용하게 된다! ….
Method f2( ), f4( )는 이미 Compile을 수행하여 Native Code가 되므로 이를 호출하면 다른 과정 없이 Cache에서 바로 Native Code를 참조하여 사용하게 된다.
참고:
서적 - Java Performance Fundamental”(김한도, 서울, 엑셈, 2009)
JVM 구성 요소
Class Loader
- .class 에서 바이트코드를 읽고 메모리에 저장
- 로딩: 클래스 읽어오는 과정
- 링크: 레퍼런스를 연결하는 과정
- 초기화: static 값들 초기화 및 변수에 할당
Memory Data Areas
메모스 영역에는 클래스 수준의 정보 (클래스 이름, 부모 클래스 이름, 메소드, 변수) 저장.
공유 자원이다.
- 힙 영역에는 객체를 저장. 공유 자원이다.
- 스택영역에는 쓰레드 마다 런타임스택을 만들고,그 안에 메소드 호출을 스택 프레임이라 부르는 블럭으로 쌓는다. 쓰레드 종료하면 런타임 스택도 사라진다.
- PC(Program Counter) 레지스터: 쓰레드 마다 쓰레드 내 현재 실행할 스택 프레임을 가리키는 포인터가 생성된다.
- 네이티브 메소드 스택
Execution Engine
- 인터프리터: 바이크 코드를 한줄 씩 실행.
- JIT 컴파일러: 인터프리터 효율을 높이기 위해, 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러로 반복 되는 코드를 모두 네이티브 코드로 바꿔둔다. 그 다음부터 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용한다.
- GC(Garbage Collector): 더이상 참조되지 않는 객체를 모아서 정리한다.
참고 :
- Java의 다양한 코드 조작방법(백기선 인프런 강의 내용)
JDK와 JRE의 차이
- JRE (Java Runtime Environment): JVM + 라이브러리
- 자바 애플리케이션을 실행할 수 있도록 구성된 배포판.
- JVM과 핵심 라이브러리 및 자바 런타임 환경에서 사용하는 프로퍼티 세팅이나 리소스 파일을 가지고 있다.
- 개발 관련 도구는 포함하지 않는다. (그건 JDK에서 제공)
- JDK (Java Development Kit): JRE + 개발 툴
- JRE+개발에필요할툴
- 소스 코드를 작성할 때 사용하는 자바 언어는 플랫폼에 독립적.
- 오라클은 자바 11부터는 JDK만 제공하며 JRE를 따로 제공하지 않는다.
- Write Once Run Anywhere(WORA)
정리하고 나서의 느낀점
정리 시간은 1주일 데드라인이다. 이미 끝난 스터디이긴하지만 녹화를 보면서 공부하고있다.
과제를 하고 녹화된 영상을 보는데 다들 엄청 열심히 준비하셨던거 같다.
나도 꼼꼼히 열심히해서 같이 공부는 아니지만 최대한 스터디하는 느낌으로 다 정리 못해도 정리하는데까지 하려고한다.
나도 조금씩 스터디를 통해서 하닜