https://huelet.tistory.com/entry/JVM-%EB%A9%94%EB%AA%A8%EB%A6%AC%EA%B5%AC%EC%A1%B0 에서 참고했습니다.
메모리는 프로그램을 실행하기 위한 데이터, 명령어가 저장되는 공간이다.
한정된 공간이기 때문에 효율적인 관리가 중요하다.
메모리가 부족하게 되면 성능 문제 뿐만 아니라 프로그램 자체가 뻗어 버릴지도 모른다.
프로그램이 실행되려면 OS가 제어하는 RAM(=memory)이 필요한데 JAVA 이전의 언어들은 OS에 종속적이다.
JAVA에서는 이런 OS 의존성을 없앴는데 그 역할을 해 주는 것이 JVM이다.
JVM이 각 OS로부터 메모리 권한을 할당 받아 프로그램을 실행해 준다.
즉, JAVA는 OS로부터는 독립적이지만 JVM에 종속적이고..
중간에 JVM이 있으니 c 언어로 짜여진 프로그램보단 느리다고 볼 수 있다.
JVM (Java Virtual Machine)
JVM은 OS로부터 메모리(=RAM)을 할당 받는데, 용도에 따라 여러 영역으로 나눠 관리한다.
Java source를 JAVA Compiler가 Class files로 만들어주고, 그 classes 파일들이 Class Loader를 통해 로딩 된다.
로딩 된 데이터 들은 Runtime Data Area에서 관리되고,
Execution Engine을 통해 byte code가 실행되면서 프로그램이 돌아간다.
Runtime Data Area
- Class 영역 (or method 영역)
-- 클래스 정보, 타입정보, 변수 정보, 메소드 정보, static 변수, constant pool 등이 저장되는 공간
- Stack
-- Last In First Out (최근에 들어온 녀석 먼저 나감)
-- method 호출때마다 각각의 Stack frame이 생성된다.
-- method 에서 사용되는 매개변수, 지역변수, 리턴 값 및 연산에 사용된다.
-- method 가 끝나면 frame 별로 삭제된다.
- Heap
이녀석은 좀 중요하다.
JVM에서 중요한 GC가 일어나는 영역이다.
-- new 연산자로 생성된 객체(위의 Class Area에 로드된 객체들만 생성가능) 및 배열이 저장된다.
- GC
JAVA에서 참조되지 않은 객체들을 탐색 후 삭제하는 녀석이다.
삭제된 객체의 메모리 반환해서 Heap 메모리를 재사용하게 해준다.
-- Minor GC
--- New 영역에서 일어나는 GC
--- Eden이 꽉차면 GC발생 > Survivor1에 복사 > Survivor1제외 나머지 영역의 객체 삭제 > Eden/Survivor1 메모리 기준치 넘으면 참조되고 있는 객체가 있는지 검사 > 참조중인 객체는 Survivor2에 복사 > Survivor2 제외 다 삭제 > 일정시간이상 참조되는 객체는 Old로 이동 > 반복
-- Major GC(=Full GC)
--- Old영역에 있는 모든 객체를 검사 > 참조 없으면 삭제
--- Minor GC에 비해 시간이 오래걸리고 실행중 프로세스가 정지됨(stop the world)
JDK 1.8 부터는 메모리 구조의 변화가 있는데
Permanent(=class area)가 두개로 나뉘어 져서 Metaspace/Heap으로 분리 되었다.
PermGen 은 자바 7까지 클래스의 메타데이터를 저장하던 영역이었고 Heap 의 일부였다. ??
다른점은
- Static Object가 Heap 영역으로 이동했다.
- 상수화된 String Object가 Heap 영역으로 이동 했다.
즉, Static Object가 GC 대상이 되어 OutOfMemory: PermGen Space error는 더이상 볼 수 없어졌다.
또한 Metaspace는 클래스 메타 데이터를 Native 공간에 저장하고 부족한 경우 자동으로 늘려주어 JVM 옵션을 설정할때의 PermSize, MaxPermSize를 더이상 지정할 필요가 없어졌다.
위에 기재된 GC는 JDK 1.7까지에서 쓰였다.
JDK 1.8 부터는 G1 GC라는 새로운 녀석이 등장했다.
Heap 영역이 최소 4GB정도 되는 머신에서 돌리기 적합하다.
Heap 영역을 여러개의 Region으로 나누고 각 Region들은 Young Generation, Old Generation을 유동적으로 사용한다.
Stop the world가 꽤 길거나 자주 일어나는 프로그램이라면 G1 GC를 고려해 보는게 좋을 것이다.
자세히는 모르겠으니 구글링 해봐야 할 듯.
4G의 Heap 이고, 1024개의 Region으로 쪼개려면 각 Region은 4M.. 아래 값으로 설정하면 된다.
-XX:G1HeapRegionSize
<https://code-factory.tistory.com/48>
https://yaboong.github.io/java/2018/06/09/java-garbage-collection/
String url = "https://";
url += "yaboong.github.io";
덧붙이는 것이 아니라, 연산이 수행된 결과가 새롭게 heap 영역에 할당된다.
기존의 "https://" 라는 문자열을 레퍼런스 하고 있는 변수는 아무것도 없으므로 Unreachable 오브젝트가 된다.
Unreachable Object 란 Stack 에서 도달할 수 없는 Heap 영역의 객체를 말하는데, 지금의 예제에서 "https://" 문자열과 같은 경우가 되겠다.
Garbage Collection 이 일어나면 Unreachable 오브젝트들은 메모리에서 제거된다.
Garbage Collection 과정은 Mark and Sweep 이라고도 한다.
스택의 모든 변수를 스캔하면서 각각 어떤 오브젝트를 레퍼런스 하고 있는지 찾는과정이 Mark.
첫번째 단계인 marking 작업을 위해 모든 스레드는 중단되는데 이를 stop the world 라고 부르기도 한다.
(System.gc() 를 생각없이 호출하면 안되는 이유이기도 하다)
그리고 나서 mark 되어있지 않은 모든 오브젝트들을 힙에서 제거하는 과정이 Sweep 이다.
(Garbage Collection 이라고 하면 garbage 들을 수집할 것 같지만 실제로는 garbage 가 아닌 것을 따로 mark 하고 그 외의 것은 모두 지우는 것이다. )
System.gc()
System.gc() 를 호출하여 명시적으로 가비지 컬렉션이 일어나도록 코드를 삽입할 수 있지만, 모든 스레드가 중단되기 때문에 코드단에서 호출하는 짓은 하면 안된다.
https://d2.naver.com/helloworld/37111
Garbage Collection 튜닝
객체 생성을 줄이는 작업을 먼저 해야 한다.
- String대신 StringBuilder나 StringBuffer를 사용하는 것을 생활화
- 로그를 최대한 적게 쌓도록 하는 것이 좋다
어쩔 수 없는 현실
XML과 JSON 파싱은 메모리를 가장 많이 사용한다.
그냥 현실이 그렇다.
GC 튜닝의 목적 두 가지
- Old 영역으로 넘어가는 객체의 수를 최소화하는 것
- Full GC의 실행 시간을 줄이는 것
구분 | 옵션 | 설명 |
힙(heap) 영역 크기 | -Xms | JVM 시작 시 힙 영역 크기 |
-Xmx | 최대 힙 영역 크기 | |
New 영역의 크기 | -XX:NewRatio | New영역과 Old 영역의 비율 |
-XX:NewSize | New영역의 크기 | |
-XX:SurvivorRatio | Eden 영역과 Survivor 영역의 비율 |
CMS GC
CMS GC 적용을 위한 JVM 옵션 : -XX:+UseConcMarkSweepGC
STW(Stop-The-World) 시간을 최소화 하는데 초점을 맞춘 GC 방식이다.
GC 대상을 최대한 자세히 파악
파악하는 과정이 복잡한 여러단계로 수행되기 때문에 다른 GC 대비 CPU 사용량이 높다
G1 GC (G1: Garbage First)
G1 GC 적용을 위한 JVM 옵션 : -XX:+UseG1GC
하드웨어가 발전되면서 Java 애플리케이션에 사용할 수 있는 메모리의 크기도 점차 켜저갔다.
큰 힙 메모리에서 짧은 GC 시간을 보장하는데 그 목적을 둔다.
JVM 힙은 2048개의 Region 으로 나뉠 수 있으며, 각 Region의 크기는 1MB ~ 32MB 사이로 지정될 수 있다. (-XX:G1HeapRegionSize 로 설정)
* Default GC:
- Java 7 - Parallel GC
- Java 8 - Parallel GC
- Java 9 - G1 GC
- Java 10 - G1 GC
WeakReferece
메모리 관리를 위하여 바로 GC가 일어나게 구현하는 방법이 있는가?
https://developer88.tistory.com/115
new() --> StringReference
WeakReferece class를 사용 --> weakReference
GC 되는 기준이 다름.
메모리를 정리할 대상인지를 판단할 때, reachability 기준을 따진다.
new()에 의해 생성된 String Reference는 가장 먼저 참조된 루트 객체로부터 사슬이 계속 연결되어 있는가.
아니라면 unreachable로 GC 대상이 된다.
Weak Reference의 경우, 명확하게 gc의 대상이 되어진다.
WeakReference<Student> mWeak = new WeakReference<>(new Student("aaa"));
Student mStudent = mWeak.get();
https://www.baeldung.com/java-memory-leaks
GC는 똑똑하지만 완벽하진 않다.
아무리 좋은 개발자가 만든 어플리케이션이더라도 메모리 부족은 여전히 숨어 있을지 모르는 법.
불필요한 Object를 만드는 경우는 얼마든지 존재한다.
메모리 릭의 잠재적인 원인은 무엇이며 런타임시 어떻게 알아차리며 어떻게 처리하는지를 알아야 한다.
메모리 릭?
더이상 사용되지 않는 Heap에 있는 Object들이 GC에 제거 되지 않을 상황이 지속되면 OutOfMemoryError가 발생한다.
타입
1. static Fields
static variables의 과도한 사용이 원인이 될 수 있다.
가령 큰 사이즈의 static List
public
class
StaticTest {
public
static
List<Double> list =
new
ArrayList<>();
public
void
populateList() {
for
(
int
i =
0
; i <
10000000
; i++) {
list.add(Math.random());
}
Log.info(
"Debug Point 2"
);
}
public
static
void
main(String[] args) {
Log.info(
"Debug Point 1"
);
new
StaticTest().populateList();
Log.info(
"Debug Point 3"
);
}
}
2. Unclosed Resources
스트림을 연다던지, 커넥션을 맺는다던지의 활동을 할 때 조심해야 한다.
자원을 닫지 않으면 GC 대상에서 벗어난다.
try-with-resources block 의 사용을 습관화 하고 finally다 잘 활용해야 한다.
3. Improper equals() and hashCode() Implementations
public
class
Person {
public
String name;
public
Person(String name) {
this
.name = name;
}
}
@Test
public
void
givenMap_whenEqualsAndHashCodeNotOverridden_thenMemoryLeak() {
Map<Person, Integer> map =
new
HashMap<>();
for
(
int
i=
0
; i<
100
; i++) {
map.put(
new
Person(
"jon"
),
1
);
}
Assert.assertFalse(map.size() ==
1
);
}
-->> 제대로 된 것
public
class
Person {
public
String name;
public
Person(String name) {
this
.name = name;
}
@Override
public
boolean
equals(Object o) {
if
(o ==
this
)
return
true
;
if
(!(o
instanceof
Person)) {
return
false
;
}
Person person = (Person) o;
return
person.name.equals(name);
}
@Override
public
int
hashCode() {
int
result =
17
;
result =
31
* result + name.hashCode();
return
result;
}
}
4. Inner Classes That Reference Outer Classes
try
{
threadLocal.set(System.nanoTime());
//... further processing
}
finally
{
threadLocal.remove();
}
'프로그래밍 > JAVA' 카테고리의 다른 글
String, StringBuffer, StringBuilder (0) | 2019.03.19 |
---|---|
Object의 메서드/equals/hashCode/clone (0) | 2019.03.19 |
ArrayList + Generic 구현하기 (0) | 2019.02.28 |
Java Collections Framework (0) | 2019.02.28 |
Exception (0) | 2019.02.28 |