안녕하세요!
오늘은 JVM 구조? 자바 애플리케이션 실행 과정? 컴파일 과정? 이전 시간에 이어 GC에 대해 알아보려 합니다!
이전 시간에 Garbage Collector가 Heap 메모리에 올라온 데이터의 생명을 관리한다고 했었죠!
이번 시간에는,
('왜 필요한지?' -> 참조)
기본 동작 개념은 뭔지!
실제 동작 과정은 어떤지!
마지막으로 JDK 7, 8이 GC 관련해서 어떤 차이가 있었는지!
알아볼겁니다:) 잘 따라오세요 가시죠!
1. Garbage Collector란?
- 정의: Garbage Collector는 힙 공간에서 메모리의 수명 관리를 해주는 주체다.
Garbage Collector의 메모리 수명 관리 판단 기준은 참조 유무입니다. 현재 프로세스 내의 변수가 객체를 참조하고 있으면 사용중이라고 판단합니다. 만약 객체를 아무 변수도 참조하고 있지 않으면 객체는 미사용으로 판단되죠. 참조되지 않은 객체는 사실상 다시 찾을 수도 없기 때문에 쓸모 없는 객체로 인식되는거죠. 이러한 메모리들을 지우는 방법으로 Garbage Collector(GC)는 힙 공간을 관리합니다.
여러분이 이 글을 전부 본 후엔 [그림1] 전체 GC 동작 과정을 모두 이해하게 될겁니다! JDK7,8 차이도요!
2. 기본 동작 개념?
Step 1: Marking
마킹은 어떤 메모리 조각이 사용중인지 사용중이지 않은지 판단하는 작업입니다. 마치 우리가 마커로 체크!하는 것과 비슷하죠:) 마킹 후에는 참조되고 있는 즉 사용중인 오브젝트는 파랑색으로 표시됩니다. 참조가 안되고 있는 다시 말하면 미사용중인 오브젝트는 주황색으로 표시되죠. 마킹 작업을 하려면 모든 오브젝트를 돌면서 체킹해야합니다! 그렇죠. 모든 오브젝트를 도는 것은 매우 성능을 잡아 먹습니다:(
Step 2: Normal Deletion
Normal Deletion은 참조되지 않은 객체들을 제거하는 작업입니다. 제거 후 비어 있는 공간의 포인터는 따로 저장하죠. 왜일까요? 이는 추후 Memory Allocator가 메모리 할당할 곳을 알아야하기 때문입니다. 빈 곳을 알아야 어디에 할당할지 결정할 수 있겠죠?
그치만 여러분 아래 그림3은 너무 비효율적이지 않아보이나요? 저 상태면 파란색 객체 사이 공간보다 큰 객체는 할당할 수 없습니다. 그렇죠. 메모리가 비효율적으로 사용됩니다! 스텝 3가 뭐일지 유추 되시나요?:)
Step3: Deletion with Compacting
더 나은 성능을 위해 삭제 후에 남아 있는 참조 되고 있는 객체들을 compact를 합니다. 이로서 두가지 장점과 단점이 생깁니다.
장점
- 비어있는 메모리가 효율적으로 사용됩니다. 비어 있는 공간 포인터에 객체를 할당 못하는 경우가 많이 줄었기 때문이죠.
- Memory Allocator는 포인터를 이제 여러개가 아닌 1개만 저장 하면 됩니다.(그림3은 포인터 4개, 그림4는 포인터 1개)
단점
- 참조 되고 있는 객체들을 compact 시키는 과정 또한 performance를 잡아 먹습니다. 공간 이익을 보기 위해 시간 이익을 줄인거죠!
여기까지가 Garbage Collecting의 기본 동작 개념입니다. GC는 메모리를 훓으며 Marking하고, 참조되지 않은 곳은 지우고, 참조된 곳을 compact 시켜서 메모리를 관리하죠. 하지만 이는 치명적인 단점이 있습니다! 계속 빨간색으로 표시했듯이 성능이 안좋습니다ㅠㅠ. 만약 모든 힙 메모리를 저렇게 검사한다고 생각해보세요! 심지어 힙 메모리를 검사할 때는 동기화 문제로 인해 프로세스의 작업은 all stop 되야합니다..
THINK!
All stop되는 기간이 길면 안되겠죠?
어떻게 하면 All stop되는 기간을 줄일 수 있을까요?
검사하는 객체 수를 줄이면 될수도 있지 않을까요?
그렇다면 검사하는 객체 수의 기준은 어떻게 알 수 있을까요?
검사하는 객체의 기준은 경험적 근거로 알 수 있습니다!
아래 그림 5는 X축: 시간과 Y축: 살아남은 객체 수를 보여줍니다. 이를 통해 극 소수의 객체만 오래 살아남고 나머지는 금방 죽는 것을 알 수 있어요.(정확한 원문은 아래 글입니다.) 그렇다면 일찍 죽는 것과 늦게 죽는 것을 따로 관리하면 되겠네요! 검사하는 객체의 기준은 수명으로 정하면 될 것 같습니다. 이제 JVM Generation을 알아보도록 하죠!
참조1
Here is an example of such data. The Y axis shows the number of bytes allocated and the X access shows the number of bytes allocated over time.
As you can see, fewer and fewer objects remain allocated over time. In fact most objects have a very short life as shown by the higher values on the left side of the graph. 출처: Oracle
3. 실제 동작 과정
GC는 Generation 기능을 사용해서 임무를 수행합니다. JVM은 Generation에 따라 객체들을 관리하기 위해 힙 공간 내
1) Hotspot heap 구조?
위에서 검사하는 객체의 기준은 '객체의 수명'이 된다고 했습니다. JVM의 Heap은 객체의 수명에 따라 세 공간으로 나뉘어집니다.
Young Generation: 새롭게 생성되는 객체가 할당되는 위치이며 여기서 aging도 발생합니다. young generation이 특정 조건 정도로 차게 되면(eden이 차면 발생) Minor garbage collection이 일어납니다. 이후 특정 기간 이상 살아남은 객체들은 Old Generation으로 옮겨지게 되죠.
- Stop the World Event: Minor garbage collection은 Stop the world event입니다! GC가 일어나면 마킹하는 동안 마킹된 대상의 참조 상태를 보장해야하기 때문에 모든 스레드가 멈춰야하기 때문입니다. 그렇죠! 동기화가 되야합니다:)
Old Generation: Old Generation은 오래 살아 남는 객체들이 저장되는 곳입니다. 오래 살아남는 만큼 해당 공간은 GC를 덜해도 되겠죠? Young generation에서 일정 기간 이상 살아남은 객체들이 Old Generation으로 옮겨지게 됩니다. Old Generation에서 일어나는 GC는 Major GC라고 합니다 Major garbage collection은 Old 뿐 아니라 Yound generation도 마킹합니다. 즉 시간이 오래걸린다는 뜻이죠! 참고로 Major GC도 Stop the world event입니다.
(JDK 7 기준) Permanent Generation: 클래스 및 메소드 등의 메타데이터가 저장되는 곳입니다. (참조: stackoverflow) JVM에 의해 런타임에 공간이 사용되죠. Java SE 라이브러리, 클래스 메소드 등의 데이터가 메타데이터가 됩니다. 클래스가 더이상 필요 없다고 판단되거나 다른 클래스의 공간이 필요하다면 JVM은 Permanent Generation 또한 GC의 대상으로 선정합니다.
- What's new in jdk8에 의하면 'Removal of PermGen'이라고 합니다. 즉 Permanent Generation이 8부터는 없어진거죠!
- 왜 없어졌을까?
- Permanent Generation은 메모리 공간이 작아서 개발자가 Memory size tunning을 해야합니다. 안하면 Out Of Memory Execution 에러가 뜨게되죠. 개발자들은 이러한 일을 줄이고 싶어한거죠.
참조2
The biggest disadvantage of PermGen is that it contains a limited size which leads to an OutOfMemoryError. The default size of PermGen memory is 64 MB on 32-bit JVM and 82 MB on the 64-bit version. Due to this, JVM had to change the size of this memory by frequently performing Garbage collection which is a costly operation. Java also allows to manually change the size of the PermGen memory. However, the PermGen space cannot be made to auto increase. So, it is difficult to tune it. And also, the garbage collector is not efficient enough to clean the memory. 출처: geeksforgeeks
- 뭘로 대체됬을까?
- Permanent Generation은 Metaspace로 변경됬습니다. 는 native area에 속하여 메모리 크기가 OS에 의해 동적으로 조정됩니다. (참조: stackoverflow) 메모리가 더 필요하다고 판단되면 OS가 자동으로 확장시켜주기 때문에 개발자가 신경쓸 필요가 많이 줄게된거죠!
- Metaspace는 뭔가요?
- 클래스와 메소드의 메타 데이터를 저장하는 Permanent Generation의 역할을 하며, 사용 가능한 공간의 크기는 OS에 의해 동적으로 조절된다.
- 클래스와 메소드의 메타 데이터는 Method Area에서 저장하지 않나? 라고 생각하실 수 있습니다. Method Area가 Permanent Generatiom의 한 부분이었기 때문에 Method Area 기능하는 부분이 힙에서 native area로 떨어져나왔다고 보면 됩니다. (참조3)
- Java spec에 의하면 Metaspace가 Heap에서 떨어져 나왔지만 여전히 논리적으로는 힙 공간의 한 부분이라고합니다. 메모리 공간의 확장성을 위해 물리적으로만 떨어뜨려놨기 때문이겠죠?ㅎㅎ 따라서 Metaspace는 여전히 GC의 대상이 될 수 있고, GC 여부는 JVM implementation에 달렸다고 합니다. (참조4, 5)
참조3
Method Area is a part of space in the PermGen and it is used to store the class structure and the code for methods and constructors. 출처: geeksforgeeks
참조4
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. 출처: java.spec
참조5
MetaSpace grows automatically by default. Here, the garbage collection is automatically triggered when the class metadata usage reaches its maximum metaspace size. 출처: geeksforgeeks
2) 동작 과정
Step1: 애플리케이션이 시작후 새롭게 할당되는 모든 객체는 Eden으로 가게됩니다. Eden은 낙원의 은유적 표현으로 쓰인다고 합니다. 마치 객체에게 생명이 주어지면(=메모리 할당, 인스턴스화), 낙원으로 가게된다고 생각할 수 있죠 ㅎㅎ 낙원이 다 차게되면 어떻게하죠? 비워야겠죠! 사용되는 것과 사용되지 않는 것을 구분해서 비워봅시다! 그리고 처음 애플리케이션 시작엔 survivor space 0(왼쪽), 1(오른쪽)은 비어있습니다!
에덴 뜻
에덴은 낙원을 가리키는 가장 유명한 말 중 하나가 되었으며, 종종 낙원의 은유적 표현으로 사용되기도 한다. 다만 실제 가톨릭이나 개신교에선 보통 에덴이라고 따로 부르지 않고 에덴 '동산'이라고 부른다. 출처: 나무위키
Step2: 만약 Eden이 다 차게되면 Minor Garbage Collecting이 작동합니다. Marking, 참조되지 않는 객체의 삭제 및 참조되고 있는 객체의 compact가 일어납니다. 이때 참조되는 객체는 Survivor space 0로 compact되죠. 그리고 각 객체들은 자신이 살아남은 수명을 표시합니다! 여기선 1이 되겠죠 ㅎㅎ
Step3: 그 다음 Minor GC에서는 Survivor space 1이 aged 공간으로 사용됩니다. Eden과 Survivor space 0이 검사 공간이 되죠. 검사 대상과 aged 공간만 바뀌지 전체 과정은 step1,2와 같습니다:) survivor space의 용도가 매번 바뀐다고 생각할 수 있겠군요!
검사 대상 (from) | aged 공간 (to) | |
Garbage collected cycle 1 | Edem, Survivor space 0 | Survivor space 1 |
Garbage collected cycle 2 | Edem, Survivor space 1 | Survivor space 0 |
Garbage collected cycle 3 | Edem, Survivor space 0 | Survivor space 1 |
... | ... | ... |
Garbage collected cycle n | Edem, Survivor space 0 or 1 | Survivor space 0 or 1 |
Step4: Survivor 공간에 있는 객체 중에 일정 cycle 이상 살아남은 객체는 Tenured 공간(=Old Generation)으로 갑니다. Tenure 뜻이 거주권이므로, 해당 공간에 온 객체들은 앞으로 오래 살아남을 애들입니다. 여기서는 일정 cycle이 9로 설정되었습니다.
Step5: Tenured 공간에서 일어나는 Garbage Collection을 Major garbage collection이라고 부른다. Major GC는 Young & Old generation 모두 GC 하기 때문에 시간이 오래 걸립니다. 즉 Stop the world가 오래동안 된다는 뜻이겠죠? Responsive 애플리케이션은 이러한 pause기간을 줄여야합니다. Garbage collector의 종류에 따라 stop the world의 기간이 줄어들기 때문에 GC 튜닝이 필요합니다!
Tenure 뜻
(주택·토지의) 거주권[사용권] 출처: 네이버 지식백과
우리가 Garbage collection을 하는 이유는 뭘까요?
바로 애플리케이션의 성능을 개선시키기 위해서입니다.
애플리케이션의 성능은 어떻게 개선시킬까요? 기준이 있나요?
4. 애플리케이션 성능 판단 기준
- Responsiveness(반응성): 반응성이란 요청된 데이터를 얼마나 빠르게 처리할 수 있나를 설명합니다. 반응성이 좋은 애플리케이션을 목표로 하려면 pause time이 길면 안됩니다. 따라서 사소하게는 minor gc, 주요하게는 Major gc의 stop the world event 시간을 줄여야죠!
- UI가 얼마나 빨리 event에 대응하나?
- 웹사이트가 얼마나 빨리 보여져지나?
- DB query가 얼마나 빨리 return하나?
- Throughput(처리량): '애플리케이션이 처리한 일 양/단위 시간'을 처리량이라고 합니다. 처리량의 관점에서는 특정 pause time이 긴 것은 문제가 안됩니다. 전체 처리량의 증가가 주 관심사기 때문이죠. 당연히 pause time이 작아야하는 것도 중요 관심사가 아닙니다.
- 주어진 시간 동안 일어난 트랜색션 갯수
- 한시간동안 batch 프로그램이 실행할 수 있는 일 갯수
- 한시간 동안 DB query가 completed될 갯수
'Computer launguage > Java' 카테고리의 다른 글
자바 NIO (1) 채널, 버퍼의 동작 과정을 간단한 채팅 서버/클라 애플리케이션 구현을 통해 리서치 (0) | 2024.09.01 |
---|---|
제한된 메모리 크기 환경에서의 LinkedList와 HashMap 튜닝 방법 (0) | 2022.07.03 |
[Deep dive] 부동 소수점, 고정 소수점 표현 방법? 연산 속도? 오차? 돈 계산? (0) | 2022.03.26 |
[Deep dive] JVM 구조? 자바 애플리케이션 실행 과정? 컴파일 과정? (2) | 2022.03.05 |
객체지향 프로그래밍? 장단점? 4대 원칙(추상화, 캡슐화, 상속, 다형성)이란? (0) | 2022.02.25 |