본문 바로가기

Java For All

자바 튜닝

무료 튜닝툴

visualgc   jvmstat-3_0.zip

% jps

21891 Java2Demo.jar

1362 Jps.jar

% visualgc 21891 250

 

% jps remote.domain

81763 Jps

1362 Jstatd

102592 Java2Demo.jar

% visualgc 102592@remote.domain

 

튜닝 툴 - JDK에 포함

jconsole

JConsole 은 JVM 의 광범위한 Java Management Extension (JMX) 사용 툴로 자바 플랫폼에서 수행되는 어플리케이션의 퍼포먼스 및 자원 사용에 대한 정보를 제공

 

jps

JVM 프로세스 목록을 보여주는 툴

jps 는 솔라리스의 proc 툴인 ps 와 유사한 툴

jps 는 시스템의 모든 프로세스들 대신에 현재 유저의 자바 프로세스들만을 찾아 낸다.

C:\Javasoft\jdk1.5.0_15\bin>jps -l

7656

7120

1780 org/netbeans/Main

4420 sun.tools.jps.Jps

1596 org.apache.catalina.startup.Bootstrap

 

jstat

JVM 통계 수치를 모니터링하는 툴. 수행 중인 핫스팟 JVM에 연결하고, 옵션에 따른 정보를 모아 로그를 남긴다.

jstat 는 자바 핫스팟 가상 머신의 퍼포먼스 통계를 보여 준다.

 

jstatd

JVM의 jstat 데몬으로 RMI 서버를 기동한다.  원격으로 JVM을 모니터링 하기 위해서 사용된다.

jstatd 는 Java Remote Method Invocation (RMI) 서버 어플리케이션으로 자바 핫스팟 가상 머신의 생성 및 종료를 모니터링하고 로컬 호스트에서 실행되고 있는 JVM 에 붙어서 원격으로 모니터링 할 수 있는 인터페이스를 제공 한다.

 

jvisualvm (JDK6.0)

jvisualvm.jpg

자바 VisualVM 툴은 JDK 6 업데이트 7 이후로 JDK 와 함께 배포 됩니다. 이 툴은 힙 덤프, 쓰레드 덤프 그리고 코어 파일들을 분석하는데에 사용될 수 있습니다. Java VisualVM guide 의 Working with Core Dumps 를 참고하시기 바랍니다.

 

jinfo (JDK 6.0)

jinfo 는 주어진 프로세스 혹은 코어 파일 혹은 원격 디버그 서버로 부터 설정 정보를 얻습니다. 설정 정보는 자바 시스템 프로퍼티와 JVM 커맨드라인 플래그들을 포함합니다. (자세한 정보는 jinfo - Configuration Info 를 참고하시기 바랍니다)


jmap (JDK 6.0)

jmap: 만약 이 툴이 어떠한 옵션도 없이 실행된다면 (pid 혹은 core 이외에), 솔라리스 pmap 툴과 유사한 정보를 보여 줍니다. 이 툴은 자바 힙 관찰을 위한 몇가지 옵션들을 제공 합니다. (jmap - Memory Map 을 참고하시기 바랍니다)

 

자바 SE 6 플랫폼에서 새로운 -dump 옵션이 추가 되었습니다. 이 옵션은 jmap 이 자바 힙을 파일로 저장하고 이후에 새로운 jhat 커맨드를 이용해서 검사해 볼 수 있습니다. (아래의 내용 참고).

 

jmap -dump 옵션은 실행중인 프로세스들에 솔라리스의 libproc 를 사용하지 않습니다. -- 대신에 실행중인 JVM 내에 작은 코드를 실행하고, 이것은 상대적으로 빠릅니다. 힙 덤프의 영향은 "full GC" (전체 힙에 대한 가비지 콜렉션) 플러스 힙의 내용을 파일에 쓰는 것과 거의 비슷합니다. 힙 덤프의 또 다른 가능한 방법은 gcore 를 이용하여 코어 덤프를 뜨고 jmap -dump 를 힙덤프를 이용해서 "오프라인" 모드로 실행하는 것입니다.

 

jstack (JDK 6.0)

jstack 은 솔라리스의 pstack 툴과 유사합니다. jstack 은 모든 자바 쓰레드들의 스택 트레이스를 출력해 주고 또한 추가적으로 네이티브 프레임들도 포함해서 출력합니다. ( jstack - Stack Trace 참고) java.util.concurrent locks 에 나오는 형태로 락과 데드락에 대한 정보도 출력 해 줍니다.

 

jsadebugd (JDK 6.0)

jsadebugd 는 자바 프로세스 혹은 코어 파일에 붙어서 jsadebugd - Serviceability Agent Debug Daemon 에서 나온대로 디버그 서버 형태로 동작합니다. jstack, jmap, 그리고 jinfo 같은 원격 클라이언트들은 자바 RMI 를 이용해서 서버에 붙을 수 있습니다.

 

jhat (JDK 6.0)

jhat 은 자바 힙 덤프 브라우저입니다. (jhat - Java Heap Analysis Tool 참고). 이 툴은 자바 힙 덤프 파일을 분석합니다( 예를 들어 위의 jmap -dump 에 의해 생성된 힙같은). jhat 은 웹서버를 실행해서 오브젝트들을 웹 브라우저에서 검사해 볼 수 있도록 합니다. 이 툴은 프로덕션 시스템에서 사용되도록 만들어지진 않았고 힙 덤프의 "오프라인" 분석을 위해서 만들어 졌습니다. jhat 툴은 플랫폼 독립적이어서 어떠한 플랫폼에서 만들어진 힙 덤프들을 보는데에도 사용될 수 있습니다. 예를 들어 솔라리스에서 jhat 에 의해 생성된 힙 덤프를 리눅스에서도 보실 수 있습니다.

 

 

기타 튜닝포인트 [펌 : 조대협 (bwcho@javastudy.co.kr)]#

Synchronized 사용을 되도록이면 배제한다

동기화 작업은 동시에 작동중인 다른 Thread의 수행을 저지하기 때문에, 필요하지 않는 동기화 작업은 사용하지 않는게 좋습니다. 단, 동기화 부분은 에러 발생 가능성이 아주 많기 때문에, 무리해서 까지 동기화 작업을 배제하시면 안됩니다.

불필요한 try-catch 문장을 자제한다.

try-catch 문장의 경우에는 if 문등을 이용한 에러 검출보다, 속도가 매우 느리게 됩니다. 프로그램의 논리성이나 가독성은 올라가는 반면에, 그만큼 속도는 감소가 되겠지요. 필수 사항은 아니며 권장 사항입니다. Performance를 중요시한다면, 불필요한 try-catch 문장 사용 하시기전에 한번 더 생각해보시기 바랍니다.

StringBuffer를 활용한다

이미 많은 자바 개발자들이 알고는 있지만 잘 활용하지 않는 부분이 바로 StringBuffer입니다. String연산이 많은 경우에는 String 보다는 StringBuffer를 사용하시는게 속도향상에 도움이 됩니다.

효과적인 Object Memory 관리  [펌 : 조대협 (bwcho@javastudy.co.kr)]#

효과적인 Object Memory 관리를 하기 위해서는, 일단 가장 좋은 방법으로는 Object에 포함된 데이터 타입을 효율적으로 사용하는데 있습니다.

 

public class IntPoint{

int x,y,z;

}

public class LongPoint{

int lx,ly,lz;

}

 

어떤 3차원 좌표값을 저장하기 위한 클래스가 위와 같이 있다고 봅시다. IntPoint Class의 경우 하나의 좌표를 저장하는데 int 3개, 즉 12byte의 용량이 소요가 되고, LongPoint Class의 경우 24byte가 소요가 됩니다. 10만 Point를 사용하는 3차원 Rendering Program이 있을 때, 1,200,000byte의 메모리가 차이가 나게 됩니다. 단 하나의 데이터 타입으로 한경우인데, 좀더 그 크기나 데이터 양이 늘어나게 되면, 그 차이는 좀더 확연히 나타나게 되겠지요? 대략적인 데이터의 크기를 예측하고, 적절한 데이터 타입을 사용하는 것 부터가, Object 의 Memory size를 줄이는데 큰 도움이 됩니다.

 

각각의 Object 사이즈를 측정하는 방법을 한번 집고 넘어가도록 하지요. 각각의 Object를 재기 위해서는 System.gc();라는 메소드를 사용하면 편리합니다. 아래와 같은 순서로 진행을 하면되는데요.

1. System.gc(); 를 콜해서, 현재 메모리를 깨끗이 비운다.

2. Runtime.totalMemory(),freeMemory() 를 이용하여, 현재 메모리 사용량을 구합니다.

3. Object 사이즈를 재고자 하는 Object를 new를 이용하여 1,000개 정도 생성합니다.

4. System.gc();를 콜해서 메모리를 다시 정리합니다.

5. Runtime.totalMemory(),freeMemory() 를 이용하여, 현재 메모리 사용량을 구합니다.

6. 2에서 구한 메모리크기에 5에서 구한 메모리크기를 뺀후에, 그것을 1,000으로 나누면 한 Object의 메모리 크기를 구하실 수 있습니다.

 

이렇게 객체 사이즈를 측정 해보면 또한 재미있는 사실을 알 수 있는데요. 똑같은 객체라도 JVM Version에 따라서, 그 Object Size가 틀려집니다. javax.swing.JTable Object의 경우 JDK 1.2.2에서 약 26000 byte, JDK 1.3에서는 4000 byte (대략 6배 이상이 차이가 납니다.) 그러니까는 최신 JDK를 사용하는 것도 하나의 방법이 될 수 도 있겠지요? ^^

 

2-3) 효과적인 Class Loading 관리

클래스는 크기를 줄이는 것보다, 로딩 시간을 줄이는 접근이 필요합니다. 불필요한 클래스 로딩을 막고, 적절한 시간에 클래스를 로딩하는 방법이 필요합니다. 워드 프로그램을 생각해보도록 합시다.. doc,hwp,html 문서를 읽을 수 있는 워드 프로그램이 있고, 각각의 doc,html,hwp 를 읽어서 파싱하는 클래스가 크다고 가정합시다.

 

public Document openDocument(Sting filename,String ext){

if(ext.equals(doc)) return new Document_MSWORD(filename);

else if(ext.equals(hwp)) return new Document_HWP(filename);

else if(ext.equals(html)) return new Document_HTML(filename);

}// open Document

 

파일을 오픈하는 로직은 위와 같이 됩니다. 즉, doc 파일만 오픈할때도, hwp,html 파일을 오픈하는 클래스가 로딩되어 버리는 메모리상, 속도상의 낭비가 있게 되지요. 그래서 필요할 때 마다 파일오픈 클래스를 로딩해버리는게 방법이 될 수 있습니다. 동적 클래스로딩 기법을 이용하여, 클래스를 변경하면, 아래와 같이 됩니다.

 

public Document openDocument(Sting filename,String ext){

if(ext.equals(doc)) return (Document)Class.forName(MSWORDDoc).newInstance();

else if(ext.equals(hwp)) return (Document)Class.forName(HWPDoc).newInstance();

else if(ext.equals(html)) return

(Document)Class.forName(HTMLDoc).newInstance();

}// open Document

이렇게 하면 필요할 때 마다 클래스를 로딩하기 때문에, 메모리 사용은 효율적이지만, 실행중에, 클래스를 로딩해야하는 속도쪽에 trade off가 발생을 하게 되고, 또한 JIT 컴파일러에 의한 속도 최적화를 기대하기가 어렵게 됩니다. 그렇지만 사용빈도가 적은 대형 클래스를 로딩할 때는 매우 유용한 방법이 됩니다.

 

이렇게 덩치가 큰 클래스를 핸들링 하는 것 이외에도, 덩치가 작은 클래스 (예를 들어, EventListener 등등)등의 메모리 사용량과 로딩 타임을 줄이기 위해서는 작은 클래스는 되도록이면 큰 클래스 하나로 통합하는 것이 좋은데, 이는 여러 디자인 패턴들이 많이 나와 있으니 참고하시기 바랍니다. 특히 UI 프로그래밍에서 많은 효과를 기대할 수 있습니다.

 

이렇게 클래스의 로딩타임등을 최적화할 때, 먼저 각 클래스의 로딩 시점등을 미리 체크해볼 수 가 있는데요. Java vm실행시 java verbose 옵션을 이용하면, 각 단계별로 로딩되는 class들의 이름을 볼 수 가 있습니다. 참고하세요.

메모리 사용량 측정 [펌 : 조대협 (bwcho@javastudy.co.kr)]#

자바에 java.lang.Runtime 클래스를 이용하면, 현재 사용중인 메모리 양을 쉽게 알 수 있습니다. Runtime.totalMemory() 메소드는 현재 실행중인 자바프로그램을 위해 잡혀있는 heap size이고, Runtime.freeMemory()는 heap중에서 현재 사용되지 않고 비어있는 메모리양을 나타냅니다.

고로, Runtime.totalMemory()-Runtime.freeMemory()를 하면 현재 사용중인 Memory HeapSize를 알 수 가 있습니다.

이 메소드들을 이용하면, 각 단계별로 메모리 사용량이나, 대략적으로, Java Application이 사용하는 Heap size를 측정할 수 있으며, Memory Leak등의 모니터링이 가능합니다.

 

이렇게 Java 프로그램의 대략적인 Heap 사용량이 측정이 되었으면, Java 실행시에, 미리 최소와 최대 Hip Size를 미리 정해줄 수 있는데, 대략 이 프로그램이 32m 정도의 hip 메모리를 사용한다면 java hotspot ms32m mx32m JavaClass 해주면, 실행 처음부터 32m의 메모리를 확보하고 실행이 됩니다. 즉 JVM이 heap size를 줄이거나 늘이는데 드는 시간이나 action이 없어져서 좀더 안정적이고 빠른 실행이 가능하게 됩니다.

 

이렇게 해서 측정해보면, 대부분의 메모리와 시간은 Object,Class,Thread 등을 로딩하는데 사용이 되는데, 이를 각각줄이게 되면, 자바 프로그램의 메모리 사용 효율성을 향상 시킬 수 있습니다.

 

일단 Thread의 경우, DB의 Connection Pooling 처럼, Thread를 Pooling하여 재사용함으로써, Loading time등을 효과적으로 control할 수 있습니다만, 일반 개발자가 개발하여 사용하기에는 어렵기 때문에(안정성등에 문제가 많습니다.). 미리 개발되어 있는 코드를 참조하셔서 개발 또는 구입하여 사용하시기 바라며, 여기서는 Object와 Class에 대해서만 살펴보도록 하겠습니다.

Class의 메모리 사용량을 줄일것이냐? Object의 메모리 사용량을 줄이느냐를 결정하는데는 중요한 Factor가 있습니다. 프로그램이 Client 소프트웨어냐, Server 소프트웨어냐에 따라 그 사용량이 틀리게 되는데.

memory.gif

두 그림을 비교해보도록 하죠. 왼쪽 그림은 서버 프로그램인데, 서버의 경우, 다수의 클라이언트로부터, 접속을 처리하기 때문에, 같은 종류의 Object가 많이 생성이 됩니다. 그러나 클라이언트 프로그램의 경우에는 객체보다는 각 종류의 클래스가 로딩되어야 되기 때문에, 한 클래스에 대한 객체수가, 서버프로그램보다 비교적 적게 됩니다. 그래서, 서버 프로그램쪽에는 Object의 사이즈에, 반대로 클라이언트 프로그램쪽에는 Class Loading에 신경을 쓰는게 좋습니다.

효율적인 메모리 사용하기 [펌 : 조대협 (bwcho@javastudy.co.kr)]#

속도 만큼 중요한 점중의 하나는 메모리 사용량입니다. 만약 SW가 RAM 사이즈보다 크게 되면, 시스템은 Virtual Memory를 사용하게 될테고, 결국 HDD IO가 발생하게 되어서 속도가 급속도로 떨어지게 됩니다. 좋은 자바프로그램이란, 효율적으로 메모리를 관리해주도록 하게 해줘야 합니다.

 

보통 자바개발자들이 GC (Garbage Collection)을 만능으로 생각하고 계시는 분이 많은데요. 이 GC가 오히려 자바 개발자들의 발목을 잡는 경우가 많습니다. Full GC의 경우에는 시스템이 클 수 록 그 시간이 많이 걸리기 때문에, 외부에서 봤을 때 Full GC중에는 시스템이 정지된 것 처럼 보이는 경우도 있구요. 가끔은 그런 것을 서버가 다운된걸로 착각해서 연동 시스템이 오작동 하는 경우도 종종 있습니다. (Cluster 상에서 node a가 Full GC중에, 그와 통신하는 node b가, node a가 다운된것으로 판단하고, node a와의 통신을 단절 시키는 경우..)

 

또한 GC가 비사용중인 메모리를 모두 수거할 것으로 생각하시는데, 의외로 그렇지 않은 경우가 있습니다. 잘못된 변수 선언으로, 계속해서 객체가 메모리상에 잡혀있어서 결국에는 Memory Leak이 생겨서 시스템 메모리를 무한정으로 잡아먹는 경우가 생기는데, 이를 예방하기 위해서는 객체의 사용이 끝난후에는 아래와 같이 꼬옥 그 객체의 reference를 null로 지정해주는게 효과적인 GC를 도울 수 있는 길이 됩니다.

  1. MyObject obj = new MyObject();

  2. :

  3. :

  4. // 사용이 끝난후

  5. obj = null;

GC (Garbage Collection)는 자바프로그래밍을 하는데 제법 중요한 점이 많으니까는 시간을 내서 따로한번 체크해보시는 것이 좋겠군요.

OutOfMemory 발생시 사용할 만한 옵션##

–verbosegc

[GC 26394K->23989K(43016K), 0.0040986 secs]

-XX:+PrintGCDetails

[GC [DefNew: 2120K->8K(2304K), 0.0002922 secs] 7509K->5397K(32576K), 0.0003947 secs]

Stream 비교 [펌 : 조대협 (bwcho@javastudy.co.kr)] ##

IO 조합

파일 복사 속도 (ms) 5M파일 복사

InputStream/OutputStream

109749

BufferedStream

5794

BufferedStream + buffer

517

  1.  private void copyby_basic(String src,String des) throws IOException{
        InputStream in = null;
        OutputStream out = null;
        byte[] buffer = new byte[1024]; // 버퍼 선언
       
        try{
            in = new BufferedInputStream(new FileInputStream(src));
            out = new BufferedOutputStream(new FileOutputStream(des));
            while(true){
                int read = in.read(buffer); // 버퍼 단위로 읽는다.
                if(read == -1) break;
                out.write(buffer,0,read);
           
            }// while
            in.close();
            out.close();
        }finally{
            if(in!=null) in.close();
            if(out!=null) out.close();
        }//try-finally
  2. }