자바에서 이상한 메모리 관리
- Posted at 2008/04/16 16:16
- Filed under Program/JAVA
오늘 코드 테스트 중 우연히 발견한 내용입니다. List에 추가된 모든 데이터의 대략 100MB도 안되는데에도 불고하고 OutOfMemory Error가 발생하였습니다. 물론 -Xmx, -Xms 옵션은 모두 256m으로 설정했습니다.
그래서 테스트 코드를 몇개 만들어서 비교해봤습니다.
List<String> datas = new ArrayList<String>(100);
String data = "0123456789" + str;
datas.add(data);
realSize += data.getBytes().length;
if (i % 10000 == 0) {
System.out.println("dataCount=" + i + ",dataSize=" + realSize +
",free=" + Runtime.getRuntime().freeMemory() +
",total=" + Runtime.getRuntime().totalMemory());
}
}
String str = "0123456789";
List<String> datas = new ArrayList<String>(100);
String data = new String("01234567890123456789");
datas.add(data);
realSize += data.getBytes().length;
if (i % 10000 == 0) {
System.out.println("dataCount=" + i + ",dataSize=" + realSize +
",free=" + Runtime.getRuntime().freeMemory() +
",total=" + Runtime.getRuntime().totalMemory());
}
}
dataCount=8640000,dataSize=172800020,free=9725240,total=266403840
위의 코드와 아래코드의 구분은 String에 대한 + 처리 부분만 차이가 납니다. 이 코드는 String에 대한 테스트 코드는 아닙니다. 일반적으로 프로그램 내에서 위에 있는 코드와 같이 생성된 객체를 List에 add하는 경우가 많아서 비교하기 쉽게 만들어 본 것입니다.
아래 코드의 경우 약 864만개의 Object를 추가할 수 있는데 이것을 메모리로 계산해보면 데이터가 172M, 860만개를 가리키기 위한 레퍼런스(1객체당 4byte) 30M 정도 소요되어 200M 정도가 데이터 저장에 사용되었습니다. 그리고 free영역으로 10M 정도 사용되었습니다. 나머지 60M는 어디에서 사용되었는지가 첫번째 이슈입니다. 일단 추측해볼 수 있는 것은 ArrayList의 경우 자신의 용량보다 더 많은 레코드에 대해 add 요청이 들어오면 확장을 하게 되는데 이 확장되어 미리 확보한 영역 때문이라고 예측을 해볼 수 있습니다. 그래서 두번째 코드에서 List를 String[] 배열로 변경하여 다시 수행해 보면 다음과 같은 결과가 나타납니다.
"dataCount=9410000,dataSize=188200020,free=56504,total=266403840"
1000만개 레퍼런스에 40M, 데이터 180M -> 220M 이정도면 봐줄만 합니다. 나머지 40M는 다른 용도로 사용하겠죠....
두번째 이슈는 첫번째 코드에서는 300만개 정도밖에 저장할 수 없다는 것입니다. for loop내에서 발생하는 String 연산 처리에서 생성된 객체들은 GC 수행시 모두 해제 되기 때문에 이론적으로는 두번째 코드와 동일해야 할 것 같은데 다르게 나타나고 있습니다.
Posted by 김형준
- Response
- No Trackback , 5 Comments
Trackback URL : http://www.jaso.co.kr/trackback/251
Comments List
-
10 length 짜리 String object 2개를 20 length짜리 String object로 합친 후 연산 전에 생성된 object를 GC가 다 제거했다면, 똑같을 것 같은데...
GC가 다 제거 하지 않았다고 봐야할까요? -
저도 이놈의 out of memory 때문에 여러가지로 고민을 해봤는데 자바의 GC가 영 험블하더군요...^^
어쨌든 직접적인 원인은 임시 객체를 저장하는 메모리 영역인 eden이 꽉차서 발생하는 문제인데...
제가 그나마 찾은 대안은 VM에 옵션에 -XX:+UseParallelGC 를 추가하는 것 정도...이 옵션을 추가하면 eden 영역 GC가 기본 옵션보다 훨씬 빨리 제거됩니다. 하드웨어의 프로세서 갯수가 많을수록 효과가 큼...
불만인 것은 'out of memory가 발생하기 전에 알아서 eden영역을 정리해주면 좋잖아~' 인데...이게 프로그래머의 예상대로 동작하지 않는다는 점이 안타까울뿐이죠...^^;
이럴땐 그저 C++의 소멸자가 그리울 뿐... -
Hadoop 때문에 우연히 방문하였다가, 이 글을 읽게되었습니다.
테스트하신 2개의 프로그램이 논리적으로 같으나, 사실은 다른 프로그램입니다. 1번이 더 실제의 상황을 반영한 프로그램입니다.
우선 몇 가지를 알려드리면,
1) 객체 자체가 최소한 4바이트를 차지합니다.
아무런 필드가 없는 빈 객체가 4바이트입니다.
반면에 String 개체는 20바이트를 차지합니다.
객체 header 4바이트
length 필드 4바이트
스트링이 저장된 공간에 대한 레퍼런스 4바이트
해쉬값으로 4파이트
char[]에 대한 레퍼런스 (또는 포인터) 4 바이트
char[] 도 객체이므로, 최소한 4바이트를 차지합니다.
거기에 실제 데이터 공간이 필요합니다.
따라서, new String("0123456789"
는 최소한
20 + 4 + 10*2 바이트를 차지합니다.
2) ArrayList에서 String 객체를 가르키는데, 4바이트를 차지합니다.
그리고, ArrayList는 크기가 커지면, 1.5 배+ 1개로 그 용량을 늘립니다.
3) Java에서 new String("abc"
는 데이터를 복제하지 않습니다.
즉, new String("01234...789"
는 Sring이라는 객체는 만들지만, 문자열 저장 공간은 공유를 합니다.
따라서, 2번 프로그램에서 실제 "0..9" 까지의 20글자짜리 문자열은
단 한개만 존재합니다.
String 객체는 그것에 대한 포인터(레퍼런스) 만을 갖고 있을 뿐입니다.
1) 2) 의 내용으로 메모리 사용량을 계산해 보시면 1번 프로그램의 결과와 대충 맞습니다.
---
좋은 정보 감사합니다.
-
-
String에 대한 사실을 알게되는군요. 대략난감이었는데






