달력

42024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
JVM GC와 메모리 튜닝



모든 Java Application은 JVM(Java Virtual Machine)위에서 동작한다.
이 JVM이 동작하는데 있어서, 메모리의 구조와 특히 GC는 Application의 응답시간과 성능에 밀접한 관계를 미친다. 이번 강좌에서는 JVM 의 메모리 구조와 GC 알고리즘 (JDK 1.4.X에 포함된 새로운 알고리즘 포함) 그리고, JVM의 메모리 튜닝을 통한 Application의 성능향상방법에 대해서 알아보도록 하자.


1.GC란 무엇인가?


GC는 Garbage Collection의 약자로 Java 언어의 중요한 특징중의 하나이다.
GC는 Java Application에서 사용하지 않는 메모리를 자동으로 수거하는 기능을 말한다.
예전의 전통적인 언어 C등의 경우 malloc, free등을 이용해서 메모리를 할당하고, 일일이 그 메모리를 수거해줘야했다. 그러나 Java 언어에서는 GC 기술을 사용함에 따라서 개발자로 하여금 메모리 관리에서 부터 좀더 자유롭게 해주었다.


2.GC의 동작 방법은 어떻게 되는가?


1) JVM 메모리 영역

GC의 동작 방법을 이해하기 위해서는 Java의 메모리 구조를 먼저 이해할 필요가 있다.
일반적으로 Application에서 사용되는 객체는 오래 유지 되는 객체보다, 생성되고 얼마안있어서 사용되지 않는 경우가 많다. <그림 1 참조>


<그림 1. 메모리 foot print>


그래서 Java에서는 크게 두가지 영역으로 메모리를 나누는데 Young 영역과 Old 영역이 그것이다.
Young 영역은 생긴지 얼마 안된 객체들을 저장하는 장소이고, Old영역은 생성된지 오래된 객체를 저장하는 장소이다. 각 영역의 성격이 다른 만큼 GC의 방법도 다르다.
먼저 Java의 메모리 구조를 살펴보자.


<그림 2. Java 메모리 구조>


Java의 메모리 영역은 앞에서 이야기한 두 영역 (Young 영역,Old 영역)과 Perm 영역 이렇게 3가지로 영역으로 구성된다.


<표 1. Java 메모리 영역>



2) GC 알고리즘

그러면 이 메모리 영역을 JVM이 어떻게 관리하는지에 대해서 알아보자.
JVM은 New/Young 영역과, Old영역 이 두영역에 대해서만 GC를 수행한다. Perm영역은 앞에서 설명했듯이 Code가 올라가는 부분이기 때문에, GC가 일어날 필요가 없다. Perm영역은 Code가 모두 Load되고 나면 거의 일정한 수치를 유지한다.


○ Minor GC
먼저 New/Young영역의 GC방법을 살펴보자 New/Young 영역의 GC를 Minor GC라고 부르는데, New/Young영역은 Eden과 Survivor라는 두가지 영역으로 또 나뉘어 진다. Eden영역은 Java 객체가 생성되자 마자 저장이 되는곳이다. 이렇게 생성된 객체는 Minor GC가 발생할때 Survivor 영역으로 이동된다.

Survivor 영역은 Survivor 1과 Suvivor2 영역 두 영역으로 나뉘어 지는데, Minor GC가 발생하면 Eden과 Survivor1에 Alive되어 있는 객체를 Suvivor2로 복사한다. 그리고 Alive되어 있지 않는 객체는 자연히 Suvivor1에 남아있게 되고, Survivor1과 Eden영역을 Clear한다. (결과적으로 Alive된 객체만 Survivor2로 이동한것이다.)
다음번 Minor GC가 발생하면 같은 원리로 Eden과 Survivor2영역에서 Alive되어 있는 객체를 Survivor1에 복사한다. 계속 이런 방법을 반복적으로 수행하면서 Minor GC를 수행한다.

이렇게 Minor GC를 수행하다가, Survivor영역에서 오래된 객체는 Old영역으로 옮기게 된다.

이런 방식의 GC 알고리즘을 Copy & Scavenge라고 한다. 이 방법은 매우 속도가 빠르며 작은 크기의 메모리를 Collecting하는데 매우 효과적이다. Minor GC의 경우에는 자주 일어나기 때문에, GC에 소요되는 시간이 짧은 알고리즘이 적합하다.

이 내용을 그림을 보면서 살펴보도록 하자.


<그림 3-1. 1st Minor GC>


Eden에서 Alive된 객체를 Suvivor1으로 이동한다. Eden 영역을 Clear한다.


<그림 3-2. 2nd Minor GC>


Eden영역에 Alive된 객체와 Suvivor1영역에 Alive된 객체를 Survivor 2에 copy한다.
Eden영역과 Suvivor2영역을 clear한다.


<그림 3-3. 3rd Minor GC>


객체가 생성된 시간이 오래지나면 Eden과 Suvivor영역에 있는 오래된 객체들을 Old 영역으로 이동한다.


○ Full GC

Old 영역의 Garbage Collection을 Full GC라고 부르며, Full GC에 사용되는 알고리즘은 Mark & Compact라는 알고리즘을 이용한다. Mark & Compact 알고리즘은 전체 객체들의 reference를 쭉 따라가다면서 reference가 연결되지 않는 객체를 Mark한다. 이 작업이 끝나면 사용되지 않는 객체를 모두 Mark가 되고, 이 mark된 객체를 삭제한다.<그림 4 참고> (실제로는 compact라고 해서, mark된 객체로 생기는 부분을 unmark된 즉 사용하는 객체로 메꾸어 버리는 방법이다.)

Full GC는 매우 속도가 느리며, Full GC가 일어나는 도중에는 순간적으로 Java Application이 멈춰 버리기 때문에, Full GC가 일어나는 정도와 Full GC에 소요되는 시간은 Application의 성능과 안정성에 아주 큰 영향을 준다.


<그림 4. Full GC>




3. GC가 왜 중요한가?


Garbage Collection중에서 Minor GC의 경우 보통 0.5초 이내에 끝나기 때문에 큰문제가 되지 않는다. 그러나 Full GC의 경우 보통 수초가 소요가 되고, Full GC동안에는 Java Application이 멈춰버리기 때문에 문제가 될 수 있다.
예를 들어 게임 서버와 같은 Real Time Server를 구현을 했을때, Full GC가 일어나서 5초동안 시스템이 멈춘다고 생각해보자.
또 일반 WAS에서도 5~10초동안 멈추면, 멈추는동안의 사용자의 Request가 Queue에 저장되었다가 Full GC가 끝난후에 그 요청이 한꺼번에 들어오게 되면 과부하에 의한 여러 장애를 만들 수 있다..
그래서 원할한 서비스를 위해서는 GC를 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 큰 변수로 작용할 수 있다.


4. 다양한 GC 알고리즘


앞에서 설명한 기본적인 GC방법 (Scavenge 와 Mark and compact)이외에 JVM에서는 좀더 다양한 GC 방법을 제공하고 그 동작방법이나 사용방법도 틀리다. 이번에는 다양한 GC 알고리즘에 대해서 알아보자. 현재 (JDK 1.4)까지 나와 있는 JVM의 GC방법은 크게 아래 4가지를 지원하고 있다.

- Default Collector
- Parallel GC for young generation (from JDK 1.4 )
- Concurrent GC for old generation (from JDK 1.4)
- Incremental GC (Train GC)

1) Default Collector
이 GC 방법은 앞에서 설명한 전통적인 GC방법으로 Minor GC에 Scavenge를, Full GC에 Mark & compact 알고리즘을 사용하는 방법이다. 이 알고리즘에는 이미 앞에서 설명했기 때문에 별도의 설명을 하지는 않는다.

JDK 1.4에서부터 새로 적용되는 GC방법은 Parallel GC와 Concurrent GC 두가지 방법이 있다. Parallel GC는 Minor GC를 좀더 빨리하게 하는 방법이고 (Throughput 위주) Concurrent GC는 Full GC시에 시스템의 멈춤(Pause)현상을 최소화하는 GC방법이다.

2) Parallel GC
JDK1.3까지 GC는 하나의 Thread에서 이루어진다. Java가 Multi Thread환경을 지원함에도 불구하고, 1 CPU에서는 동시에 하나의 Thread만을 수행할 수 밖에 없기때문에, 예전에는 하나의 CPU에서만 GC를 수행했지만, 근래에 들어서 하나의 CPU에서 동시에 여러개의 Thread를 실행할 수 있는 Hyper Threading기술이나, 여러개의 CPU를 동시에 장착한 HW의 보급으로 하나의 HW Box에서 동시에 여러개의 Thread를 수행할 수 있게 되었다.

JDK 1.4부터 지원되는 Parallel GC는 Minor GC를 동시에 여러개의 Thread를 이용해서 GC를 수행하는 방법으로 하나의 Thread를 이용하는것보다 훨씬 빨리 GC를 수행할 수 있다.


<그림 7. Parallel GC 개념도>


<그림 7> 을 보자 왼쪽의 Default GC방법은 GC가 일어날때 Thread들이 작업을 멈추고, GC를 수행하는 thread만 gc를 수행한다. (그림에서 파란영역), Parallel GC에서는 여러 thread들이 gc를 수행이 가능하기 때문에, gc에 소요되는 시간이 낮아진다.

Parallel GC가 언제나 유익한것은 아니다. 앞에서도 말했듯이 1CPU에서는 동시에 여러개의 thread를 실행할 수 없기 때문에 오히혀 Parallel GC가 Default GC에 비해서 느리다. 2 CPU에서도 Multi thread에 대한 지원이나 계산등을 위해서 CPU Power가 사용되기 때문에, 최소한 4CPU의 256M 정도의 메모리를 가지고 있는 HW에서 Parallel GC가 유용하게 사용된다.

Parallel GC는 크게 두가지 종류의 옵션을 가지고 있는데,Low-pause 방식과 Throughput 방식의 GC방식이 있다.

Solaris 기준에서 Low-pause Parallel GC는 ?XX:+UseParNewGC 옵션을 사용한다. 이 모델은 Old 영역을 GC할때 다음에 설명할 Concurrent GC방법과 함께 사용할 수 있다. 이 방법은 GC가 일어날때 빨리 GC하는것이 아니라 GC가 발생할때 Application이 멈춰지는 현상(pause)를 최소화하는데 역점을 뒀다.

Throughput 방식의 Parallel GC는 ?XX:+UseParallelGC (Solaris 기준) 옵션을 이용하며 Old 영역을 GC할때는 Default GC (Mark and compact)방법만을 사용하도록 되어 있다.Minor GC가 발생했을때, 되도록이면 빨리 수행하도록 throughput에 역점을 두었다.

그외에도 ParallelGC를 수행할때 동시에 몇개의 Thread를 이용하여 Minor영역을 Parallel GC할지를 결정할 수 있는데, -XX:ParallelGCThreads= 옵션을 이용하여 Parallel GC에 사용되는 Thread의 수를 지정할 수 있다.

3) Concurrent GC

앞에서도 설명했듯이, Full GC즉 Old 영역을 GC하는 경우에는 그 시간이 길고 Application이 순간적으로 멈춰버리기 때문에, 시스템 운용에 문제가 된다.

그래서 JDK1.4부터 제공하는 Concurrent GC는 기존의 이런 Full GC의 단점을 보완하기 위해서 Full GC에 의해서 Application이 멈추어 지는 현상을 최소화 하기 위한 GC방법이다.
Full GC에 소요되는 작업을 Application을 멈추고 진행하는것이 아니라, 일부는 Application이 돌아가는 단계에서 수행하고, 최소한의 작업만을 Application이 멈췄을때 수행하는 방법으로 Application이 멈추는 시간을 최소화한다.


<그림 8. Concurrent GC 개념도>


그림 8에서와 같이 Application이 수행중일때(붉은 라인) Full GC를 위한 작업을 수행한다. (Sweep,mark) Application을 멈추고 수행하는 작업은 일부분 (initial-mark, remark 작업)만을 수행하기 때문에, 기존 Default GC의 Mark & Sweep Collector에 비해서 Application이 멈추는 시간이 현저하게 줄어든다.

Solaris JVM에서는 -XX:+UseConcMarkSweepGC Parameter를 이용해 세팅한다.

4) Incremental GC (Train GC)

Incremental GC또는 Train GC라고도 불리는 GC방법은 JDK 1.3에서부터 지원된 GC방법이다. 앞에서 설명한 Concurrent GC와 비슷하게, 의도 자체는 Full GC에 의해서 Application이 멈추는 시간을 줄이고자 하는데 있다.

Incremental GC의 작동방법은 간단하다. Minor GC가 일어날때 마다 Old영역을 조금씩 GC를 해서 Full GC가 발생하는 횟수나 시간을 줄이는 방법이다.


<그림 9. Incremental GC 개념도>


그림 9에서 보듯이. 왼쪽의 Default GC는 FullGC가 일어난후에나 Old 영역이 Clear된다. 그러나, 오른쪽의 Incremental GC를 보면 Minor GC가 일어난후에, Old 영역이 일부 Collect된것을 볼 수 있다.

Incremental GC를 사용하는 방법은 JVM 옵션에 ?Xinc 옵션을 사용하면 된다.
Incremental GC는 많은 자원을 소모하고, Minor GC를 자주일으키고, 그리고 Incremental GC를 사용한다고 Full GC가 없어지거나 그 횟수가 획기적으로 줄어드는 것은 아니다. 오히려 느려지는 경우가 많다. 필히 테스트 후에 사용하도록 하자.

※ Default GC이외의 알고리즘은 Application의 형태나 HW Spec(CPU수, Hyper threading 지원 여부), 그리고 JVM 버전(JDK 1.4.1이냐 1.4.2냐)에 따라서 차이가 매우 크다. 이론상으로는 실제로 성능이 좋아보일 수 있으나, 운영환경에서는 여러 요인으로 인해서 기대했던것만큼의 성능이 안나올 수 있기 때문에, 실환경에서 미리 충분한 테스트를 거쳐서 검증한후에 사용해야 한다.


5. GC 로그는 어떻게 수집과 분석


JVM에서는 GC 상황에 대한 로그를 남기기 위해서 옵션을 제공하고 있다.
Java 옵션에 ?verbosegc 라는 옵션을 주면되고 HP Unix의 경우 ?verbosegc ?Xverbosegc 옵션을 주면 좀더 자세한 GC정보를 얻을 수 있다. GC 정보는 stdout으로 출력이 되기 때문에 “>” redirection등을 이용해서 file에 저장해놓고 분석할 수 있다.

Example ) java ?verbosegc MyApplication

그럼 실제로 나온 GC로그를 어떻게 보는지를 알아보자.


<그림 5. 일반적인 GC 로그, Windows, Solaris>


<그림 5>는 GC로그 결과를 모아논 내용이다. (실제로는 Application의 stdout으로 출력되는 내용과 섞여서 출력된다.)
Minor GC는 ”[GC “로 표기되고, Full GC는 “[Full GC”로 표기된다.
그 다음값은 Heap size before GC인데,GC 전에 Heap 사용량 ( New/Young 영역 + Old 영역 + Perm 영역)의 크기를 나타낸다.

Heap size after GC는 GC가 발생한후에 Heap의 사용량이다. Minor GC가 발생했을때는 Eden과 Survivor 영역으 GC가 됨으로 Heap size after GC는 Old영역의 용량과 유사하다.(Minor GC에서 GC되지 않은 하나의 Survivor영역내의 Object들의 크기도 포함해야한다.)

Total Heap Size는 현재 JVM이 사용하는 Heap Memory양이다. 이 크기는 Java에서 ?ms와 ?mx 옵션으로 조정이 가능한데. 예를 들어 ?ms512m ?mx1024m로 해놓으면 Java Heap은 메모리 사용량에 따라서 512~1024m사이의 크기에서 적절하게 늘었다 줄었다한다. (이 늘어나는 기준과 줄어드는 기준은 (-XX:MaxHeapFreeRatio와 ?XX:MinHeapFreeRation를 이용해서 조정할 수 있으나 JVM vendor에 따라서 차이가 나기때문에 각 vendor별 JVM 메뉴얼을 참고하기 바란다.) Parameter에 대한 이야기는 추후에 좀더 자세히하도록 하자.

그 다음값은 GC에 소요된 시간이다.

<그림 5>의 GC로그를 보면 Minor GC가 일어날때마다 약 20,000K 정도의 Collection이 일어난다. Minor GC는 Eden과 Suvivor영역 하나를 GC하는 것이기 때문에 New/Young 영역을 20,000Kbyte 정도로 생각할 수 있다.

Full GC때를 보면 약44,000Kbyte에서 1,749Kbyte로 GC가 되었음을 볼 수 있다. Old영역에 큰 데이타가 많지 않은 경우이다. Data를 많이 사용하는 Application의 경우 전체 Heap이 512이라고 가정할때, Full GC후에도 480M정도로 유지되는 경우가 있다. 이런 경우에는 실제로 Application에서 Memory를 많이 사용하고 있다고 판단할 수 있기 때문에 전체 Heap Size를 늘려줄 필요가 있다.

이렇게 수집된 GC로그는 다소 보기가 어렵기 때문에, 좀더 쉽게 분석할 수 있게 하기 위해서 GC로그를 awk 스크립트를 이용해서 정제하면 분석이 용이하다.


<표 2. gc.awk 스크립트>


이 스크립트를 작성한후에 Unix의 awk 명령을 이용해서

% awk ?f gc.awk GC로그파일명

을 쳐주면 아래<표 3>와 같이 정리된 형태로 GC 로그만 추출하여 보여준다.


<표 3. gc.awk 스크립트에 의해서 정재된 로그>


Minor와 Major는 각각 Minor GC와 Full GC가 일어날때 소요된 시간을 나타내며, Alive는 GC후에 남아있는 메모리양, 그리고 Freed는 GC에 의해서 collect된 메모리 양이다.

이 로그파일은 excel등을 이용하여 그래프등으로 변환해서 보면 좀더 다각적인 분석이 가능해진다.

※ JDK 1.4에서부터는 ?XX:+PrintGCDetails 옵션이 추가되어서 좀더 자세한 GC정보를 수집할 수 있다.


※ HP JVM의 GC Log 수집

HP JVM은 전체 heap 뿐 아니라 ?Xverbosegc 옵션을 통해서 Perm,Eden,Old등의 모든 영역에 대한 GC정보를 좀더 정확하게 수집할 수 있다.

Example ) java ?verbosegc ?Xverbosegc MyApplication ß (HP JVM Only)

HP JVM의 GC정보는 18개의 필드를 제공하는데 그 내용을 정리해보면 <표 4.>와 같다.

<GC : %1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11 %12 %13 %14 %15 %16 %17 %18>


<표 4. HP JVM GC 로그 필드별 의미>


이 로그를 직접 보면서 분석하기는 쉽지가 않다. 그래서, HP에서는 좀더 Visual한 환경에서 분석이 가능하도록 HPJtune이라는 툴을 제공한다. 다음 URL에서 다운로드 받을 수 있다.

http://www.hp.com/products1/unix/java/java2/hpjtune/index.html


<그림 6. HP Jtune을 이용해서 GC후 Old영역의 변화 추이를 모니터링하는 화면>




6. GC 관련 Parameter


GC관련 설정값을 보기전에 앞서서 ?X와 ?XX 옵션에 대해서 먼저 언급하자. 이 옵션들은 표준 옵션이 아니라, 벤더별 JVM에서 따로 제공하는 옵션이기 때문에, 예고 없이 변경되거나 없어질 수 있기 때문에, 사용전에 미리 JVM 벤더 홈페이지를 통해서 검증한다음에 사용해야한다.

1) 전체 Heap Size 조정 옵션

전체 Heap size는 ?ms와 ?mx로 Heap 사이즈의 영역을 조정할 수 있다. 예를 들어 ?ms512m ?mx 1024m로 설정하면 JVM은 전체 Heap size를 application의 상황에 따라서 512m~1024m byte 사이에서 사용하게 된다. 그림2의 Total heap size

메모리가 모자를때는 heap을 늘리고, 남을때는 heap을 줄이는 heap growing과 shirinking 작업을 수행하는데, 메모리 변화량이 큰 애플리케이션이 아니라면 이 min heap size와 max heap size는 동일하게 설정하는 것이 좋다. 일반적으로 1GB까지의 Heap을 설정하는데에는 문제가 없으나, 1GB가 넘는 대용량 메모리를 설정하고자 할 경우에는 별도의 JVM 옵션이 필요한 경우가 있기때문에 미리 자료를 참고할 필요가 있다.

※ IBM AIX JVM의 경우
%export LDR_CNTRL=MAXDATA=0x10000000
%java -Xms1500m -Xmx1500m MyApplication

2) Perm size 조정 옵션

Perm Size는 앞에서도 설명했듯이, Java Application 자체(Java class etc..)가 로딩되는 영역이다. J2EE application의 경우에는 application 자체의 크기가 큰 편에 속하기 때문에, Default로 설정된 Perm Size로는 application class가 loading되기에 모자른 경우가 대부분이기 때문에, WAS start초기나, 가동 초기에 Out Of Memory 에러를 유발하는 경우가 많다.

PermSize는 -XX:MaxPermSize=128m 식으로 지정할 수 있다.
일반적으로 WAS에서 PermSize는 64~256m 사이가 적절하다.

3) New 영역과 Old 영역의 조정New 영역은 ?XX:NewRatio=2 에 의해서 조정이 된다.
NewRatio Old/New Size의 값이다. 전체 Heap Size가 768일때, NewRatio=2이면 New영역이 256m, Old 영역이 512m 로 설정이 된다.
JVM 1.4.X에서는 ?XX:NewSize=128m 옵션을 이용해서 직접 New 영역의 크기를 지정하는 것이 가능하다.

4) Survivor 영역 조정 옵션
-XX:SurvivorRatio=64 (eden/survivor 의 비율) :64이면 eden 이 128m일때, survivor영역은 2m가 된다.

5) -server와 ?client 옵션
JVM에는 일반적으로 server와 client 두가지 옵션을 제공한다.
결론만 말하면 server 옵션은 WAS와 같은 Server환경에 최적화된 옵션이고, client옵션은 워드프로세서와 같은 client application에 최적화된 옵션이다. 그냥 언뜻 보기에는 단순한 옵션 하나로보일 수 있지만, 내부에서 돌아가는 hotspot compiler에 대한 최적화 방법과 메모리 구조자체가 아예 틀리다.

○ -server 옵션

server용 application에 최적화된 옵션이다. Server application은 boot up 시간 보다는 user에 대한 response time이 중요하고, 많은 사용자가 동시에 사용하기 때문에 session등의 user data를 다루는게 일반적이다. 그래서 server 옵션으로 제공되는 hotspot compiler는 java application을 최적화 해서 빠른 response time을 내는데 집중되어 있다.

또한 메모리 모델 역시, 서버의 경우에는 특정 사용자가 서버 운영시간동안 계속 서버를 사용하는게 아니기 때문에 (Login하고, 사용한 후에는 Logout되기 때문에..) 사용자에 관련된 객체들이 오래 지속되는 경우가 드물다. 그래서 상대적으로 Old영역이 작고 New 영역이 크게 배정된다. <그림 7. 참조 >

○ -client 옵션

client application은 워드프로세서 처럼 혼자 사용하는 application이다. 그래서 client application은 response time보다는 빨리 기동되는데에 최적화가 되어 있다. 또한대부분의 client application을 구성하는 object는GUI Component와 같이 application이 종료될때까지 남아있는 object의 비중이 높기 때문에 상대적으로 Old 영역의 비율이 높다.


<그림 7. ?server와 ?client 옵션에 따른 JVM Old와 New영역>


이 두옵션은 가장 간단한 옵션이지만, JVM의 최적화에 아주 큰부분을 차지하고 있는 옵션이기 때문에, 반드시 Application의 성격에 맞춰서 적용하기 바란다.
(※ 참고로, SUN JVM은 default가 client, HPJVM는 default가 server로 세팅되어 있다.)

○ GC 방식에 대한 옵션

GC 방식에 대한 옵션은 앞에서도 설명했지만, 일반적인 GC방식이외에, Concurrent GC,Parallel GC,Inceremental GC와 같이 추가적인 GC Algorithm이 존재한다. 옵션과 내용은 앞장에서 설명한 “다양한 GC알고리즘” 을 참고하기 바란다.


7.JVM GC 튜닝


그러면 이제부터 지금까지 설명한 내용을 기반으로 실제로 JVM 튜닝을 어떻게 하는지 알아보도록 하자.

STEP 1. Application의 종류와 튜닝목표값을 결정한다.

JVM 튜닝을 하기위해서 가장 중요한것은 JVM 튜닝의 목표를 설정하는것이다. 메모리를 적게 쓰는것이 목표인지, GC 횟수를 줄이는것이 목표인지, GC에 소요되는시간이 목표인지, Application의 성능(Throughput or response time) 향상인지를 먼저 정의한후에. 그 목표치에 근접하도록 JVM Parameter를 조정하는것이 필요하다.

STEP 2. Heap size와 Perm size를 설정한다.

-ms와 ?mx 옵션을 이용해서 Heap Size를 정한다. 일반적으로 server application인 경우에는 ms와 mx 사이즈를 같게 하는것이 Memory의 growing과 shrinking에 의한 불필요한 로드를 막을 수 있어서 권장할만하다.

ms와mx사이즈를 다르게 하는 경우는 Application의 시간대별 memory 사용량이 급격하게 변화가 있는 Application에 효과적이다.
PermSize는 JVM vendor에 따라 다소 차이가 있으나 일반적으로 16m정도이다. Client application의 경우에는 문제가 없을 수 있지만, J2EE Server Application의 경우 64~128m 사이로 사용이 된다.

Heap Size와 Perm Size는 아래 과정을 통해서 적정 수치를 얻어가야한다.

STEP 3. 테스트 & 로그 분석.

JVM Option에 GC 로그를 수집하기 위한 ?verbosegc 옵션을 적용한다. (HP의 경우 ?Xverbosegc 옵션을 적용한다.)

LoadRunner나 MS Strest(무료로 MS社의 홈페이지에서 다운로드 받을 수 있다.)와 같은 Strest Test툴을 통해서 Application에 Strest를 줘서. 그 log를 수집한다. 튜닝에서 있어서 가장 중요한것은 목표산정이지만, 그만큼이나 중요한것은 실제 Tuning한 Parameter가 Application에 어떤 영향을 주는지를 테스트하는 방법이 매우 중요하다. 그런 의미에서 적절한 Strest Tool의 선정과, Strest Test 시나리오는 정확한 Tuning을 위해서 매우 중요한 요인이다.

○ Perm size 조정
아래 그림8.은 HP JVM에서 ?Xverbosegc 옵션으로 수집한 GC log를 HP Jtune을 통해서 graph로 나타낸 그래프이다. 그림을 보면 Application이 startup되었을때 Perm 영역이 40m에서. 시간이 지난후에도 50m 이하로 유지되는것을 볼 수 있다. 특별하게 동적 classloading등이 수십m byte가 일어나지 않는등의 큰 변화요인이 없을때, 이 application의 적정 Perm 영역은 64m로 판단할 수 있다.


<그림 8. GC 결과중 Perm 영역 그래프>


○ GC Time 수행 시간 분석

다음은 GC에 걸린 시간을 분석해보자. 앞에 강좌 내용에서도 설명햇듯이. GC Tuning에서 중요한 부분중 하나가 GC에 소요되는 시간 특히 Full GC 시간이다.

지금부터 볼 Log는 모社의 물류 시스템의 WAS 시스템 GC Log이다. HP JVM을 사용하며, -server ?ms512m ?mx512m 옵션으로 기동되는 시스템이다.

그림 9를 보면 Peak 시간 (첫번째 동그라미) 14시간동안에 Full GC(동그란점)가 7번일어난것을 볼 수 있다. 각각에 걸린 시간은2.5~6sec 사이이다.
여기서 STEP 1.에서 설정한 AP Tuning의 목표치를 참고해야하는데.

Full GC가 길게 일어나서 Full GC에 수행되는 시간을 줄이고자 한다면 Old 영역을 줄이면 Full GC가 일어나는 횟수는 늘어나고, 반대로 Full GC가 일어나는 시간을 줄어들것이다.

반대로 Full GC가 일어나는 횟수가 많다면, Old 영역을 늘려주면 Full GC가 일어나는 횟수는 상대적으로 줄어들것이고 반대로 Full GC 수행시간이 늘어날 것이다.

특히 Server Application의 경우Full GC가 일어날때는 JVM자체가 멈춰버리기 때문에, 그림 9의 instance는 14시간동안 총 7번 시스템이 멈추고, 그때마다 2.5~6sec가량 시스템이 response를 못하는 상태가 된것이다. 그래서 멈춘 시간이 고객이 납득할만한 시간인지를 판단해야 하고, 거기에 적절한 Tuning을 해야한다.

Server Application에서 Full GC를 적게일어나게하고, Full GC 시간을 양쪽다 줄이기 위해서는 Old영역을 적게한후에, 여러개의 Instance를 동시에 뛰어서 Load Balancing을 해주면, Load가 분산되기 때문에 Full GC가 일어나는 횟수가 줄어들테고, Old 영역을 줄였기 때문에, Full GC에 드는 시간도 줄어들것이다. 또한 각각의 FullGC가 일어나는동안 하나의 서버 instance가 멈춰져 있어도, Load Balancing이 되는 다른 서버가 response를 하고 있기때문에, Full GC로 인한 Application이 멈추는것에 의한 영향을 최소화할 수 있다.


<그림 9. GC 소요시간>


데이타에 따라서 GC Tuning을 진행한후에는 다시 Strest Test를 진행해서 응답시간과 TPS(Throughput Per Second)를 체크해서 어떤 변화를 주었는지를 반드시 체크해봐야한다.


<그림 10. GC후의 Old 영역>


그림 10은 GC후에 Old 영역의 메모리 변화량을 나타낸다.

금요일 업무시간에 메모리 사용량이 올라가다가. 주말에가서 완만한 곡선을 그리는것을 볼 수 있다. 월요일 근무시간에 메모리 사용량이 매우 많고, 화요일에도 어느정도 메모리 사용량이 있는것을 볼 수 있다. 월요일에 메모리 사용량이 많은것을 볼때, 이 시스템의 사용자들이 월요일에 시스템 사용량이 많을 수 있다고 생각할 수 있고, 또는 다른 주의 로그를 분석해봤을때 이 주만 월요일 사용량이 많았다면, 특별한 요인이나 Application 변경등이 있었는지를 고려해봐야할것이다.

이 그래프만을 봤을때 Full GC가 일어난후에도 월요일 근무시간을 보면 Old 영역이 180M를 유지하고 있는것을 볼 수 있다. 이 시스템의 Full GC후의 Old영역은 80M~180M를 유지하는것을 볼 수 있다. 그래서 이 시스템은 최소 180M이상의 Old 영역을 필요로하는것으로 판단할 수 있다.

STEP 4. Parameter 변경.
STEP 3에서 구한 각 영역의 허용 범위를 기준으로 Old영역과 New 영역을 적절하게 조절한다.
PermSize와 New영역의 배분 (Eden,Survivor)영역등을 조정한다.
PermSize는 대부분 Log에서 명확하게 나타나기 때문에, 크게 조정이 필요가 없고 New영역내의 Eden과 Survivor는 거의 조정하지 않는다. 가장 중요한것은 Old영역과 New 영역의 비율을 어떻게 조정하는가가 관건이다.

이 비율을 결정하면서, STEP1에서 세운 튜닝 목표에 따라서 JVM의 GC Algorithm을 적용한다. GC Algorithm을 결정하는 기본적인 판단 내용은 아래와 같다.



이렇게 Parameter를 변경하면서 테스트를 진행하고, 다시 변경하고 테스트를 진행하는 과정을 거쳐서 최적의 Parameter와 GC Algorithm을 찾아내는것이 JVM의 메모리 튜닝의 이상적인 절차이다.


지금까지 JVM의 메모리 구조와 GC 모델 그리고 GC 튜닝에 대해서 알아보았다.

정리하자면 GC 튜닝은 Application의 구조나 성격 그리고, 사용자의 이용 Pattern에 따라서 크게 좌우 되기때문에, 얼마만큼의 Parameter를 많이 아느냐 보다는 얼마만큼의 테스트와 로그를 통해서 목표 값에 접근하느냐가 가장 중요하다
Posted by tornado
|

http://www.servletsuite.com/tips/cells.htm

 

헐.. TD 를 에디트 하다뉘.. 머리 조타...

 

'JAVA > JSP_Servlet' 카테고리의 다른 글

OSCache / Quartz ... 등등 좋은것들..  (0) 2005.06.03
[펌] Opensource Web Grid  (0) 2005.05.30
Tyrex 이용한 jndi 이용방법..  (2) 2005.05.10
에혀... 문서를 자세히 봐야해...  (0) 2005.05.03
이클립스플러긴  (0) 2005.04.25
Posted by tornado
|

하이버네이트 작업중에...... 새로운 로우가 추가되는 부분에서 계속 오류 ...

쿼리 찍어봤더니..

Insert  문이 아닌.. Update 문을 신나게 찍고 있다 -.-

왜그럴까... 소스 한참 보다가 보니...

Util 클래스에서.. Wrapper 클래스가 null 이면.. 0 으로 바꿔주는 부분이 있다..

하여간 PK 부분이 null 이 아니고... 0 으로 들어가니... update xx  set kkk = ? where DOC_NO = ?

으로 계속 업데이트를 하고 앉아있지 ㅡㅡ

 

many-to-one 으로 지정된 클래스에서 이렇게 됨 ㅡㅡ;

 

괜히 무게잡구 담배만 빨았네 ㅡㅡ;

Posted by tornado
|

잼있네요.^^ 제소스를 조금 테스트 해보니 후두둑 쏟아지는 버그들 -_-..

근데 여러번 느끼지만, 외국은 맥 정말 많이 쓰네요. 개발자들도.. 냐옹~

PMD로 버그 잡기
목차:
PMD의 설치와 실행
결과 분석
규칙
자신의 규칙 세트 구현하기
결론
참고자료
필자소개
기사에 대한 평가
관련자료:
FindBugs, Part 1: Improve the quality of your code
FindBugs, Part 2: Writing custom detectors
Diagnosing Java code: Unit tests and automated code analysis working together
Subscriptions:
dW newsletters
손쉬운 정적 분석 툴로 버그 잡기

난이도 : 중급

Elliotte Rusty Harold
조교수, Polytechnic University
2005년 1 월 07 일

소스, 정적 분석 툴인 PMD는 버그를 잡기위한 툴로 손색이 없다. PMD의 사용법을 설명한다.

Tom Copeland의 PMD는 오픈 소스(BSD 라이센스) 툴로서 자바 소스 코드를 분석하여 잠재적인 버그를 찾아낸다. 일반적인 부분에서는 FindBugs와 Lint4j(참고자료)같은 툴과 비슷하다. 하지만 이 모든 툴들은 다른 버그들을 찾아내기 때문에 주어진 코드 기반에서 이들 각자를 실행하는 것이 적합하다. 이 글에서 PMD를 사용하는 방법과 활용법을 설명하겠다. PMD의 명령행 인터페이스도 연구할 예정이다. PMD를 Ant와 통합하여 자동 소스-코드 체크를 수행할 수 있고 주요 IDE와 프로그래머의 에디터를 위한 플러그인도 있다.

PMD의 설치와 실행
PMD는 자바로 작성되고 JDK 1.3 또는 이후 버전이 필요하다. 명령행을 사용하는 것이 익숙하다면 PMD의 설치와 실행은 단순하다. zip 파일을 다운로드하고(참고자료), /usr 또는 홈 디렉토리에 저장한다. 이 글에서는 /usr에 저장하는 것으로 간주하겠다.

PMD 를 실행하는 가장 쉬운 방법은 pmd.sh 스크립트(Unix/Linux) 또는 pmd.bat 스크립트(Windows)를 호출하는 것이다. 이 스크립트들은 bin 디렉토리 보다는 pmd-2.1/etc에 있다. 스크립트에는 세 개의 명령행 인자들이 있다:

  • 체크 할 .java 파일로 가는 경로
  • 아웃풋 포맷을 가르키는 html 또는 xml 키워드
  • 실행 할 규칙 세트 이름들

예를 들어, 다음 명령어는 네이밍 규칙 세트를 사용하여 ImageGrabber.java 파일을 검사하여 XML 아웃풋을 만든다:

$ /usr/pmd-2.1/etc/pmd.sh ImageGrabber.java xml rulesets/naming.xml

결과 분석
위 명령어에서 나온 아웃풋은 기본적으로 System.out으로 보내지며 리포트 형식을 취한다(Listing 1):

Listing 1. PMD XML 리포트
 <?xml version="1.0"?><pmd> <file name="/Users/elharo/src/ImageGrabber.java"> <violation line="32" rule="ShortVariable" ruleset="Naming Rules" priority="3"> Avoid variables with short names like j </violation> <violation line="105" rule="VariableNamingConventionsRule" ruleset="Naming Rules" priority="1"> Variables that are not final should not contain underscores (except for underscores in standard prefix/suffix). </violation> </file> <error filename="/Users/elharo/src/ImageGrabber.java" msg="Error while processing /Users/elharo/ImageGrabber.java"/> </pmd>

Listing 1에서 PMD 가 두 개의 문제들을 발견했음을 알 수 있다: ImageGrabber.java의 32번째 줄에서 짧은 변수 이름과 105번째 줄에서 밑줄을 포함하는 이름이 그것이다. 작은 문제인 것 처럼 보이지만 결과는 엄청날 수 있다. 이 경우, 105번째 줄의 밑줄은 10년 묵은 코드의 그저 픽스하기 쉬운 코드의 단편이였다. 하지만 첫 번째 문제를 검사해보면 j 변수를 완전히 제거할 수 있었다는 것을 깨닫게 된다. 왜냐하면 각각 증가되고 있었던 또 다른 변수의 기능들을 중복시키기 때문이다. 프로그램은 작동했지만 앞으로의 변화에 대비해야 하는 것보다는 훨씬 더 위험한 것이었다. 여러분이 제거한 모든 코드 라인들은 버그가 들어올 수 있는 하나의 작은 장소이다.

PMD 아웃풋을 파일로 리다이렉트 하거나 일반적인 방식으로 에디터로 전달(pipe)할 수 있다. 나는 HTML로 아웃풋을 만들어서 이것을 웹 브라우저에 로딩해 보곤 한다. (그림 1)

그림 1. PMD 아웃풋(HTML)
PMD sample output

아웃풋을 파일로 보내는 것은 소스 트리를 검사할 때 특히 유용하다. 디렉토리 이름, zip 파일, JAR 아카이브 파일을 첫 번째 인자로서 전달한다면 PMD 는 그 디렉토리 또는 아카이브에 있는 모든 .java 파일을 반복적으로 검사한다. 소량의 아웃풋이 위협적이다. 특히 PMD가 많은 오류 가능성을 만들어 낼 때 그렇다. 예를 들어 XOM 코드 베이스(참고자료)에서 PMD를 실행할 때, "in과 같은 짧은 이름을 가진 변수를 피하라(Avoid variables with short names like in" 는 보고를 지속적으로 보낸다. "in"은 InputStream을 나타내는 변수 이름으로서 완벽하다고 생각한다. 그럼에도 불구하고 괜찮은 텍스트 에디터에서 아웃풋을 검사한다면 빈번한 오류를 인식하고 지우는 것이 쉽다는 것을 알게 된다. 이들은 매우 비슷하기 때문이다. 그때 다른 재명명 문제를 픽스 할 수 있다.

PMD에서 유일하게 부족한 한 가지 기능은 "lint comment"를 소스 코드에 추가하여 명백히 위험한 연산을 수행한다는 것을 나타내는 기능이다. 이것은 기능이지 버그는 아니다. 이것 외에는 PMD는 대체적으로 괜찮다. 예를 들어, 오랜 시간동안 try-catch 블록은 XOM의 다양한 장소에서 발생했다:

try { this.data = data.getBytes("UTF8"); } catch (UnsupportedEncodingException ex) { // All VMs support UTF-8 }

PMD는 이것을 빈 catch 블록으로 플래그를 단다. VM이 UTF-8 인코딩을 인식하지 못한다는 것을 발견하게 될 때 까지는 문제가 없을 것처럼 보였다. 그래서 이 블록을 다음과 같이 바꾸고 PMD는 괜찮아졌다:

try { this.data = data.getBytes("UTF8"); } catch (UnsupportedEncodingException ex) { throw new RuntimeException("Broken VM: Does not support UTF-8"); }

규칙
PMD는 16 가지 규칙 세트가 있고 자바 코드의 다양한 일반 문제들을 다룬다. 이중 어떤 것은 문제가 있는 것도 있다:

규칙 이름
명령행에서 전달되는 규칙들의 이름은 문서화가 잘 되어있지 않다. 이들을 파악하려면 여러 시도와 오류 과정을 거쳐야 한다. 괄호 안에 있는 이름들을 사용할 수 있다.

  • Basic (rulesets/basic.xml) -- 대부분의 개발자들이 동의하는 규칙: catch 블록들은 비어있어서는 안되고, equals()를 오버라이딩 할 때 마다 hashCode()를 오버라이드한다.

  • Naming (rulesets/naming.xml) -- 표준 자바 네이밍 규약을 위한 테스트: 변수 이름들은 너무 짧아서는 안된다; 메소드 이름은 너무 길어서는 안된다; 클래스 이름은 대문자로 시작해야 하고, 메소드와 필드 이름들은 소문자로 시작해야 한다.

  • Unused code (rulesets/unusedcode.xml) -- 결코 읽히지 않은 프라이빗 필드와 로컬 변수, 접근할 수 없는 문장, 결코 호출되지 않는 프라이빗 메소드 등을 찾기.

  • Design (rulesets/design.xml) -- 다양한 좋은 디자인 원리 체크, 이를 테면: switch 문장은 default 블록을 갖고 있어야 하고, 심하게 중첩된 if 블록은 피해야 하고, 매개변수들은 재할당되어서는 안되며, 더블(double)이 동일함(equality)과 비교되어서도 안된다.

  • Import statements (rulesets/imports.xml) -- 임포트 문장에 대한 작은 문제들 점검. 같은 클래스를 두 번 반입하는 것이나 java.lang에서 클래스를 임포팅하는 것 등.

  • JUnit tests (rulesets/junit.xml) -- 테스트 케이스와 테스트 메소드 관련 특정 문제 검색. 메소드 이름의 정확한 스펠링과 suite() 메소드가 정적이고 퍼블릭인지 여부.

  • Strings (rulesets/string.xml) -- 스트링 관련 작업을 할 때 발생하는 일반적인 문제들 규명. 스트링 리터럴 중복, String 구조체 호출, String 객체에 toString() 호출하기 등.

  • Braces (rulesets/braces.xml) -- for, if, while, else 문장이 괄호를 사용하는지 여부 검사.

  • Code size (rulesets/codesize.xml) -- 과도하게 긴 메소드, 너무 많은 메소드를 가진 클래스, 리팩토링에 대한 유사한 후보들을 위한 테스트.

  • Javabeans (rulesets/javabeans.xml) -- 직렬화 될 수 없는 bean 클래스 같이 JavaBeans 코딩 규약을 위배하는 JavaBeans 컴포넌트 검사.

  • Finalizers -- finalize() 메소드는 자바에서 일반적인 것은 아니기 때문에 사용법에 대한 규칙이 비교적 익숙하지 않다. 이 그룹의 검사는 finalize() 메소드 관련한 다양한 문제들을 찾는다. 이를 테면, 비어있는 finalizer, 다른 메소드를 호출하는 finalize() 메소드 finalize()로의 호출 등이 그것이다.

  • Clone (rulesets/clone.xml) -- clone() 메소드에 대한 규칙: clone()을 오버라이드하는 클래스는 Cloneable을 구현해야 하고, clone() 메소드는 super.clone()을 호출해야 하며, clone() 메소드는 실제로 던지지 않더라도 CloneNotSupportedException을 던지도록 선언되어야 한다.

  • Coupling (rulesets/coupling.xml) -- 클래스들간 과도한 커플링 표시 검색. 지나치게 많은 임포트, supertype 또는 인터페이스가 충분한 곳에서 subclass 유형 사용하기, 너무 적은 필드, 변수, 클래스 내의 리턴 유형 등.

  • Strict exceptions (rulesets/strictexception.xml) -- 예외 테스트: 메소드는 java.lang.Exception을 던지도록 선언되어서는 안되고, 예외는 플로우 제어에 사용되어서는 안되며, Throwable은 잡혀서는 안된다.

  • Controversial (rulesets/controversial.xml) -- 일부 PMD 규칙들은 유능한 자바 프로그래머가 받아들일 수 있는 것들이다. 하지만 어떤 것은 논쟁의 여지가 충분하다. 이 규칙 세트에는 좀더 의심스러운 검사들이 포함되어 있다. 변수에 null 할당하기, 메소드에서 온 다중의 리턴 포인트 sun 패키지에서 임포팅 등이 포함된다.

  • Logging (rulesets/logging-java.xml) -- java.util.logging.Logger를 위험하게 사용하는 경우 검색: 끝나지 않고 정적이지 않은 logger와 한 클래스에 한 개 이상의 logger 등.

명령행에서 이름과 콤마를 분리하여 여러 규칙 세트들을 한번에 검사할 수 있다:

$ /usr/pmd-2.1/etc/pmd.sh ~/Projects/XOM/src html rulesets/design.xml,rulesets/naming.xml,rulesets/basic.xml

자신의 규칙 세트 구현하기
특정 규칙 세트로 종종 검사하고 있다면 이들을 자신만의 규칙 세트 파일로 결합할 수도 있다.(Listing 2) 이 규칙 세트는 기본 규칙, 네이밍 규칙, 디자인 규칙들을 반입한다:

Listing 2. 기본 규칙, 네이밍 규칙, 디자인 규칙들을 반입하는 규칙 세트
 <?xml version="1.0"?> <ruleset name="customruleset"> <description> Sample ruleset for developerWorks article </description> <rule ref="rulesets/design.xml"/> <rule ref="rulesets/naming.xml"/> <rule ref="rulesets/basic.xml"/> </ruleset>

좀더 세분화 된 것을 원한다면 각 세트에서 원하는 개별 규칙들을 선택할 수 있다. 예를 들어 Listing 3은 세 개의 빌트인 세트에서 11 개의 특정 규칙들을 선택한 커스텀 규칙 세트를 보여주고 있다. 큰 코드 기반을 검사하는 것은 많은 시간이 걸리기 때문에 찾고자 하는 특정 문제를 보다 빨리 찾을 수 있다.

Listing 3. 11 가지 특정 규칙을 반입한 규칙 세트
 <?xml version="1.0"?> <ruleset name="specific rules"> <description> Sample ruleset for developerWorks article </description> <rule ref="rulesets/design.xml/AvoidReassigningParametersRule"/> <rule ref= "rulesets/design.xml/ConstructorCallsOverridableMethod"/> <rule ref="rulesets/design.xml/FinalFieldCouldBeStatic"/> <rule ref="rulesets/design.xml/DefaultLabelNotLastInSwitchStmt"/> <rule ref="rulesets/naming.xml/LongVariable"/> <rule ref="rulesets/naming.xml/ShortMethodName"/> <rule ref="rulesets/naming.xml/VariableNamingConventions"/> <rule ref="rulesets/naming.xml/MethodNamingConventions"/> <rule ref="rulesets/naming.xml/ClassNamingConventions"/> <rule ref="rulesets/basic.xml/EmptyCatchBlock"/> <rule ref="rulesets/basic.xml/EmptyFinallyBlock"/> </ruleset>

세트 안에 대부분의 규칙들을 포함시킬 수 있지만 동의하지 않는 것이나 또는 오류 가능성들이 있는 것들은 배제할 수 있다. 예를 들어, XOM은 테이블 검색을 수행할 때 종종 switch 문장을 디폴트 블록 없이 사용한다. 나는 대부분의 디자인 규칙들을 지킬 수 있지만 <exclude name="SwitchStmtsShouldHaveDefault"/> 자식 요소를 디자인 규칙을 반입하는 규칙 요소에 추가하여 default블록을 놓치는 지에 대한 검사를 하지 않는다:

Listing 4. switch 문장이 디폴트를 가져야 한다는 디자인 규칙을 배제한 규칙 세트
 <?xml version="1.0"?> <ruleset name="dW rules"> <description> Sample ruleset for developerWorks article </description> <rule ref="rulesets/design.xml"> <exclude name="SwitchStmtsShouldHaveDefault"/> </rule> </ruleset>

(PMD가 옳다면 대신 디폴트 블록을 추가해야 한다.)

빌트인 규칙에는 제한이 없다. 자바 코드를 작성하고 PMD를 재컴파일 하거나 XPath 식을 작성하여 새로운 규칙을 추가할 수 있다.

결론
(매우 비싼) 빌트인 규칙들을 사용해서 PMD는 코드의 실제 문제들을 반드시 찾아낸다. 이들 중 어떤 것은 미미하지만 어떤 것은 그렇지 않다. 모든 버그를 찾는 것은 아니다. 단위 테스트와 수락 테스트를 수행해야 한다. 또한 PMD는 알려진 버그를 잡을 때 훌륭한 디버거용 대체도 아니다. 하지만 알지 못했던 버그를 찾을 때 빛을 발한다. PMD가 문제를 찾을 수 없었던 코드 베이스를 보지 못했다. PMD는 싸고 쉬우며 재미있는 방식으로 프로그램을 향상시킨다. 전에 PMD를 사용하지 않았다면 시도해보라.

 

출처 : http://www-128.ibm.com/developerworks/kr/library/j-pmd/#N101F9

 

'JAVA > JSE' 카테고리의 다른 글

Comparable 인터페이스 이용해서 Object sort 하기..  (0) 2005.05.25
[펌] JVM GC와 메모리 튜닝  (0) 2005.05.18
FileFilter... 까먹기 싫어~~~  (0) 2005.02.12
[nio] Channels 클래스  (0) 2005.01.17
[펌] Sorting Algorithm  (0) 2005.01.15
Posted by tornado
|

proxool  커넥션 풀 소스를 보다가... jndi 를 이용하는 다른 방법이 보이길래 끄적여 봄...

 

http://tyrex.sourceforge.net/  <-- 요기에 보면 여러가지 프로젝들이 보인다..

그중... 설명은 안나온것 같은데.. 톰캣 처럼.. jndi 를 불편하게 사용해야 할 경우(반드시 server.xml 에 적어줘야 하며, 서버 재시작 필요함..  readonly 환경이라 그렇다고 하넹..)

tyrex-naming 을 이용하면 된다....

현재 버젼은 1.0.2 인가 그렇고..

테스트 한 패키지는 1.0.1 이며 naming 관련 부분만 이용했다.

 

먼저.. 간단한 자바 빈 클래스.. 이 녀석을 jndi 에 등록할것임..

 

package com.javarush.test;


public class TestBean {
   
    private String print;
   
    public TestBean(){ }

    public String getPrint() {
        return print;
    }

    public void setPrint(String print) {
        this.print = print;
    }   
}

-----------------------------------------------

초기화 시켜줄 서블릿...

web.xml 에 당근 등록해야 하고... <load-on-startup>3<load-on-startup>  으로 지정했다.

import java.io.IOException;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.spi.NamingManager;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import tyrex.naming.MemoryContextFactory;
import com.javarush.test.TestBean;

public class InittServlet extends HttpServlet {

    public void destroy() {
        super.destroy();
    }


    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }


    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

    }

    public void init() throws ServletException {
        String alias = "myAliasName";
        String jndiName = "my/testbean";
       
        try{
           
            TestBean bean = new TestBean();
            bean.setPrint("HIHI");
       
         Hashtable env = new Hashtable();
         env.put(Context.INITIAL_CONTEXT_FACTORY, MemoryContextFactory.class.getName());
         env.put(Context.URL_PKG_PREFIXES, "tyrex.naming");
         env.put(Context.PROVIDER_URL, alias);
         Context context = new InitialContext(env);
         context.createSubcontext("my");
         context.bind(jndiName, NamingManager.getObjectInstance(bean, null, null, null));
         context.close();
       
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

---------------------------------------------------------------------

Test 용 JSP

<%@ page contentType="text/html; charset=euc-kr"
 import = "javax.naming.*, java.util.*, com.javarush.test.*, tyrex.naming.*"

%><%

        String alias = "myAliasName";
        String jndiName = "my/testbean";
        Hashtable env = new Hashtable();
        env.put(Context.INITIAL_CONTEXT_FACTORY, MemoryContextFactory.class.getName());
        env.put(Context.URL_PKG_PREFIXES, "tyrex.naming");
        env.put(Context.PROVIDER_URL, alias);

        Context context = new InitialContext(env);
        TestBean bean = (TestBean) context.lookup(jndiName);
        context.close();
       
        out.print(bean.getPrint());

%>

 

 

------------------------------------

첨부된 tyrex-1.0.1_naming.jar 파일은 WEB-INF/lib 에 두고..

실행해 보면

브라우저에 HIHI 가 출력될 것임....

 

 

 

 

 

 

'JAVA > JSP_Servlet' 카테고리의 다른 글

[펌] Opensource Web Grid  (0) 2005.05.30
[펌] TD 를 에디트 하기 ,,,  (0) 2005.05.13
에혀... 문서를 자세히 봐야해...  (0) 2005.05.03
이클립스플러긴  (0) 2005.04.25
자바싱글사인온 .. 오오옷~! 함 해봐야쥐  (0) 2005.04.13
Posted by tornado
|
<Host name="www.mycompany.com" ...>  ...  <Alias>mycompany.com</Alias>  ...</Host>

 

톰캣 설정 중에 Host name Alias 땜쉬.. 잠깐 헤맸네 ㅎㅎ

Posted by tornado
|

[link]spring ide

JAVA/Framework 2005. 4. 27. 19:09
Posted by tornado
|

http://www.junginger.biz/eclipse/index.html

 

메모리 보는것과 RSS 뷰~ 

Posted by tornado
|

http://blog.empas.com/ahnyounghoe   에서 퍼왔습니다~

 

http://www.java201.com/

 

이런데가 다 있었다니...

일단 링크걸어두고 ..

나중에(?) 꼭 전부 봐야쥐 ㅎㅎ

 

 

 

Posted by tornado
|

'JAVA > JSP_Servlet' 카테고리의 다른 글

에혀... 문서를 자세히 봐야해...  (0) 2005.05.03
이클립스플러긴  (0) 2005.04.25
[펌] lucene index browser 오옷~ 00  (2) 2005.03.31
[링크]proxool.. 편리한 풀...  (0) 2005.03.22
[링크]자바로 만든 블로그  (2) 2005.02.11
Posted by tornado
|
Lucene for ASP.NET) 인덱스데이터 브라우저 Luke 소개.. by udanax

  E-mail : udanax@msn.com
  Read : 53

  Date : 2005-03-28(월) 11:47:31
  IP : 211.195.195.xxx

Lucene 인덱스 데이터 브라우저군요.
You can download the source code here (106kB): luke.zip
자동 설치 URL은 http://www.getopt.org/luke/luke.jnlp 입니다.

Posted by tornado
|

Spring 가지고 놀다보니... JdbcDaoSupport 가 눈에 띤다..

자세한 내용은 Spring doc 를 참고하면 되고...   오늘 신경 거슬리게 만든것 하나..

 

Select Count(*) from tbl_xxx  <-- 이 쿼리 날려야 하는데..

도큐먼트에 SqlFunction 이라는 넘을 쓰면 된단다..

해보니... 잘 된다 ^^

 

그런데!!!!!!

 

Select Count(*) from tbl_xxx where xx > ? and yy < ?                 

이넘~!~! 안된다 ㅜㅜ

 

왜 안되지??

파람 넘겨주는 데가 없다.. 아무래도 PreparedStatement 를 생성하는것 같지 않다.

그래서리... API 찾아보니.. 허거걱...

queryForInt(String arg0, Object arg1)  <-- 요런넘이 있네 ^^

 

Object[] params = new Object[]{new Integer(xx), new Integer(yy)};

return getJdbcTemplate().queryForInt(query, params);

 

간단하게 끝남...

그냥 JDBC 코딩 하는것 보다.. 무지 편리함..

Transaction 처리도.. 설정에서 끝남...

    <!--  Transaction Manager -->
 <bean id="transactionManager"
     class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
     <property name="dataSource"><ref bean="dataSource"/></property>
 </bean>

 

   <bean id="xxxService"
     class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    <property name="transactionManager"><ref local="transactionManager" /></property>
    <property name="target"><ref local="xxxServiceTarget" /></property>
    <property name="transactionAttributes">
       <props>
       <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
    <prop key="list*">PROPAGATION_REQUIRED,readOnly</prop>
    <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
    <prop key="create*">PROPAGATION_REQUIRED,-xxxException</prop>
    <prop key="read*">PROPAGATION_REQUIRED,readOnly</prop>
    <prop key="update*">PROPAGATION_REQUIRED</prop> 
    <prop key="delete*">PROPAGATION_REQUIRED</prop>
       </props>
    </property>
   </bean> 

 

이런식으로... 트랜잭션 매니저 셋팅하고... 프락시 셋팅하고..

설정에서... - 표시로 익셉션을 발생시키면.. 된다..

 

근데.. DataSource 를 반드시 이용해야 하는데..

테스트 할때 proxool 을 사용했다...

데이터 소스 없다 ㅡㅡ

소스포지 가서 proxool cvs 보니... dev 에 테스트 있네 ㅡㅡ

소스 받아서 컴팔하고 해서 성공..

 

 

Posted by tornado
|

http://proxool.sourceforge.net/index.html

설정 엄청 편리... 관리 서블릿 제공 ... 무지 단순 -_-;

 

 

Posted by tornado
|

The documented method of obtaining a reference to the WebApplication context is:

Code:
  WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);



Unfortunately, this does not work when using the Struts ContextLoaderPlugIn. The workaround is the following:

Code:
WebApplicationContext webApplicationContext = (WebApplicationContext)
        servletContext.getAttribute(ContextLoaderPlugIn.SERVLET_CONTEXT_PREFIX);



Can the ContextLoaderPlugIn be modified to support the documented method? It should be a simple one-line change and would have saved a lot of time.

포럼 보다가.. 가져옴..

Posted by tornado
|

작년부터 스트럿을 썼는데..  Validator 를 한번도 써보지 않았다 ^^;

 

http://struts.apache.org/userGuide/dev_validator.html

 

여기 보니 상당히 자세히 나왔음..

 

간단하게 몸풀기 예제 하나 적어놔야쥐..

 

준비물 :  commons-validator.jar , jakarta-oro.jar  <-- 필수

나머지 준비물 : vssh 에 필요한 lib 들...

 

1. 폼빈 작성(DynaValidatorForm)

 

<form-bean name="loginForm"
               type="org.apache.struts.validator.DynaValidatorForm">
   <form-property name="id" type="java.lang.String" />
   <form-property name="passwd" type="java.lang.String" />
</form-bean>

 

2. ActionServlet 작성 (검증만 할것이기 때문에 암것도 없다..)

package test;

 

import ........

 

public class LoginAction extends Action {
 
    public ActionForward execute(
        ActionMapping mapping,
        ActionForm form,
        HttpServletRequest req,
        HttpServletResponse res
    )throws IOException, ServletException {

        return mapping.findForward("success");

    }

}

 

 

3. action-mappings 에 등록..

   <action-mappings>
    <action
      path="/login"
      type="test.LoginAction"
      name="loginForm"
      scope="request"
      input="/login.vm"
      unknown="false"
      validate="true"
    >
      <forward
        name="success"
        path="/index.do"
        redirect="true"
      />
     

</action-mappings>

 

4. validator-rules.xml 등록..(첨부자료에 있음..)

 

5. validation.xml 만들기..

<?xml version="1.0" encoding="euc-kr" ?>
<!DOCTYPE form-validation PUBLIC
          "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.0//EN"
          "http://jakarta.apache.org/commons/dtds/validator_1_0.dtd">

<form-validation>
 <formset>
  <form name="loginForm">
   <field property="id" depends="required,minlength,maxlength">
    <arg0  key="아이디" resource="false"/>
    <arg1 name="minlength" key="${var:minlength}" resource="false"/>
    <arg1 name="maxlength" key="${var:maxlength}" resource="false"/>
    <var>
     <var-name>minlength</var-name>
     <var-value>3</var-value>
    </var>
    <var>
     <var-name>maxlength</var-name>
     <var-value>12</var-value>
    </var>    
   </field>
   <field property="passwd" depends="required">
    <arg0 key="비밀번호" resource="false"/>
   </field>
  </form>
 </formset>
</form-validation>

 

6. messageResource_ko.properties 만들기(native2ascii 해줄것)

# Struts Validator Error Messages
errors.required=<Font color="green">{0}</font> 는(은) 반드시 입력하셔야 합니다!!
errors.minlength=<Font color="green">{0}</font> 는(은) 최소 <Font color="red">{1}</font> 자 이상 입력하셔야 합니다!!
errors.maxlength=<Font color="green">{0}</font> 는(은) 최대 <Font color="red">{1}</font> 자 이하 입니다!!

 

7. struts-config.xml 에  messageResource 등록하기..

<message-resources parameter="messageResource" />

 

8. struts-config.xml 에 Validator PlugIn 등록

 <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
  <set-property property="pathnames"
   value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/>
 </plug-in>

 

9. 입력폼 만들기.

##------------------------------

## PRINT ERROR MESSAGE 

##------------------------------

#macro (errorMarkup)
    #if ($errors.exist())
        <ul>
        #foreach ($e in $errors.all )
            <li>$e</li>
        #end
        </ul>
    #end
#end

<table width="100%" border="0" cellspacing="0" cellpadding="0">
  <tr>
    <td>
    #errorMarkup()
    </td>
  </tr>
  <tr>
 <form method="POST"
  name="loginForm"
  action="$link.setRelative("/login.do")" >
    <td width="400" height="200" align="center">     
   <fieldset style="width:95%">
  <legend align="left" align="center" width="100%" >&nbsp;<B>로그인 하세요</B>&nbsp;&nbsp;</legend>
  <table width="95%" border="0" align="center" cellpadding="0" cellspacing="0" bordercolor="#C6C6C6">
          <tr>
            <td width="50%" style="padding-left:6px">ID</td>
   
          <td width="50%" style="padding-left:6px"><input type="text" name="id"  value="$!loginForm.get("id")" /></td>
          </tr>
          <tr>
            <td width="50%" style="padding-left:6px">PWD</td>
   <td width="50%" style="padding-left:6px"><input type="password" name="passwd" value="$!loginForm.get("passwd")" /></td>
          </tr>   
          <tr>
            <td width="50%"style="padding-left:6px" colspan="2">
     <input type="submit" value=" 전 송 " >
   </td>
          </tr>   
        </table>   
   </fieldset>
    </td>
    </form>
  </tr>
</table>

 

편하긴 한데.. 설정 바꾸고.. 리로딩 하고 -.-;;

업로드 되는 곳이면 업로드 하면서 검증하는건지??

 

 

 

 

Posted by tornado
|

   <property name="mappingDirectoryLocations">
     <value>/WEB-INF/hbm</value>
    </property>

 

스프링 + 하이버네이트 사용시에 하이버네이트 설정을 스프링에서 하게 되는데

API 를 보니...

 

public void setMappingDirectoryLocations(Resource[] mappingDirectoryLocations)
Set locations of directories that contain Hibernate mapping resources, like "WEB-INF/mappings".

Can be used to add to mappings from a Hibernate XML config file, or to specify all mappings locally.

See Also:
Configuration.addDirectory(java.io.File)

요런 넘이 있었넹...

몰랐다는 -.-;;

Posted by tornado
|

하이버네이트 보던 도중.. 이런 에러 발생..

정확한 에러는

net.sf.hibernate.LazyInitializationException: Failed to lazily initialize a collection - no session or session was closed
 at net.sf.hibernate.collection.PersistentCollection.initialize(PersistentCollection.java:209)
 at net.sf.hibernate.collection.PersistentCollection.read(PersistentCollection.java:71)
 at net.sf.hibernate.collection.Set.size(Set.java:106)

.

.

.

 

머 대충 이런 메세지다.

 

이 에러가 발생하는 원인은.. mapping 설정 중에 lazy="true" 부분이 있다.

하이버네이트 설명중에 lazy="true" 일 경우.. 객체를 뒤늦게 초기화 한다(?) 라고 하는데..

이게 쿼리는 이미 만들어져있지만 실제 데이터를 꺼내와서 객체에 담는 작업을 나중에 한다는 말인지.. 잘 모르겠다 ㅡ,.ㅡ 

 

예상이지만 하이버네이트의 PersistenceCollection 의 동작방식이 열쇠를 쥐고 있을 것 같다.

 

지금 연습삼아 만들어보는 게시판의 클래스와 Mapping 정보를 보면 이렇다.

   /**
    * @hibernate.set
    *  table="board_file"
    *  lazy="true"
    *  inverse="false"
    *  order-by="idx"
    *  cascade="all"
    *
    * @hibernate.collection-key
    *  column = "parent"
    * @hibernate.collection-one-to-many
    *  class = "com.javarush.board.dto.AttatchFile"
    */
    public Set getAttatchFile() {
        return articleFile;
    }

    public void setAttatchFile(Set attatchFile) {
        this.attatchFile= attatchFile;
    }

 

hbm.xml 파일로 번역되면 아래와 같이 된다.

 

        <set
            name="attatchFile"
            table="board_file"
            lazy="true"
            inverse="false"
            cascade="all"
            sort="unsorted"
            order-by="idx"
        >

              <key
                  column="parent"
              >
              </key>

              <one-to-many
                  class="com.javarush.board.dto.AttatchFile"
              />

        </set>

 

하나의 게시물을 읽어올때... 게시물 고유번호에 대한 자식들(Upload) 을 같이 가져와야 하는데..

이 매핑 정보를 통해 별도의 쿼리 없이 Upload 정보를 담고있는 객체들이 Set 에 입력되어서

게시물 Bean 에 담겨서 반납된다..

 

문제는... lazy="true" 일때.. 하이버네이트 세션이 닫혀있으면.. 객체정보를 가져오지 못한댄다 ㅡㅡ

 

그래서... 세션이 닫히기 전에 객체를 초기화 시켜줘야 했다.

초기화 방법은 간단..

 

Session ss = factory.openSession();

bean = mgr.read(new Integer(게시물고유번호));

 

// Set 의 size() 를 호출하여 객체를 초기화(객체로딩???)시킨다.

bean.getAttatchFile().size(); <-- 요기서 명시적으로 객체를 로딩하는것 같다.

 

ss.close();

 

Iterator iter = bean.getAttatchFile().iterate();

 

while(iter.hasNext()){

  Object = iter.next();

}

 

이렇게 해서 세션이 닫히기 전에 객체들을 가져왔다.

 

만약 위에 코드가 아래처럼 되어있으면 영락없이 LazyInitializationException 가 던져진다

Session ss = factory.openSession();

bean = mgr.read(new Integer(게시물고유번호));

ss.close();

// 세션이 닫힌 상태기 때문에 아래쪽으로는 LazyInitializationException  가 발생한다 ;;

Iterator iter = bean.getAttatchFile().iterate();

 

while(iter.hasNext()){

  Object = iter.next();

}

 

Posted by tornado
|

훔... 하이버네이트만 쓸때는

Query q = sess.creaeQuery("from CustomBean as xxx order by xx asc");

q.setFirstResult(0);

q.setFetchSize(20);

list = query.list();

 

sess.close();

 

이렇게 하면 꼭 mysql 에서 limit 을 쓴것처럼 가져오는데..

 

Spring + Hibernate 를 쓰니까... 이게 좀 애매해 짐..

그래서 HibernateDaoSupport 를 상속한 DAOImpl 클래스 안에다가..

무식하게 시리..

return getHibernateTemplate().find(

    "from XXXBean as xxx order by xxx.desc limit ?,?"

    , new Object[]{ new Integer(first), new Integer(fetchSize) } ) ;

 

이렇게 했는데.. 일단 돌아가기는 원활히 돌아가는데..

기분이 영~~ 찝찝하네...

재미있는건... 코딩이 더 힘들것 같았던 struts + spring + hivernate 가

일단 기본 설정파일이 완성된 후에는 코딩량이 줄었다는 것임 ;;

 

pstmt.setString(1, xxx);

pstmt.setString(2, yyy);

.

.

.

.

pstmt.executeUpdate();

 

이런짓거리 안해두 되니 무지 편리 ㅎㅎㅎ

Posted by tornado
|
Posted by tornado
|

 /**
  * @hibernate.property
  * @hibernate.column name="contents" sql-type="text" not-null="true"
  */
 public String getContents() {
     return contents;
 }

 public void setContents(String contents) {
     this.contents = contents;
 }

 

음.. 매녈에 없어서 메일링에서 보구 찾았음

 

 

Posted by tornado
|