캐시에 대해서 검색을 하다가.. 캐시가 어디에 저장되는 것이 문득 궁금해졌다.
캐시는 JVM Heap 메모리에 저장할 수 있다고 한다.
JVM Heap 메모리는 무엇일까?
JVM이 구동되면 JVM은 운영체제에서 할당받은 메모리영역을 메소드영역, 힙 영역, 스택영역으로 구분해서 사용한다고 한다.
메소드영역은
'바이트코드 파일을 읽은 내용이 저장되는 영역'이며 클래스 정보, static 변수, final 상수 등이 저장된다고 한다.
여기서 바이트코드 파일은 소스파일(.java)을 작성한 후에 컴파일하면 나오는 확장자 명이 .class인 바이트코드 파일가 생성된다.
애플리케이션 실행시, 클래스 최초 사용시 (클래스가 처음 로딩될 때) 저장되며, 대부분의 경우는 애플리케이션 종료시까지 계속 유지된다.
Java 8이후로 Metaspace라는 것을 사용, OS 메모리를 사용하여 동적으로 증가가능하다고 한다.
정적변수나 상수 값에 접근시 매우 빠르게 접근 할 수 있지만 동적으로 생성된 클래스가 많을 경우 Metaspace OOM(out of memory)이 가능하다고 한다.
힙 영역은 객체, 배열이 저장되는 영역이다.
자바 애플리케이션에서 new 키워드로 생성되는 모든 객체(List, Map, DTO, Service)들은 힙 영역에 저장된다.
new ArrayList<>(), newHashMap<>(), new DTO()... 등등
new 키워드로 객체가 생성될 때 동적으로 힙에 저장되고, 해당 객체에 참조가 사라지면 GC(Garbage Collector)가 제거한다.
그리고 메모리의 대부분을 차지한다고 한다.
힙 영역은 대규모 데이터 저장이 가능하지만 객체가 많아지면 GC가 너무 자주 제거업무를 해야해서 CPU사용율이 급증하여 성능이 저하되고, 메모리 누수시에는 OutofMemoryError가 발생할 수 있다.
스택 영역은
지역변수, 메서드를 호출할 때마다 생성되는 프레임, 지역변수(int, double 등), 매개변수(전달되는 값)이 저장되는 영역으로, 임시적으로 사용하는 공간이다.
메서드가 호출될 때 스택 프레임이 생성되고, 메서드 내부에서 선언한 지역변수는 이때 저장된다.
메서드 실행이 종료되면 해당 스택 프레임은 제거(pop)된다.
각 쓰레드마다 개별 스택을 가지며, 너무 깊은 재귀나 대량의 지역변수일 경우에는 StackOverflowError가 발생한다고 한다.
그러고 보니 StackOverflowError는 몇 번 마주친 적이 있었다.
스택 영역은 LIFO 구조로 빠른 할당, 해제를 통해 성능이 매우 좋으나 위에 말한대로 스택오버플로우 위험가능성이 있다. (무한재귀, 깊은 함수 호출시)
재미있는 것은 List<String> AList = new ArrayList<String>(); 이렇게 선언했다고 하면,
AList라고 하는 참조변수는 스택에 저장되고,
newArrayList<String>()은 힙에 저장된다.
세 영역 비교 요약표
영역 | 저장대상 | 생명주기 | 크기/유동성 | 서버에 미치는 영향 |
메소드(Method) | 클래스 정보, static | 앱 실행~종료 | JVM or OS 메모리 | 클래스 수 많을 시 OOM 가능 |
스택(Stack) | 지역 변수, 호출 정보 | 메서드 실행 시 | 작음 (1MB ~ 2MB) | 깊은 재귀시 StackOverflow |
힙(Heap) | new 객체, 배열 | 참조 유지 시 | 가장 큼 (수백 MB~수 GB) | GC 비용 높음, OOM 발생 가능 |
신기한 자바와 스프링의 세계.
OOM을 방지하기 위해서는 static 선언을 자제하고, 더 이상 사용하지 않는 객체는 연결을 끊어주는 것이 좋다고 한다.
static List<String> hugeList = new ArrayList<>();
public void loadData() {
for (int i = 0; i < 1000000; i++) {
hugeList.add("data" + i);
}
// 작업 끝났으면 리스트 초기화
hugeList = null;
}
위와 같이 리스트 사용이 끝나면 null선언 해서 참조를 끊어주면, 가비지 콜렉터가 객체를 수거한다.
public void loadData() {
List<String> hugeList = new ArrayList<>();
for (int i = 0; i < 1000000; i++) {
hugeList.add("data" + i);
}
// 지역 변수이므로 메서드 종료와 함께 GC 대상이 됨
}
혹은 static 대신 지역변수로만 선언하기! 가장 안전하고 메모리 누수를 원천적으로 막는 방법이라고 한다.
그리고 리스트가 커야하면 BufferedWriter 같은 스트림을 써서 바로 디스크에 기록하는 것이라고 하는데...
또다시 BufferedWriter를 마주쳤다! BufferedReader, Writer에 대해서도 공부해보아야겠다.
'JAVA(초기 공부)' 카테고리의 다른 글
[JAVA] 1016 String 클래스의 기능들 (0) | 2025.03.08 |
---|---|
[JAVA] 1014 상속 Inheritance (0) | 2025.03.08 |
[JAVA] 1013 BufferedReader와 BufferedWriter (0) | 2025.03.08 |
[JAVA] 1013 getter와 setter, private 접근제한자 (0) | 2025.03.08 |
[JAVA] Class 와 Method (0) | 2025.03.08 |