Java Virtual Machine
✅ JVM
- JAVA to be run regardless of OS
- Manage and optimize memory
☑️ complile time
JVM안에서 build하는 시간
- build 🟰 complie
- 자바는 컴파일 언어
- 자바에서는
소스 코드
를 짜면 컴파일을 통해 클래스 파일의바이트 코드
로 만든다. - 컴파일러: 컴파일 하는 도구
💥 error
- 에러 생기면 아예 complile자체가 안 됨(IntelliJ가 잡아줌)
- syntax erroe(오타를 냈음)
- type check error(data type)
protected int male= "Male"
- 접근제어자 오류(private해놓고 자식이 사용함)
☑️ run time
JVM에서 build한 프로그램이 돌아가는 시간
💥 error
- complile, build는 되는데 실행이 안 됨
- 0나누기 오류
- NULL 참조 오류
- 메모리 부족 오류
✅ How JVM is run
JVM을 실행하면
RAM
의heap
위에 올라가게 된다.
JVM을 실행하면RAM
에서 특정 메모리 영역을 할당받아 실행되게 되는구나.
run time data area 위에 힙이 있는 것- Programmer codes java
source code(.java)
- Compiler
JavaC
compilessource code(.java)
intojava byte code(.class)
the code is consisted of Opcode - Transmit compiled
byte code
to class loader - Class loader uses Dynamic Loading to load and link needed classes
and puts it on run time data area, which is JVM memory - class files are interpreted by Execution Engine
- thread synchronization, garbage collection is run
- compile optimization:
- optimize executable code(
java byte code(.class)
) before running - optimize executable code
- optimize executable code(
💡 Class Loader: 호출
- Java code가 컴파일된
.class
파일을 JVM으로 처음 로드하여, 컴퓨터가 JVM의 실행하기 직전 준비 - class loader가 만들어진 클래스 파일을 부른다.
- class loader 실행 후 Runtime Data Area에 저장
- ⭐️ CLASS LOADER단계에서
static 변수
와함수
가 실행된다
☑️ loading
- 클래스 파일을 가져와서 JVM의 메모리에 로드
- Method Area에 저장하는 단계인데,
- 이 중 Static 변수와 초기값을 Method Area에 저장한다.
☑️ linking
클래스 파일을 사용하기 위해 검증/준비/분석
- verifying: 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 구성되어 있는지 검사합니다.
- preparing: 클래스가 필요로 하는 메모리 할당
- resolving 분석, 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경
☑️ initialization
- static 변수와 static block의 실행을 수행
- 클래스 변수들을 적절한 값으로 초기화
- 예를 들어
int
는 0으로,string
값은 null로 초기화
-Xlog:class+init
- ❗️ 초기화 단계는 코드에서 클래스가 처음 사용될 때 실행
💡 Execution Engine: 수행
실제 바이트 코드를 수행
-XX:+PrintCompliation
☑️ JAVA interpreter
자바 코드를 읽어서 프로그램 실행
- 인터프리터: 동시 통역 (0, 1을 컴퓨터 언어로 해석)
compiler 🆚 interpreter
- 공통점: 프로그래밍 언어를 컴퓨터가 실행할 수 있는 기계어로 바꾸기
✔️ compiler
- compile the whole programming code to whole executable file
- 👍🏻 whole executable file can be run fast by computer
- 👎🏻 if source code is changed, need to compile again
✔️ interpreter
- when program is run, read source code line by line and interprete to executable file
- 👎🏻 slow
- 👍🏻 if source code is changed, applied immediately
☑️ Just In Time(JIT) compiler
- 반복문장 메모
- Native code: 반복되는 코드((native)(static) 이라고 뒤에 붙어있음)
- 매번 통역을 하다보면, 반복되는 코드들이 있음. (“안녕하세요” 처럼 계속 나오는 코드)
- 똑같은 코드들을 메모에 저장을 해 두었다가 인터프리터로 또 통역하는게 아니라 복붙해서 씀
- 👍🏻 faster than interpreter
☑️ Garbage Collector
쓰레기 수집 from heap
필요없는 데이터들을 수집해서 버리는 역할
- object is null
- objects created in block, and when the block is finished
parent object is null, the child
- unreachable한 객페들을 주기적으로 없애서 heap사용을 최적화
참조되지 않은 객체들을 탐색 후 삭제 → 삭제된 객체의 메모리 반환 → 힙 메모리 재사용
- stop the world
💡 JVM Runtime Data Area: 저장
JVM이 OS위에서 실행될 때 할당받는 메모리 영역
실제 동작할 때 데이터들이 저장되는 곳
- Method area
- HEAP
- JVM Stack
- PC Register
- Native method stack
☑️ Method Area (Runtime Constant Pool)
when class is loaded
created when JVM starts
saves runtime static, method code, class, final
- class loader가 실행되어 호출하면 바로 여기에 저장되는 것!
나중에 실행 엔진이 여기에 있는 코드를 읽어서 실행을 하는 것
다음과 같은 메타 정보들을 저장- 메소드: 매개변수/접근제어자/이름
- 클래스: 속성/ 메소드/ 상속 정보 등
- 메소드: 매개변수/접근제어자/이름
- class variable 클래스 변수 🟰 정적 필드(static)은 여기에 저장
- 상수(final static)
- 특히, 상수(static)는 runtime 때 만들어지는 것이 아니라 컴파일 때 만들어진다.
- 문자열 Literal 여기에 저장
☑️ HEAP
when run
런타임 시, 동적으로 할당되는 영역으로 인스턴스와 배열 타입 등의 주소 저장
- dynamic: JVM이 관리, GC가 자동으로 관리
- 인스턴스 변수(instance variable)
new
저장 - 객체, 배열 저장
- 힙에 할당된 데이터는 가비지 컬렉터의 대상이 된다.
❗️ String type은 primitive type인지 reference type인지 주의
1
2
3
4
5
6
//primitive type= literal
//constant pool 에 저장
String s1= "cat";
//reference type
//heap에 저장
String s2= new String("cat");
☑️ JVM stack(call stack, execution stack)
when compiled
프로그램 실행 중, 임시로 할당되고 소멸되는 정보 저장
save local variable, paramether
메서드의 작업에 필요한 메모리 공간을 제공
- 예를 들어 메소드가 실행되는 동안 사용되는 지역 변수(local variable)
☑️ PC Register
when new thread is created
how thread should be run
JVM command address
☑️ Native method stack
run the byte code
code that is not Java
✔️ 프레임
어떤 메소드를 실행하게 되면, 그 메소드만을 위한 공간, 즉 프레임이 만들어지게 된다.
즉, 메소드가 수행되는데 필요한 만큼의 메모리를 스택에 할당받는 것이다.
첫 번쨰로 호출된 메서드를 위한 작업 공간이 호출스택의 맨 밑에 마련되고
두 번째로 메소드가 호출되면 첫 번째 메소드의 바로 위에 두 번째 메소드를 위한 공간이 마련된다.
이 때 첫 번째 메소드는 수행을 멈추고, 두 번째 메소드가 수행된다.
따라서 호출스택의 제일 위에 위치하는 메소드가 현재 실행되고 있는 메소드이다.
그리고 그 아래에 있는 메소드가 바로 위의 메서드를 호출한 메서드이다.
나머지는 대기상태로 기다린다.
메소드가 종료되면 프레임도 삭제된다.
프레임이 삭제되며 사용했던 메모리를 반환하고 스택에서 제거된다. 이런 프레임을 관리하는 곳이 바로 JVM stack
프레임이 여러개 쌓여 있어서 이름이 stack
매개 변수, 지역 변수 등이 임시 저장
✅ JAVA 클래스 런타임 때 일어나는 일
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
public class JvmRun {
public static void main(String[] args) {
//primitive type ➡️ stack
int myInt= 5;
//final ➡️constant pool
final long My_LONG= 5L;
//reference type ➡️constant pool
// heap & stack
Customer customer1= new Customer("Kim");
//reference type ➡️ heap & stack
int[] intArr= new int[5];
//문자열 Literal ➡️ constant pool
String str1= "Hello World";
//reference type ➡️ heap
String str2= new String("Hello World");
customer1= null;
//method ➡️ method영역, 프레임
myMethod(myInt);
}
static void myMethod(int param){
int myInt2= param;
//instance ➡️constant pool
Customer customer2= new Customer("Lee");
}
}
1️⃣ 가장 먼저 Constant Pool에 compile하자마자 넣어 둔다.
final long My_LONG= 5L;
Customer customer1= new Customer("Kim");
String str1= "Hello World";
Customer customer2= new Customer("Lee");
2️⃣ stack에 추가 시작
int myInt= 5;
final long My_LONG= 5L;
이미 Constant Pool애 았으므로 Constant Pool에서 가져온다
3️⃣ new
Customer customer1= new Customer("Kim");
customer는 reference type
heap에 만들어지고
stack에 변수가 heap을 가리키고 있다.
4️⃣ array
int[] intArr= new int[5];
마찬가지고 intArr을 위한 메모리 장소가 heap에 마련되고
stack에는 그 메모리 주소를 가리키는 변수
5️⃣ 문자열 literal
String str1= "Hello World";
stack에 변수 추가되고, constant pool에 이미 있는 “Hello World”을 가리키고 있다.
6️⃣ instance
String str2= new String("Hello World");
새롭게 instance가 heap에 추가되었고
그 주소는 아까 constant pool에 이미 있는 “Hello World”을 바라보고 있다.
7️⃣ 아까 선언된 인스턴스 값을 바꾸면?
customer1= null;
customer은 Kim 가리키고 있었음
이재 바꿨으니
customer의 메모리 주소가 null
8️⃣ myMethod 메소드
메소드를 실행하게 되면 메소드 프레임이 생긴다.
⭐️ heap에는 클래스와 함께 인스턴스 변수들이 저장되어 있음.
⭐️ method는 메소드 영역에 메타 정보로 저장됨
myMethod(myInt);
메소드 프레임 안에서 parameter 해석
param 받았으니
int myInt2= param;
에 값 넣기
메소드 안에서 instance를 생성하면
Customer customer2= new Customer("Lee");
메소드 프레임 안에서 이루어지는데
customer2 instance는 heap의 메모리 공간을 차지하게 됨
customer2는 메모리 주소 가지게 된다.
heap에는 프레임이라는 개념은 없음 ❌
9️⃣ myMethod 메소드 종료
메소드 프레임이 삭제된다.
그러나 main메소드는 계속 운영되고 있으니 나머지 stack은 유지되고 있음
그리고 아까 instance 선언했다가 null로 바꾼 customer1은 heap에 메모리 차지하고 있음, 🗑️ Garbage collector 등장! 🗑️
그냥 customer1을 가리키는 변수가 stack에 없을 뿐.
마찬가지로 customer2도 메모리에 주소가 있으나
메소드가 종료되었기 때문에 customer2를 가리키는 변수는 stack의 프레임에 없음 🗑️ Garbage collector 등장! 🗑️
🔟 메인 메소드도 종료하면
stack, heap은 깨끗하게 비워짐
그러나 constant pool만 남아 있음
💡 JAVA 가상 메소드 기술
1
2
3
4
5
6
7
8
9
10
Animal animal= new Animal();
Animal bird= new Bird(); //upcast
Animal fish= new Fish();
Animal person= new Person();
feed(animal, "bugs");
feed(animal, "bugs");
feed(bird, "cherry");
feed(fish, "shrimp");
feed(person, "rice");
메소드는 한 번만 메소드영역에 저장되고 끝
(메소드 feed를 여러번 호출했다고 여러번 저장되는 것은 아님)
인스턴스 type을 Animal로 설정했어도
원래는 Bird()라면(upcast) 실제 runtime때 일어나는 것은 Bird의 메소드가 실행된다.
🗑️ Garbage collector
- 메소드가 종료되어 heap에는 아직 instance 메모리가 할당되어 있지만 그 메모리 주소를 바라보는 변수는 없는 상태
- 위 코드에서 myMethod 메소드 종료 후
Customer customer2= new Customer("Lee");
와 같은 경우임 - 이런 경우 Garbage collector가 heap에서 배정된 메모리 지워버린다. 그래서 Heap을 비움
System.gc()
이 코드 실행 후 또 customer2이 쓰이지 않으면 Garbage collector가 heap에서 지워버림
- 👍🏻 Developer does not have to manage memory
- 👍🏻 메모리 관리, 효율성
- 👎🏻 When GC operates, stops all other process, overhead
- 👎🏻 difficult to know when GC will start
☑️ Garbage collector의 대상 판별 방법
✔️ reachable: 객체가 참조되고 있는 상태
객체가 heap위에 메모리 할당되어 올라가 있는가? ⭕️
그 객체를 가리키는 변수가 있는가? ❌
✔️ unreachable: 객체가 참조되고 있지 않은 상태
객체가 heap위에 메모리 할당되어 올라가 있는가? ⭕️
그 객체를 가리키는 변수가 있는가? ⭕️
🗑️ Garbage collector 출동
☑️ Garbage collector 청소 방법
✔️ Mark
어떤 객체가 reachable한지 아니면 unreachable한지 파악해서 치울 객체 마킹
✔️ Sweep
unreachable 이라고 마킹한 객체 쓸어 없애기
✔️ Compaction
중간중간에 치워서 빈 공간 생기면 남아있는 reachable한 객체 모아모아서 공간 확보 ➡️ 메모리 최적화
☑️ Garbage collector 청소 종류
사실 JAVA GC는 자동으로 수행되고 있고
직접 GC를 호출하는 것은 아주 무거운 작업이라 실행할 때 주의가 필요하다.
✔️ Minor GC
based on weak generation hypothesis newly created objects are soon useless, reference from old to new object occurs rarely
young generation
새로운 객체들이 할당되는 영역
여기에서 여러 차례 살아남으면 old generation으로 옮겨진다.
young generation은 자주 청소를 한다.
자주 생기고 사라지니까.
영역도 major GC보다는 작은 편
✔️ Major GC
old generation
여러번 사용된 객체들이 할당되는 영역
Minor GC에 비해 덜 자주 청소
✔️ Stop-the-World:
Major GC 청소하는 동안 모든 프로그램 멈춰!
Major GC가 일어날 때 Java의 Thread 동작이 일시정지하여
짧은시간 Java 애플리케이션이 느려지게 되는 ”STOP_THE_WORLD” 현상이 일어나기도 한다.
☑️ Garbage Collection Algorithm
✔️ Serial GC:
- 하나의 스레드만 사용하는 단일 프로세스 GC알고리즘
✔️ Parallel GC:
- 여러 스레드 사용하는 멀티 프로세스 GC알고리즘
✔️ CMS GC:
- Concurrent Mark Sweep
- 다수 스레드 사용
- 가비지 수집 작업 병행적으로 수행
✔️ G1 GC:
- Garbage First
CMS GC의 대안, 메모리의 사용 상황을 고려해 가비지 수집
- 👎🏻 공간 부족 상태를 조심해야 한다.
- minor GC, major GC수행 후에도 여유 공간이 부족하다면
- 이떄는 full GC수행, fullGC는 single thread로 동작
✅ JVM Tuning
1️⃣ 애플리케이션 분석
- analyze application resource
2️⃣ 메모리 튜닝
- JVM memory optimization
- GC settings
3️⃣ 성능 모니터링
- monitor application JVM
4️⃣ 튜닝 평가
- evaluate JVM tuning results
5️⃣ 수정 반복
- If result is satisfying, finish
- if not, repeat from process 2