정의
자바 가상 머신
특징
1.
OS에 종속적이지 않다(CPU가 JAVA를 인식해서 사용한다).
2.
JAVA Source를 기계어로 컴파일 해주어 CPU가 *.JAVA를 인식할 수 있게 해준다.
여기서 JAVA Compiler는 JDK를 설치하면 bin에 존재하는 javac.exe
javac를 이용하여 *.java를 *.class로 컴파일 가능 | javac *.java |
bytecode는 기계어가 아니라서 OS에서 실행되지 않는다. 또 다시 JVM이 이를 해석해서 기계어로 변환
JVM을 구동시키는 명령프로그램은 JDK의 /bin에 존재하는 java.exe
바이트 코드란?
정의
1.
JVM이 해석할 수 있는 언어 ⇒ java bytecode
2.
명령어의 크기가 1바이트
특징
1.
자바컴파일러에 의하여 바이너리 코드(1, 0으로 구성된 이진코드)로 변환
CPU가 이해하는 언어 = 바이너리 코드
바이너리 코드를 만드는 녀석 = JVM
JVM이 알아먹는 언어 = 바이트 코드
바이트코를 알아먹는 녀석 = JAVAC (JAVA Compiler)
JIT란?
정의
동적 번역
특징
인터프린터로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 기계어로 변경 더 이상 인터프린팅하지 않는다. 기계어를 직접 실행
1.
인터프린터 방식
•
실행 중 언어를 읽어가며 해당기능에 대응하는 기계어 코드를 실행
•
최적화 과정이 없이 순차적으로 읽기 때문에 성능이 낮다.
2.
정적 컴파일
•
실행하기 전 미리 기계어로 변환
•
컴파일 과정이 필요해서 다양한 플랫폼에 맞추려면 시간이 걸린다.
한번 컴파일 된 기계어는 캐시에 보관하여 두번 컴파일되지 않는다.
만약 한번만 사용하는 코드라면 컴파일 하지 않는 인터프린팅이 유리 따라서 JIT는 메서드사용의 빈도와 일정 정도를 넣을 때 컴파일을 수행
JVM의 구성요소?
•
클래스 로더
•
실행 엔진
1) 인터프린터
2) JIT 컴파일러
3) 가비지 컬렉터
•
런타임 데이터 영역
클래스로더?
정의
JVM내로 클래스파일 *.class를 로드, 링크를 통하여 배치하는 작업을 수행하는 모듈
클래스를 처음으로 참조할 때 , 해당 클래스를 로드하고 링크하는 역할
여기서 말하는 로딩 기능은 컴파일 타임이 아닌 클래스를 처음 참조할 때 즉, 런타임에 클래스 파일을 로딩, 연결, 초기화 작업이 이루어진다.
Loading의 상속관계/계층구조
Application classLoader extends Extension ClassLoader
Extension ClassLoader extends Bootstrap ClassLoader
클래스로더(클래스 로더 서브 시스템) 구성 : Loading 과정
•
Loading : 아래 세가지 클래스에 의하여 클래스가 로드된다. 세가지는 상속관계로 정의되어 있으며 Delegate(위임) 방식으로 작업 진행
Class Loader의 가시제약 조건
일종의 범위룰이 적용되어 부모로더에서 찾지 못한 class는 자식로더에서 찾지 못한다(이는 Spring-boot에서도 찾아 볼 수 있는 Delegate방식인 것 같다. 확실하지는 않지만 Default Bean로드 과정에서 위와 비슷한 방식을 찾아볼 수 있다).
Unload 불가
JVM상에 로드된 class는 지우는게 불가능
클래스로더(클래스 로더 서브 시스템) 구성 : Linking 과정
•
연결(Linking)과정은 총 3가지로 구성되며 연결이 완료된다면 초기화과정이 이루어진다.
클래스로더(클래스 로러 서브 시스템) 구성 : initialize과정
•
클래스 로딩의 마지막 단계 모든 정적변수가 자바 바이트 코드에 명시된 값으로 초기화, 모든 정적 블록 실행
실행 데이터 영역(Runtime Data Area)
주로 5가지 영역으로 구분할 수 있다.
1.
메서드 영역
2.
힙 영역
3.
스택 영역
4.
PC 레지스터
5.
네이티브 메서드 스택 영역
1.
메서드 영역
•
모든 클래스 수준의 데이터가 저장된다.
•
공유자원이며 JVM당 하나의 영역만 존재
•
이명으로 Class Area, Code Area, Static Area
•
메서드 영역 저장 목록
◦
타입정보
Type 전체이름, 직계 하위 클래스 이름, Type의 클래스 인터페이스 여부, Type의 수정자, 연관 인터페이스 이름
◦
런타임 상수 풀
Type의 모든 상수 정보, (Type Field, Method)등 모든 심볼릭 레퍼런스(참조하는 대상 이름)
◦
필드 정보
Field Type, Field수정자(접근제어자)
◦
메서드 정보
메서드 이름, 리턴, 파라미터 수, 메서드 수정자, 메서드 구현, 메서드 바이트 코드, 메서드 스택 프레임의 작업공간 및 지역변수 공간 크기, 예외 테이블
◦
클래스 변수
Class 변수는 static 키워드로 선언된 변수 의미
인스턴스가 없어도 공유, 클래스 소속 변수, 클래스 사용 이전에 메모리 할당
final class 변수는 상수로 치환되어 Runtime constant pool 값을 복사
static 변수는 해당 영역에 저장, 기본형이 아닌 static 클래스형 변수는 레퍼런스 변수만 저장 되고 실제 인스턴스는 heap에 저장
b.
힙 영역
•
JVM이 관리하는 프로그램에서 데이터를 저장하기 위해 동적으로 할당하여 사용하는 공간
•
new 키워드로 생성된 객체, 인스턴스와 배열 저장
•
힙에 생성된 객체, 배열은 스택영역의 변수나 다른 객체의 필드에서 참조 (스택 영역의 변수, 다른 객체의 필드)
•
참조하는 변수, 필드가 없으면 GC의 대상이 되는 영역
•
힙 영역의 분류
JAVA 8, Permanent Area(PermGen)은 어디로?
JAVA7에서 JAVA8로 넘어 오면서 JVM Hotspot의 PermGen영역은 사라졌다.
String과 Static변수들은 모두 PermGen영역에서 JAVA Heap 영역으로 위치가 변경 되어서 Garbage Collector의 대상이 되고, 클래스의 메타정보들은 Metaspace영역으로 옮겨갔다.
Metaspace는 Native Memory(off-heap) 공간으로 옮겨졌고, 기존에 PermGen 처럼 정적인 공간(사용자 설정 가능)으로 생겼는데 기본적으로 작게 할당이 되어 자주 out-of-memory 문제가 생긴 것을 해결할 수 있었다.
요약
1. permGen : 클래스 메타데이터를 읽고 해당 메타 데이터를 통해 객체를 생성하던 곳
(자바의 Heap이라곤 하지만 조금 속성이 다른 느낌, 기존에는 클래스 메타데이터 뿐만 아니라 static 변수와 상수 정보들이 저장, 그럼 이때는 GC대상이 아니였단 거임)
2. OS, JVM Version 마다 각기 다른 default 값이라 out-of-memory가 자주 발생
3. Java8부터 permGen을 없애고 Metaspace로 대체
4. Metaspace는 클래스 메타데이터를 native메모리에 저장하고 부족하면 동적으로 늘려서 사용
5. 기존 perm에 존재하던 static object는 heap으로 옮겨져 최대한 GC의 대상이 되도록함
결론
heap에 있던 Perm을 Native 영역으로 빼고 이름은 Metaspace 즉, 클래스 메타정보만 담음
c.
스택 영역
•
쓰레드가 생성될때 메모리 영역이 같이 생성되며, 쓰레드가 종료되면 메모리에서 해제됨 (용량이 작다.)
•
메서드를 호출할때 메서드 Stack Frame이 저장되는 영역(Stack Frame 내부에는 지역변수, 인자값, 리턴값 저장)
•
스택 사용 과정
1) Thread 하나당 하나의 Stack Frame 존재
2) 메서드 호출 시 Stack Frame을 하나 생성하고 push
3) Stack Frame 내부의 스택에는 메서드와 관련된 지역변수, 매개변수 등을 스택에 저장
4) 메서드 호출 완료시에 완전 pop되며 소멸
•
저장 목록
◦
기본 자료형
기본 자료형 변수의 데이터는 Stack 영역에 저장
◦
참조 자료형
실제 “값”이 아닌 “주소”를 기억한다. 그래서 메서드 내부에서 참조 자료형의 값을 변경하면 주소가 바뀌는 것. 그래서 실제 “값”은 heap에 존재한다.
iv.
PC 레지스터
•
쓰레드가 하나 생길때 생기는 것으로 각 스레드 마다 존재
•
PC레지스터는 현재 실행중인 JVM의 명령 주소(Program Counter를 갖는 영역)
•
CPU의 레지스터와는 별개로 Stack 내에서 작동, NAtive Stack을 실행할때는 사용되지 않는다.
•
쓰레드가 어떤 부분을 어떤 명령어로 실행해야 할지에 대한 기록을 하는 부분으로 현재 실행 중인 JVM 명령어의 주소를 갖는다.
v.
네이티브 스택
•
JAVA 이외의 언어로 만들어진 코드를 위한 Stack
•
JNI(JAVA Native Interface)를 통하여 호출되는 C/C++ 등의 코드를 수행하기 위한 Stack
•
JVM 내부에 영향을 주지 않기 위하여 따로 메모리 공간 활용
프로그램의 실행은 CPU에서 명령어,즉 instruction을 수행하는 과정으로 이루어진다. CPU는 이러한 Instruction을 수행하는 동안 필요한 정보를 레지스터라고 하는 CPU내의 기억장치를 사용한다.1+2라는 연산을 수행하는 경우 이것을 최소단위로 쪼개보면 연산의 대상이 되는 1과 2도 있을 것이고 더하라는 연산도 있을 것이다. 연산을 하나씩 수행해 보면 먼저 1이라는 값을 받고 다시 2라는 값을 받은 후 이 두 숫자를 더한 결과값을 내는 과정으로 진행될 것이다. 그런데 1을 받고 2를 받을 때 먼저 받아 놓은 1이라는 상수는 2를 받는 행위를 할 동안 이 값들은 CPU에 잠시 기억된다.
1과 2처럼 명령 실행에 사용되는 데이터를 Operand라고 하며, 더하라는 연산 명령과 같은 add Instruction도 존재한다. CPU는 이것도 미리 기억을 하고 있었어야 한다. 그리고 이 연산의 결과인 3이라는 Operand도 메모리로 전달하기 전에 CPU 어딘가에 잠시 머무르도록 해야 한다.
이때 사용하는 CPU내의 기억장치를 레지스터라 한다.
하지만 Runtime Data Area의 메모리 영역인 PC Register는 이것과는 다르다. Java는 Register-Base로 구동되는 방식이 아니라 Stack-Base로 작동한다. JVM은 CPU에 직접 Instruction을 수행하지 않고 Stack에서 Operand를 뽑아 내어 이를 별도의 메모리 공간에 저장하는 방식을 취하고 있는데 이것이 PC Register이다.