Java GC의 또 다른 문제 swap

neptune에 multi-row selection 기능(like, between) 을  구현하고 테스트 하는 중에 계속 GC로 인해 10 ~ 30초 정도 시스템이 멎는 증상이 발생하였습니다. 지난번부터 계속 GC가 문제를 일으키고 있는데 UseConcMarkSweepGC 옵션으로 해결된 것 같은 문제가 다시 재현되었습니다.

일주일 삽질 끝에 발견한 것은 테스트 하는 장비의 환경이 바뀌어서 물리적인 메모리가 작아졌기 때문이었습니다. 메모리가 모자라서 GC가 오래 걸렸다라기 보다는 메모리는 여유 있는데 메모리 swap이 발생하면서 GC 도는 시간이 오래 걸린 증상이었습니다.
GC 로그를 보면

[ParNew: 52353K->6528K(59008K), 7.8146970 secs] 331439K->326208K(2090624K), 7.8233400 secs] [Times: user=0.43 sys=0.00, real=7.82 secs]

GC 시간 7초 정에 user 시간은 0.43인 것을 볼 수 있습니다. 나머지 시간의 swap 메모리를 로딩하거나 디스크로 내리는데 걸린 wait 시간으로 볼 수 있습니다. 프로그램을 잘못 만든것이 아닌가 하고 열심히 튜닝했었는데 결국 원인은 외부에 있었네요...
일주일 삽질의 결과입니다. ㅋ

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


RESTful 웹 서비스란?

RESTful 웹 서비스라는 것이 단순히 HTTP로 request를 받아서 결과를 XML 형태로 전송해주는 것이라고만 생각했는데 관련 문서를 찾아보니 잘못 생각하고 있었네요.

REST와 관련하여 몇가지 개념이 있는데 제가 알고 있는 개념인 HTTP를 이용하고 query string에 처리할 action에 대한 정보 등을 포함하는 방식을 REST-RPC 개념으로 설명하면서 진정한 의미의 REST한 서비스는 아니라고 합니다.

REST는 REpresentational State Transfer의 약자입니다.
이론적 배경은 다음 논문을 참고하세요.
http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm

일단 개발자에게 피부로 와 닿는 설명은 다음과 같습니다.

1. HTTP 프로토콜의 PUT, GET, POST, DELETE 등과 같은 Method를 의미 그대로 사용한다.
2. Resource에 대한 접근을 URI를 이용한다.

다음 HTTP request가 REST 웹 서비스의 예제입니다.

GET /book HTTP/1.1
Host: www.jaso.co.kr
Accept: application/xml

위의 HTTP request는 책 목록을 가져 오기 위한 요청입니다. 특정 책의 상세 정보를 요청하는 경우에는 다음과 같이 할 수 있습니다.

GET /book/isbn_0001 HTTP/1.1
Host: www.jaso.co.kr
Accept: application/xml

특정 책 정보를 삭제하는 경우는 다음과 같습니다.

DELETE /book/isbn_0001 HTTP/1.1
Host: www.jaso.co.kr
Accept: application/xml


물론 위와 같은 처리를 다음과 같이 URL의 query string에 넣어서 처리할 수도 있습니다.

http://www.jaso.co.kr/book?action=delete&book_id=isbn_0001

이런 방식을 REST-RPC라고 합니다.

굳이 이렇게 나누는 것은 REST한 서비스 구성을 자세히 보면 특정 패턴이 보이고 이 패턴을 이용하여 프로그램을 쉽고 표준적으로 할 수 있기 때문입니다. 표준적으로 할 수 있다는 것은 프레임워크나 경량의 컨테이너가 출현할 가능성이 높고 이런 프레임워크를 통해 아주 쉽게 개발할 수 있게 됩니다.
최근 자바 진영에서는 JSR 311에 JAX-RS: The Java API for RESTful Web Services라는 형태로 표준화 하고 있습니다. 이것을 지원하는 프레임워크도 일부 나오고 있습니다.
단순한 개념인 것 같지만 그 개념의 이론을 만들고 이론에 따르는 시스템들이 개발되어 나오고 다시 이것들이 솔루션으로 만들어져 나오는 이런 선순환 구조를 통해 소프트웨어 산업이 계속 발전하고 성장하는 것 같습니다.

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


JVM 옵션

JVM의 GC 관련 옵션을 찾는 중에 다음 URL을 발견하였는데

http://wiki.ex-em.com/index.php/JVM_Options

http://ukja.tistory.com/51

http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html

1.3 정도에서 JVM 파라미터로 튜닝하던 때에 옵션에 대해서 공부하다가 지금 보니까 엄청나게 많아지고 다양한 정보를 설정할 수 있게 되어 있네요...

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


NegativeArraySizeException 관련

분산/병렬 프로그램 만들다 보니 전혀 재현 불가능한 에러도 발생합니다. 오늘 발생한 에러는 거의 재현하기 불가능할 것 같습니다.

코드는 다음과 같습니다.

class Test {
    TreeSet<String> values = new TreeSet<String>();
    
     //이부분이 Multi client로부터 접속되는 코드
     public void exec() {
        values.add(...);
        ...
        //다양한 조건을 이용하여 valus add, remove
        List<String> temp = new ArrayList<String>();
        temp.addAll(values);
     }
}

원래는 Test하는 코드의 exec를 호출하는 것은 앞단에서 하나의 클라이언트만 접속하게 되어 있었는데 이 부분에 약간 잘못이 있어 동시에 접속되는 상황이 발생한 것 같습니다.
예외는 다음과 같이 발생했습니다.

java.lang.NegativeArraySizeException
        at java.util.AbstractCollection.toArray(AbstractCollection.java:119)
        at java.util.ArrayList.addAll(ArrayList.java:472)

AbstractCollection 소스를 보니 TreeSet의 size()에서 반환되는 값이 0 보다 작은 경우에만 이런 예외가 발생하게 되어 있습니다.

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


자바에서 byte[] memory usage

어제 포스팅한 자바 메모리 관련해서 계속 프로파일링 도중에 다음과 같은 아주 심플한 코드들 JProfiler를 이용하여 프로파일링을 해 봤습니다.

길이 10의 byte[]를 10,000개 만들 경우 실제 데이터의 메모리는 100,000 byte가 됩니다. 하지만 프로파일러에는 234KB가 10,0001개 생성되었고 234KB 할당 되어 있다고 나옵니다.
사용자 삽입 이미지
    List<byte[]> values =  new ArrayList<byte[]>();
   
    byte[] aaa = new byte[10];
   
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    String a = "1";
    int size = 0;
    for(int i = 1;  i < 1000000; i++) {
      values.add("1234567890".getBytes());
      size += 10;
     
      if(i % 10000 == 0) {
        System.gc();
        long free = Runtime.getRuntime().freeMemory();
        long total = Runtime.getRuntime().totalMemory();
        System.out.println(i + ">" + size + ">" + free + ">" + total + ">" + (total-free));
        reader.readLine();
      }
    }

이것과 관련해서 검색해보니까 Java에서는 32bit JVM의 경우 하나의 byte[]를 저장하는데 12byte의 추가 메모리가 필요하다고 합니다(http://forum.java.sun.com/thread.jspa?threadID=5216017&messageID=9879177). 그러면 100,000byte + 120,000byte = 220,000 byte로 프로파일링된 결과가 많이 비슷해진 것을 알 수 있습니다.
오버헤드가 너무 심하다는 생각입니다. byte[]에 아주 작은 값이 많이 쌓이는 경우라면 실제 생성한 데이터의 크기와 메모리 할당되는 크기가 너무 차이가 나서 개발자가 예상하지 못한 곳에서 OutOfMemory가 발생할 가능성이 많습니다.
제 경우도 이런 노가다 분석을 시작한 이유가 이것 때문입니다. 실제 데이터는 100MB  정도 넣었는데 메모리는 거의 400MB 이상을 사용하고 있었던 거죠...
난감하네요. 해결해야 하는데

http://www.javaworld.com/javaworld/javatips/jw-javatip130.html?page=2
크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


자바에서 이상한 메모리 관리

오늘 코드 테스트 중 우연히 발견한 내용입니다. List에 추가된 모든 데이터의 대략 100MB도 안되는데에도 불고하고 OutOfMemory Error가 발생하였습니다. 물론 -Xmx, -Xms 옵션은 모두 256m으로 설정했습니다.
그래서 테스트 코드를 몇개 만들어서 비교해봤습니다.

    String str = "0123456789";
   
    List<String> datas = new ArrayList<String>(100);
    long realSize = 0;
    for (int i = 0; i < 100000000; i++) {
      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());
      }
    }
dataCount=3130000,dataSize=62600020,free=270816,total=266403840


    String str = "0123456789";
   
    List<String> datas = new ArrayList<String>(100);
    long realSize = 0;
    for (int i = 0; i < 100000000; i++) {
      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 수행시 모두 해제 되기 때문에 이론적으로는 두번째 코드와 동일해야 할 것 같은데 다르게 나타나고 있습니다.
크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


AOP(Aspect Oriented Program)을 위해 Proxy를 이용하여 특정 클래스의 method하는 경우 InvocationHandler를 implements 한다. 이때 invoke() 메소드 내에서 synchronized 처리에 주의해야 한다. 다음 코드의 경우

public class DefaultInvocationHandler implements InvocationHandler {

Object monitor = new Object();

 

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

    {

      synchronized(monitor) {

        try {

          return method.invoke(targetObject, args);

        } catch (Throwable e) {

          e.printStackTrace(System.out);

          throw e;

        }

      }

    }

}


이 경우 일반적인 경우에는 잘 작동하지만 호출하는 메소드 내에서 다시 Proxy 객체의 메소드를 호출하는 경우 Handler 내부적으로는 recursive하게 call되어 두번째 메소드가 수행되는 시점에서는 monitor에 대한 lock을 가져오지 못해 무한대기 상황이 발생한다.
따라서 method.invoke(targetObject, args); 이부분 수행할 때에는 lock에 대한 처리가 없어야 한다.

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


Java File 클래스 renameTo 메소드 관련

Java의 File 클래스에 있는 기능 중 renameTo 메소드는 잘못 사용할 가능성이 많은 메소드이다. Windows와 Linux에서 다르게 동작할 수 있으며 파일 시스템의 디스크 구성에 따라 다르게 작동할 수 있기 때문이다.
예를 들어 windows에서 C 드라이브에 있는 파일을 D 드라이브로 renameTo 메소드를 이용할 경우 rename 되지 않는다. 이유는 windows의 경우 논리적으로 다른 드라이버에서의 rename은 rename이 아니라 copy & delete old 이기 때문이다. 이런 로직을 renameTo 처리해 주면 좋겠지만 그렇지 않다.
또 다른 문제는 서브 디렉토리에 대한 문제이다. 디렉토리를 renameTo할 경우 해당 디렉토리 아래에 파일 또는 서브 디렉토리가 존재하는 경우에도 정상적으로 작동하지 않을 가능성이 크다. 따라서 rename의 경우 별도로 메소드를 만들어서 사용하는 것이 환경적인 문제로 발생하는 오동작을 피할 수 있다.
어제 개발 도중에 이런 문제로 인해 인터넷을 검색해 봤지만 쓸만한 코드를 찾지 못해 직접 만들었다.

private boolean rename(File fromFile, File toFile) throws IOException {

    if (fromFile.isDirectory()) {

      File[] files = fromFile.listFiles();

      if (files == null) {

        //디렉토리 아무것도 없는 경우

        return fromFile.renameTo(toFile);

      } else {

        //디렉토리내 파일 또는 디렉토리가 존재하는 경우

        if(!toFile.mkdirs()) {

          return false;

        }

        for (File eachFile : files) {

          File toFileChild = new File(toFile, eachFile.getName());

          if (eachFile.isDirectory()) {

            if(!rename(eachFile, toFileChild)) {

              return false;

            }

          } else {

            if(!eachFile.renameTo(toFileChild)) {

              return false;

            }

          }

        }

        return fromFile.delete();

      }

    } else {

      //파일인 경우

      if(fromFile.getParent() != null) {

        if(!toFile.mkdirs()) {

          return false;

        }

      }

      return fromFile.renameTo(toFile);

    }

  }

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


Java Language Spec 출간

드디어 Java Language Spec이 출간한다. 2006년 봄에 사내 자바 개발자와 같이 번역을 시작한지 꼬박 1년 6개월 정도의 시간이 지난후에야 나오게 되는 책이다.
처음에는 스펙이니까 자바 기본 서적 정도의 수준이겠지 라는 마음으로 시작했지만 몇페이지를 넘겨본 순간 이거 장난아니구나 라는 생각과 함께 괜히 시작했다라는 후회가 들기도 했지만 끝까지 번역을 네분이 잇었기에 출간이 가능하지 않았나 생각한다.
초급에서 중급 이상의 실력을 갖추기 위해서는 반드시 읽어 봐야할 책이 아닌가 생각한다.

사용자 삽입 이미지


http://www.acornpub.co.kr/blog/183


감수자로만 참여를 했는데 번역하신 네분에게 너무 수고했다라는 말을 전한다.

크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


Linux 64bits JVM 메모리 문제??

Linux 64bits JVM에서 메모리 할당에 대한 문제인지는 모르겠지만 다음과 같은 증상 발견....

Linux에서 top 또는 vmstat 명령으로 메모리 상황을 보면 free, cached 영역이 있는데

cached 영역은 OS가 파일 I/O의 처리를 위해 임시로 사용한 영역으로 메모리를 차지하고는 있지만

다른 프로세스가 메모리 요청을 하면 할당해줄 수 있는 메모리 영역이다.

Linux 64bits JVM에서 메모리 할당을 할 때 이 cached 영역에 있는 메모리는 할당받지 못하는 문제가 있다.

예를 들어 free에 1GB가 있고 cached에 1GB가 있는 경우 1.5GB를 할당받으면 OutOfMemoryError가 발생한다.

동일한 환경에서 32bits 설치하고 실행하면 정상적으로 수행되고 프로그램 수행이 완료된 후에 free는 1.5GB 이상

으로 증가되어 있고 cached가 0.5GB로 줄어든다.

버그인지 아니면 또 다른 옵션이 있는건지.... 쩝.
크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 김형준


« Previous : 1 : 2 : 3 : 4 : Next »