레퍼런스 : http://blog.naver.com/loves0508/3585185

모든 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를 많이 아느냐 보다는 얼마만큼의 테스트와 로그를 통해서 목표 값에 접근하느냐가 가장 중요하다.

'개발 이야기 > Java Story' 카테고리의 다른 글

Java GC의 원리  (0) 2010.02.18
How to find bottleneck in J2EE application (조대협)  (0) 2010.02.11
Java jstat로 메모리 모니터링  (0) 2010.02.11
iBatis 튜토리얼 문서  (0) 2008.05.15
Web Service 이야기  (0) 2008.05.08
Posted by 서오석
,
우선 간단히 확인하면 아래처럼 나온다. jstat -옵션 -pid -시간 하면 된다.
ex) ~]jstat -gc 16543 1000

저건 pid 16543(java)를 1초에 한번씩 결과값을 보여달란거다.

pid는 ps -efw | grep java로 알아내면 된다.

~] jstat --help

 invalid argument count
Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

Definitions:
  <option>      An option reported by the -options option
  <vmid>        Virtual Machine Identifier. A vmid takes the following form:
                     <lvmid>[@<hostname>[:<port>]]
                Where <lvmid> is the local vm identifier for the target
                Java virtual machine, typically a process id; <hostname> is
                the name of the host running the target Java virtual machine;
                and <port> is the port number for the rmiregistry on the
                target host. See the jvmstat documentation for a more complete
                description of the Virtual Machine Identifier.
  <lines>       Number of samples between header lines.
  <interval>    Sampling interval. The following forms are allowed:
                    <n>["ms"|"s"]
                Where <n> is an integer and the suffix specifies the units as
                milliseconds("ms") or seconds("s"). The default units are "ms".
  <count>       Number of samples to take before terminating.
  -J<flag>      Pass <flag> directly to the runtime system.



옵션은 다음과 같다.

 옵션명 내용 
 class  클래스 로더의 동작에 관한 통계 데이터
 compiler  HotSpot Just-in-Time 컴파일러의 동작에 관한 통계 데이터
 gc  가베지 컬렉트된 heap의 동작에 관한 통계 데이터
 gccapactiy  세대마다의 용량과 대응하는 영역에 관한 통계 데이터
 gccause  가베지 콜렉션 통계 데이터의 개요 (-gcutil 와 같다)와 직전 및 현재 (적용 가능한 경우)의 가베지 콜렉션 이벤트의 원인
 gcnew  New 세대의 동작에 관한 통계 데이터
 gcnewcapacity  New 세대의 사이즈와 대응하는 영역에 관한 통계 데이터
 gcold  Old 세대 및 Permanent 세대의 동작에 관한 통계 데이터
 gcoldcapacity  Old 세대의 사이즈에 관한 통계 데이터
 gcpermcapacity  Permanent 세대의 사이즈에 관한 통계 데이터
 gcutil  가베지 콜렉션 통계 데이터의 개요
 printcompilation HotSpot 컴파일 방법의 통계 데이터

각 옵션에 대한 컬럼에 대한 설명은 다음과 같다.

-class  클래스 로더의 통계 데이터
 Loaded  Bytes  Unloaded  Bytes  Time
 로드 된 클래스의 수  로드 된 K 바이트수  언로드된 클래스의 수  언로드된 K 바이트수  클래스의 로드나 언로드 처리에 필요로 한 시간

-compiler HotSpot Just-In-Time 컴파일러의 통계 데이터
 Compiled  Failed  Invalid  Time  FailedType  FailedMethod
 실행된 컴파일 태스크의 수  실패한 컴파일 태스크의 수  무효로 된 컴파일 태스크의 수  컴파일 태스크의 실행에 필요로 한 시간  마지막에 실패한 컴파일의 컴파일 타입  마지막에 실패한 컴파일의 클래스명과 메소드

-gc 가비지 컬렉트된 heap의 통계 데이터
 컬럼명  설명
 S0C  Survivor 영역 0 의 현재의 용량 (KB)
 S1C  Survivor 영역 1 의 현재의 용량 (KB)
 S0U  Survivor 영역 0 의 사용율 (KB)
 S1U  Survivor 영역 1 의 사용율 (KB)
 EC  Eden 영역의 현재의 용량 (KB)
 EU  Eden 영역의 사용율 (KB)
 OC  Old 영역의 현재의 용량 (KB)
 OU  Old 영역의 사용율 (KB)
 PC  Permanent 영역의 현재의 용량 (KB)
 PU  Permanent 영역의 사용율 (KB)
 YGC  Young 세대의 GC 이벤트수
 YGCT  Young 세대의 가베지 콜렉션 시간
 FGC  풀 GC 이벤트수
 FGCT  풀 가베지 콜렉션 시간
 GCT  가베지 콜렉션의 합계 시간

-gccapacity 메모리프르 세대 및 영역 용량

 컬럼명 설명 
 NGCMN  New 세대의 최소 용량 (KB)
 NGCMX  New 세대의 최대 용량 (KB)
 S0C  Survivor 영역 0 의 현재의 용량 (KB)
 S1C  Survivor 영역 1 의 현재의 용량 (KB)
 EC  Eden 영역의 현재의 용량 (KB)
 OGCMN  Old 세대의 최소 용량 (KB)
 OGCMX  Old 세대의 최대 용량 (KB)
 OGC  Old 세대의 현재의 용량 (KB)
 OC  Old 영역의 현재의 용량 (KB)
 PGCMN  Permanent 세대의 최소 용량 (KB)
 PGCMX  Permanent 세대의 최대 용량 (KB)
 PGC  Permanent 세대의 현재의 용량 (KB)
 PC  Permanent 영역의 현재의 용량 (KB)
 YGC  Young 세대의 GC 이벤트수
 FGC  풀 GC 이벤트수
 NGC  New 세대의 현재의 용량 (KB)

-gcutil 가베지 콜렉션 통계 데이터의 개요
 컬럼명  설명
 S0  Survivor 영역 0 의 사용율 (현재의 용량에 대한 퍼센티지)
 S1  Survivor 영역 1 의 사용율 (현재의 용량에 대한 퍼센티지)
 E  Eden 영역의 사용율 (현재의 용량에 대한 퍼센티지)
 O  Old 영역의 사용율 (현재의 용량에 대한 퍼센티지)
 P  Permanent 영역의 사용율 (현재의 용량에 대한 퍼센티지)
 YGC  Young 세대의 GC 이벤트수
 YGCT  Young 세대의 가베지 콜렉션 시간
 FGC  풀 GC 이벤트수
 FGCT  풀 가베지 콜렉션 시간
 GCT  가베지 콜렉션총시간

-gccause GC 이벤트를 포함한 가베지 콜렉션 통계 데이터(gcutil에 두개 컬럼이 추가됨)
 컬럼명  설명
 LGCC  마지막 가베지 콜렉션의 원인
 GCC  현재의 가베지 콜렉션의 원인

-gcnew New 세대의 통계 데이터
 컬럼명  설명
 S0C  Survivor 영역 0 의 현재의 용량 (KB)
 S1C  Survivor 영역 1 의 현재의 용량 (KB)
 S0U  Survivor 영역 0 의 사용율 (KB)
 S1U  Survivor 영역 1 의 사용율 (KB)
 TT  전당 들어가 귀의치
 MTT  최대 전당 들어가 귀의치
 DSS  적절한 Survivor 사이즈 (KB)
 EC  Eden 영역의 현재의 용량 (KB)
 EU  Eden 영역의 사용율 (KB)
 YGC  Young 세대의 GC 이벤트수
 YGCT  Young 세대의 가베지 콜렉션 시간

-gcold 옵션 Old 및 Permanent 세대의 통계 데이터
 컬럼명 설명 
 PC  Permanent 영역의 현재의 용량 (KB)
 PU  Permanent 영역의 사용율 (KB)
 OC  Old 영역의 현재의 용량 (KB)
 OU  Old 영역의 사용율 (KB)
 YGC  Young 세대의 GC 이벤트수
 FGC  풀 GC 이벤트수
 FGCT  풀 가베지 콜렉션 시간
 GCT  가베지 콜렉션총시간

-gcoldcapacity Old 세대의 통계 데이터
 컬럼명 설명 
 OGCMN  Old 세대의 최소 용량 (KB)
 OGCMX  Old 세대의 최대 용량 (KB)
 OGC  Old 세대의 현재의 용량 (KB)
 OC  Old 영역의 현재의 용량 (KB)
 YGC  Young 세대의 GC 이벤트수
 FGC  풀 GC 이벤트수
 FGCT  풀 가베지 콜렉션 시간
 GCT  가베지 콜렉션총시간

-printcompilation HotSpot 컴파일 방법의 통계 데이터

 컬럼명 설명 
 Compiled  실행된 컴파일 태스크의 수
 Size  메소드의 바이트 코드의 바이트수
 Type  컴파일 타입
 Method  컴파일 방법을 특정하는 클래스명과 메소드명. 클래스명에서는, 이름 공간의 단락 문자로서 「.」(은)는 아니고 「/」이 사용된다. 메소드명은, 지정된 클래스내의 메소드이다. 이러한 2 개의 필드의 형식은, HotSpot -XX:+PrintComplation 옵션과 대응하고 있다


사용 방법

jstat -gcutil -h5 16543 1000 10

jstat를 -gcutil 옵션을 주고 5개씩 출력마다 헤더를 출력(h5)하고 1초마다 출력하되 10개씩 보여준다.

'개발 이야기 > Java Story' 카테고리의 다른 글

How to find bottleneck in J2EE application (조대협)  (0) 2010.02.11
JVM GC와 메모리 튜닝 (조대협)  (0) 2010.02.11
iBatis 튜토리얼 문서  (0) 2008.05.15
Web Service 이야기  (0) 2008.05.08
Xpath Syntax  (0) 2008.05.03
Posted by 서오석
,

레퍼런스 : http://blog.naver.com/whosnext?Redirect=Log&logNo=100037993078
               http://gear4001.tistory.com/17

정렬을 하려면 2가지 인터페이스를 사용할 수 있다.

java.lang.Comparable
java.util.Comparator

1. Arrays.sort()로 sort 하는 법

배열인 경우인 경우는 Arrays.sort(arr);

리스트로 만들어진 경우 Collections.sort(list);

이럼 끝

2. 리스트인데 안에 정렬 기준을 바꾸고 싶은 경우 

리스트 같은 객체들을 인자로 받아서 TreeSet을 만들면 알아서 기본순서로(compareTo에 정의된 대로) 정렬
Set set = new TreeSet(list);

정렬기준을 바꾸고 싶다면 Comparator를 구현한 객체를 생성자로 만든후 리스트를 집어넣어줌

Set set2 = new TreeSet(new YoungOrderComparator());
set2.addAll(list);


예제 소스



3. 중복 제거하고 정렬하기

Treeset 사용

Posted by 서오석
,

회사에서는 iBatis를 주로 사용하는데 급 jdbc로 직접 데이터를 퍼와야 할 일이 생겼다.
(대용량의 데이터를 iBatis로 가져오다보니 메모리 때문에 서버가 뻗어버린다. --;; iBatis의 데이터 처리 문제라서 결국..ㅠㅠ)

근데 하도 오랜만에 jdbc를 써가지구 자꾸 까먹어 여따가 정리해놓는다.

예제는 그냥 멤버테이블에 갯수 가져오는 거다. (자세한 설명은 안한다..;;)



import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;


public class JdbcConnect {

 public static void main(String[] arg){
  String dbUrl = "jdbc:oracle:thin:@255.255.255.255:1521:orcl";           
  String dbUser = "5dol";                                                              
  String dbPassword = "story";
  String sql = null;
  ResultSet rs = null;
  
  Connection conn = null;
  //Statement stmt = null;
  PreparedStatement pstmt = null;
  
  try {
   Class.forName("oracle.jdbc.driver.OracleDriver");  //드라이버 로딩   
   conn = DriverManager.getConnection(dbUrl, dbUser, dbPassword);   
   //stmt = conn.createStatement();
   
   sql = "select count(*) num1 from member";
   
   pstmt = conn.prepareStatement(sql);
   
   rs = pstmt.executeQuery();
   
  while(rs.next()){
   System.out.println(rs.getString("num1"));
  }
   
  rs.close();
  conn.close();
   
  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  } catch (SQLException e) {
   e.printStackTrace();
  }
  
 }
 
}

Posted by 서오석
,
스프링에서 iBatis나 Spring을 설정하고 사용하는 도중 아래와 같은 메시지를 담은 exception을 나타나면

Bean property 'TestDao' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?

이것은 자신이 testDao를 bean으로 설정 한 후 testDao를 사용하는 어떤 클래스에 testDao를 setter injection하지 않았기 때문이다.

간단하게

 public void setTestDao(TestDao testDao) {
  this.testDao = testDao;
 }

같은 setter를 하나 만들어주면 잘 될 것이다.

'개발 이야기 > 삽질의 해답들' 카테고리의 다른 글

"URL" not allowed by Access-Control-Allow-Origin  (0) 2013.08.19
자바로 정렬하기  (0) 2010.02.02
Posted by 서오석
,

Struts2하고 Spring2.0하고 엮어서 presentation Layer를 Struts로 사용하고 Business Layer를 Spring으로 사용하려고 세팅을 하고 Action을 만든 후에 Action에서 Spring bean을 사용하려고 하면 아래와 같은 에러가 발생하는 경우가 있다. 

에러 메시지
이 에러를 고치는데 별 짓을 다 해보다가 결국 struts 설정에 에러가 있다는 걸 알았다.. ㅠㅠ(이틀을 날려버린..--;)

우선 struts랑 spring이랑 붙이려면 라이브러리 파일이 하나 필요하다. (라이브러리가 struts2 버젼과 같아야 한다.)

이 라이브러리를 추가해주고 나서

struts.properties 안에 "struts.objectFactory" 활성화 해 준 후에

struts.objectFactory = org.apache.struts2.spring.StrutsSpringObjectFactory

이렇게 명시해준다. 이건 object를 스프링이 관리해주도록 하는 거이란다.

그리고 중요한 것! 저 에러가나는 원인은

struts.objectFactory.spring.autoWire = name   <- 이 부분 때문에 그렇다.

저 autoWire를 name으로 설정해서 에러가 나는 것이다. 이걸 type으로 바꾸면 된다. 

struts.objectFactory.spring.autoWire = type 

으로 변경해주면 제대로 에러없이 돌아간다.

이거 몰라서 개고생 했다..--;;
Posted by 서오석
,

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.net.SocketException;

import org.apache.commons.net.ftp.FTP;

import org.apache.commons.net.ftp.FTPClient;

import org.apache.commons.net.ftp.FTPReply;

 

public class adminFTP {

    

    private String server = "000.000.000.000";  //파일 업로드 할 서버 IP

    private String username = "DAUM";       //사용자 Id

    private String password = "DAUM";       //패스워드

    private String defaultPath = "/data/";     // 저장할 경로

    

/**

     * 파일을 업로드 해준다.

     * @param filePath  자신의 하드에 있는 파일의 경로를 말한다. 파일 경로랑 파일명까지다. ex: c:\\test.jpg

     * @param destfilePath FTP서버에 업로드할 경로를 말한다.

     *                     상단의 defaultPath로 기본 위치를 잡고 그 뒤에 경로와 파일명까지 붙여서 쓴다.

     * @return

     */

    public boolean upLoad(String filePath, String destfilePath){

        FTPClient ftpClient = new FTPClient();

        ftpClient.setControlEncoding("euc-kr");  

        try {

            ftpClient.connect(server);

            int reply = ftpClient.getReplyCode();

            if (!FTPReply.isPositiveCompletion(reply)) {

                ftpClient.disconnect();

                System.out.println("FTP server refused connection.");

            }  else {

                 System.out.println("Connect successful");

                 ftpClient.setSoTimeout(10000);               

                 ftpClient.login(username, password);

                 ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

                 ftpClient.changeWorkingDirectory(defaultPath);

                 File put_file = new File(filePath);

                 FileInputStream inputStream = new FileInputStream(put_file);

                 boolean result = ftpClient.storeFile(destfilePath, inputStream);

                 System.out.println("FILE TRANSPORT STATUS  :"+result);

                 inputStream.close();

                 ftpClient.logout();

            }

            

        } catch (SocketException e) {

            System.out.println(e);          

            e.printStackTrace();

            return false;

        } catch (IOException e) {

            System.out.println(e);

            e.printStackTrace();

            return false;

        }

        return true;        

    }


Posted by 서오석
,
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.handler.ContextHandlerCollection;
import org.mortbay.jetty.webapp.WebAppContext;

public class Was {
 public static void main(String args[]){

  Server server = new Server();

  SocketConnector connector = new SocketConnector();

  connector.setPort(8000);
  server.setConnectors(new Connector[] { connector });

  ContextHandlerCollection contexts = new ContextHandlerCollection();

  server.setHandler(contexts);

  WebAppContext context = new WebAppContext();
  context.setResourceBase("D:\\workspace\\velocity\\WebRoot");
  context.setContextPath("/");
  contexts.addHandler(context);  

  try {

   server.start();

  } catch(Exception e) {
   e.printStackTrace();
  }
 } 

}
Posted by 서오석
,

Ant로 배포를 한 후 테스트를 할라고 하면 404가 뜨는 경우가 있다. 그래서 htdocs의 배포된 곳에 가면 배포된 파일은 죄다 없고 loader만 딸랑 남는 경우가 있는데 이건 아주 간단한 이유다.

만약 사용자가 배포하는 해당 폴더에 FTP로든 콘솔로든 접근을 해 있는 상태라면 배포가 안된다.
무슨 말인가 하면
test라는 프로젝트를 배포한다고 했을 때
/htdocs/test 가 배포의 위치라고 하자.

근데 만약 consol(z-term or putty)같은 거로 해당 디랙토리에 이미 접근해있다면( 사용자의 위치가 /htdocs/test/sample 라는 폴더에 있다면) 배포를 해도 배포가 안되고 배포된 플젝 폴더 안에는 Loader라는 녀석만 덩그러니 남는다.

간단히 그냥 consol을 종료하던 아니면 배포되는 위치를 벗어난 후 다시 배포를 하면 이상없이 배포가 된다.
Posted by 서오석
,

자바로 메일보내는 폼을 개발할 이유가 있었는데 원래 그냥 Sun에서 지원하는 Mail.jar를 이용한 메일보내기를 쓰다가 더 쉬운 걸 찾아냈다.



위의 3개 파일의 압축을 풀면 Mail.jar, activations.jar, commons-email-1.1.jar 이 나온다.

뭐 각 JAR에 대한 설명은 워낙 인터넷에 마구 떠돌아 다니니까 알아서 찾고 바로 쓰는 법부터 보자

우선 위의 세 jar을 라이브러리에 추가를 한다.

그리고 메일을 보내는 action이 있는 곳에 이렇게 적어보자.

public void sendMail(){

SimpleEmail email = new SimpleEmail(); // 선언을 한다
email.setCharset("UTF-8");     // 인코딩을 설정한다.
email.setHostName("smtp.daum.com");
email.setFrom("5dols.daum.com", "5dols"); //보내는 사람
email.setAuthentication("USER", "PASSWORD"); //SMTP 인증이 필요할 경우(없으면 지워버린다.)
email.addTo("5dols.naver.com", "5dolsstory"); //받는사람
email.setSubject("메일의 제목입니다."); //메일 제목
email.setMsg("메일의 내용입니다."); //메일 내용
email.send(); //메일 발송
}

여기서부터는 펌글




더 자세한 내용은 아래 링크를 따라가자~

http://grooveit.org/blog/?tag=commons-mail
Posted by 서오석
,

소트하고 싶은게 list라면

class EngNameComparator implements Comparator {
  public int compare(Object o1, Object o2) {
    String en1 = ((Customer)o1).engName;
    String en2 = ((Customer)o2).engName;
    return en1.compareTo(en2); // ascending 정렬
  }


위의 형식과 같은 클래스 만들고~ (String 형으로 정렬 때)

class YoungOrderComparator implements Comparator {
  public int compare(Object o1, Object o2) {
    int by1 = ((Customer)o1).birthYear;
    int by2 = ((Customer)o2).birthYear;
    return by1 > by2 ? -1 : (by1 == by2 ? 0 : 1); // descending 정렬.....
  }


이건 int 형으로 정렬 때


그리고 정렬하고 싶은 리스트 가 있는 곳에 이거 붙이면 끝~ (근데 왜케 수행속도가 안나오지..--;)

Collections.sort(list, new EngNameComparator());

Posted by 서오석
,

타일즈에서 extends가 이상하게 안될때는..

toolbox.xml 에 있는 노드의 경로들이 제대로 잘 박혀 있는지 확인해보자.
기본 default tool들은 괜찮은데, 보통 커스텀으로 박아넣은것들에서 오타가
날 확률이 많으니 조심하자.



삽펐던 사연은 아래 자세히..















=============================================================================


어제 타일즈 삽 펀 사연..



스프링이 대새인데.. 스트러츠로 그냥 하던대로 프로젝트 빌딩을 하고 있었다.

다 하고 나니 이상하게

tiles-defs.xml 에 설정한것과 다르게 동작을 했다.


빌딩과정..
struts-config.xml 에 타일즈 설정을 했고..
<plug-in className="org.apache.struts.tiles.TilesPlugin">
  <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml, /WEB-INF/tiles-defs-info.xml" />
  <set-property property="definitions-parser-validate" value="true" />
  <set-property property="moduleAware" value="true" />
 </plug-in>

라고 정상적으로 설정을 했고..




벨로시티로 타일즈를 구성한곳에 박아야 하니..
velocity.properties 에도 정상적으로 잘 등록을 했고..

velocimacro.library = VM_global_library.vm
velocimacro.permissions.allow.inline = true
velocimacro.permissions.allow.inline.to.replace.global = false
velocimacro.permissions.allow.inline.local.scope = false 
velocimacro.context.localscope = false

resource.loader = webapp

webapp.resource.loader.class = org.apache.velocity.tools.view.servlet.WebappLoader
webapp.resource.loader.path = /WEB-INF/template/
webapp.resource.loader.cache = false
webapp.resource.loader.modificationCheckInterval = 60

input.encoding=UTF-8
output.encoding=UTF-8
default.contentType=text/html;charset=utf-8

directive.foreach.counter.name = velocityCount
directive.foreach.counter.initial.value = 1




web.xml 에도 vm파일을 사용할 수 있도록 정상적으로 등록을 하고..
<servlet>
  <servlet-name>velocity</servlet-name>
  <servlet-class>
   org.apache.velocity.tools.view.servlet.VelocityViewServlet
  </servlet-class>
  <init-param>
   <param-name>org.apache.velocity.toolbox</param-name>
   <param-value>/WEB-INF/toolbox.xml</param-value>
  </init-param>
  <init-param>
   <param-name>org.apache.velocity.properties</param-name>
   <param-value>/WEB-INF/velocity.properties</param-value>
  </init-param>
  <load-on-startup>10</load-on-startup>
 </servlet>

<servlet-mapping>
  <servlet-name>velocity</servlet-name>
  <url-pattern>*.vm</url-pattern>
 </servlet-mapping>



toolbox.xml 에도 잘 설정을 한듯했다..


그런데 어쩐지 tiles-defs.xml 과 tiles-defs-info.xml 요렇게 2개로 갈라서
처리하려고 했는데..

 tiles-defs.xml
<tiles-definitions>

 <!-- 1단 레이어  -->
 <definition name="layout_onecolumn.tiles" path="/layout/layout_onecolumn.vm">
  <put name="tab" value="/layout/top_tab.vm" />
  <put name="bottom" value="/layout/bottom.vm" />
 </definition>

 <!-- Blank 레이아웃 -->
 <definition name="layout_blank.tiles" path="/layout/layout_blank.vm" />
  <definition name="blank.tiles" path="/layout/blank2.vm" />

</tiles-definitions>




tiles-defs-info.xml
<tiles-definitions>

  <definition name="survey.top.tiles" extends="blank.tiles">
  <put name="content" value="/top/topContent.vm" />
 </definition>
 
 <definition name="survey.survey.tiles" extends="layout_onecolumn.tiles">
  <put name="content" value="/test/topContent.vm" />
 </definition>

</tiles-definitions>



이렇게 설정해서  blank.tiles를 extends 하려고 했는데.. extends를 하는 놈은
안중에도 없고, tils-defs.xml 에 있는 blank.tiles name을 가진 녀석의
/layout/blank2.vm 파일 내용만 보여지는것이다. extends 한녀석도 같이 박혀야
하는데..

그래서 vm파일을 옮겨도 보고, 바꿔도 보고.. 타일즈 문법이 틀렸는지 확인도 해보며
수십번 삽질하다가..

toolbox.xml 의 어떤 한 노드의 경로에 오타가 있단느것을 발견해서
경로를 제대로 잡아주니, extends 가 안되던 것이 에러가 해결됐다..
<!-- Struts -->
 <tool>
  <key>tiles</key>
  <scope>request</scope>
  <class>org.apache.velocity.tools.struts.TilesTool</class>
 </tool>
 
 
 <!-- Custom -->
 <tool>
  <key>strUtil</key>
  <scope>application</scope>
  <class>net.coader.util.velocity.StringUtilsTool</class>
 </tool>

class의 경로에 오타가 있다거나 틀리면 extends문제가 발생한다.
보통 사용자가 java를 통해 custom tool을 만들때 경로를 집어 넣다가 발생할 수 있는
에러 이다.


이건 뭐 차라리 아예 안되서 에러를 내든지 하면 첨부터 설정을 봤을텐데 말이다..
삽질한 내 시간을 돌려줘 ㅠ_ㅠ 


- 내 사수인 종민님 블로그에서 퍼온 것임 -

'개발 이야기 > 유용한 Coding' 카테고리의 다른 글

Java로 쉽게 메일 보내기 메소드 만들기  (0) 2008.11.17
Collections.sort 로 쉽게 소트하기  (0) 2008.11.17
ANT FTP 에러 해결하는 방법  (1) 2008.07.28
chat Server  (0) 2008.05.14
간단한 ChatClient  (0) 2008.05.14
Posted by 서오석
,
이클립스에서 ant java.lang.NoClassDefFoundError: org/apache/commons/net/ftp/FTPClient

이클립스에서 ANT를 이용해 FTP 로 올릴려고 build.xml 작성하는 도중에 에러발생!!


미친에러 자식..--;

에러 : java.lang.NoClassDefFoundError: org/apache/commons/net/ftp/FTPClient

해결 방법 : commons-net-1.4.0.jar을 이클립스 window - preferences - ant - runtime - classpath - ant home entries에 추가해 준다.

Posted by 서오석
,

iBatis 아파치 홈페이지에가면 한국어로 된 튜토리얼이 있는데 그걸 가져왔습니다.

혹시라도 iBatis 공부하는데 필요한 사람은 다운받아가세용~



이건 iBaits Jar 파일입니다.

'개발 이야기 > Java Story' 카테고리의 다른 글

JVM GC와 메모리 튜닝 (조대협)  (0) 2010.02.11
Java jstat로 메모리 모니터링  (0) 2010.02.11
Web Service 이야기  (0) 2008.05.08
Xpath Syntax  (0) 2008.05.03
Core J2EE Patterns - Business Delegate  (0) 2008.04.26
Posted by 서오석
,

본 코드는 Head first Java의 예제 소스이다.

import java.io.*;
import java.net.*;
import java.util.*;

public class VerySimpleChatServer {
  ArrayList clientOutputStreams;
 
  public class ClientHandler implements Runnable{
   BufferedReader reader;
   Socket sock;
   
   public ClientHandler(Socket clientSocket){
    try{
     sock = clientSocket;
     InputStreamReader isReader = new InputStreamReader(sock.getInputStream());
     reader = new BufferedReader(isReader);
     
    }catch(Exception ex){
     ex.printStackTrace();
    }
   
   }
   
   public void run(){
    String message;
    try{
     while((message = reader.readLine()) != null){
      System.out.println("read "+ message);
      tellEveryone(message);
     }
    }catch(Exception ex){
     ex.printStackTrace();
    }
   
   }
   
  }
  public static void main(String[] args){
   new VerySimpleChatServer().go();
  }
 
  public void go(){
   clientOutputStreams = new ArrayList();
   try{
    ServerSocket serverSock = new ServerSocket(5000);
    while(true){
     Socket clientSocket = serverSock.accept();
     PrintWriter writer = new PrintWriter(clientSocket.getOutputStream());
     clientOutputStreams.add(writer);
     
     Thread t = new Thread(new ClientHandler(clientSocket));
     t.start();
     System.out.println("got a connection");    
    }
   
   }catch(Exception ex){
    ex.printStackTrace();
   }
   
  }
 
  public void tellEveryone(String message){
   Iterator it = clientOutputStreams.iterator();
   while(it.hasNext()){
    try{
     PrintWriter writer = (PrintWriter)it.next();
     writer.println(message);
     writer.flush();
    }catch(Exception ex){
     ex.printStackTrace();
    }
   }
  } 

}

Posted by 서오석
,

아래 코드는 Head first Java의 예제이다.

import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;

import com.sun.corba.se.spi.servicecontext.SendingContextServiceContext;

import java.awt.*;
import java.awt.event.*;

public class SimpleChatclient {
 JTextArea incoming;
 JTextField outgoing;
 BufferedReader reader;
 PrintWriter writer;
 Socket sock;
 
 public static void main(String[] args){
  SimpleChatclient client = new SimpleChatclient();
  client.go();
 }
 
 public void go(){
  JFrame frame = new JFrame("Simple Chat Client");
  JPanel mainPanel = new JPanel();
  incoming = new JTextArea(15,50);
  incoming.setLineWrap(true);
  incoming.setWrapStyleWord(true);
  incoming.setEditable(false);
  JScrollPane qScroller = new JScrollPane(incoming);
  qScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
  qScroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
  outgoing = new JTextField(20);
  JButton sendButton = new JButton("Send");
  sendButton.addActionListener(new SendButtonListener());
  mainPanel.add(qScroller);
  mainPanel.add(outgoing);
  mainPanel.add(sendButton);
  setUpNetworking();
 
  Thread readerThread = new Thread(new IncomingReader());
  readerThread.start();
 
  frame.getContentPane().add(BorderLayout.CENTER, mainPanel);
  frame.setSize(400, 500);
  frame.setVisible(true);  
 }
 
 private void setUpNetworking(){
  try{
   sock = new Socket("127.0.0.1",5000);
   InputStreamReader streamReader = new InputStreamReader(sock.getInputStream());
   reader = new BufferedReader(streamReader);
   writer = new PrintWriter(sock.getOutputStream());
   System.out.println("networking established");
  }catch(IOException ex){
   ex.printStackTrace();
  }
 }
 
 public class SendButtonListener implements ActionListener{
  public void actionPerformed(ActionEvent ev){
   try{
   writer.println(outgoing.getText());
   writer.flush();
   }catch(Exception ex){
    ex.printStackTrace();
   }
   outgoing.setText("");
   outgoing.requestFocus();
  }
 }
 
 public class IncomingReader implements Runnable{
  public void run(){
   String message;
   try{
    while((message = reader.readLine())!= null){
     System.out.println("read "+ message);
     incoming.append(message+ "\n");
     }
    }catch(Exception ex){
     ex.printStackTrace();
    }
   }
  }
 }

Posted by 서오석
,

Thread.sleep()이라는 정적 메소드는 적어도 sleep 메소드에 전달된 인자로 지정한 시간 동안 스레드를 실행중인 상태를 떠나있게 만든다. 값에 100을 주면 100밀리세컨드 동안 스레드가 멈춘다.

sleep()메소드에서는 확인 예외(interruptedException)를 던지기 때문에 sleep()을 호출할 때에는 반드시 try/catch로 감싸거나 예외를 선언해야 한다.

스레드 두 개 이상이, 힙에 있는 동일한 객체를 접근하는 경우에 심각한 문제가 생길 수 있습니다.
 
스레드를 두 개 이상에서 똑같은 객체에 접근하면 데이터가 엉망이 될 수 있다. 예를들어, 한 스레드가 객체의 중요한 상태를 조작하는 도중에 실행중인 상태에서 벗어나면 심각한 문제가 생길 수 있다.

스레드를 사용할 때도 객체를 안전하게 만들고 싶다면 어떤 선언문들이 원자적인 절차로 처리되어야 하는지 결정해야 한다. 즉 다른 스레드가 같은 객체의 같은 메소드에 들어가기 전까지 끝까지 실행되어야만 하는 메소드를 결정해야 한다.

스레드 두개가 메소드 하나에 동시에 들어가는 일을 방지하고 싶다면 메소드 선언부에 synchronized 키워드를 추가해야 한다.

모든 객체에는 자물쇠가 하나씩 있으며 그 자물쇠에는 열쇠가 하나뿐이 없다. 대부분의 경우에 그 자물쇠에 대해서 신경을 쓸 필요가 없지만 객체에 동기화된 메소드가 있으면 자물쇠가 중요한 역할을 한다.

스레드에서 어떤 동기화된 메소드로 들어가려면 그 객체에 대한 열쇠가 있어야 한다. 열쇠가 없으면 그 스레드는 대기실 같은 공간으르 들어가서 열쇠를 슬 수 있을 때 까지 기다려야 한다. 이는 운영체제의 세마포와 비슷하다.

객체에 동기화된 메소드가 두개 이상 있어도 열쇠는 여전히 하나뿐이 없다. 어떤 스레드가 그 객체에 동기화된 메소드에 들어가면 다른 어떤 스레드도 같은 객체에 있는 동기화된 메소드에 들어갈 수 없다. 이런 제한이 있어야 데이터를 조작하는 모든 메소드를 동기함으로 데이터를 보호할 수 있다.

Posted by 서오석
,
네트워크 Socket 통신

  • 클라이언트와 서버 APP는 Socket 연결을 통해서 통신한다.
  • Socket은 서로 다른 물리적인 시스템 두 개에서 실행될 가능성이 있는 애플리케이션 두 개사이의 연결을 나타낸다.
  • 클라이언트는 서버 애플리케이션의 IP주소와 TCP포트 번호를 알아야 한다.
  • TCP포트는 특정 서버 애플리케이션에 할당된 16비트 부호가 없는 정수이다. TCP 포트 번호는 서로 다른 클라이언트가 똑같은 시스템에 접속하여 그 시스템에서 돌아가고 있는 서로 다른 애플리케이션과 통신을 할 수 있게 해주는 역할을 한다.
  • 클라이언트에서 서버  Socket을 만드는 방법으로 서버에 연결을 한다.
    Socket s = new Socket("127.0.0.1",4200);
  • 일단 연결되고 나면 클라이언트는 소켓으로부터 입력 및 출력 스트림을 얻을 수 있다. 이런 스트림은 저수준 '연결' 스트림이다.
    sock.getInputStream();
  • 서버로부터 텍스트 데이터를 읽고 싶다면 Socket으로부터 가져온 입력 스트림에 연쇄된 InputStreamReader와 이와 연쇄된 BufferReader를 만들면 된다.
  • InputStreamReader는 바이트를 받아서 텍스트 데이터로 변환해주는 '다리' 역할을 하는 스트림이다. 주로 고수준의 BufferedReader와 저수준의 Socket 입력 스트림 사이에 들어가는 가운데 고리 역할을 한다.
  • 서버로 텍스트 데이터를 보낼 때는 소켓의 출력 스트림에 직접 연쇄된 PrintWriter를 만들면 된다. 이 객체의 Print() 또는 println()메소드를 호출하면 서버로 String을 보낼 수 있다.
  • 서버에서는 특정 포트 번호로 들어오는 클라이언트 요청을 기다리기 위해  ServerSocket을 사용한다.
  • ServerSocket으로 요청이 들어오면 그 클라이언트와 Socket 연결을 함으로써 그 요청을 수락한다.

스레드

  • thread는 자바에서의 실행 스레드를 의미한다.
  • 자바에서는 스레드마다 각각의 호출 스택이 있다.
  • 대문자 T로 시작하는 Therad는 java.lang.Thread 클래스를 의미한다. Thread 객체는 실행 스레드를 나타낸다.
  • Thread에는 처리할 작업, 즉 해야할 일이 있어야 한다. Thread에서 처리할 작업은 Runnable  인터페이스를 구현하는 클래스의 인스턴스로 지정할 수 있다.
  • Runnable 인터페이스에는 메소드가 run() 하나밖에 없다. 새로운 Call stack의 맨 밑으로 들어가는 것이 바로 이 메소드이다. 즉 새롱누 스레드에서 가장 먼저 실행되는 것이 바로 run()메소드이다.
  • 새로운 스래드를 시작하려면 Thread의 생성자에 전달 할 Runnable 객체가 필요하다.
  • Thread의 인스턴스를 만들긴 했는데 아직 start() 메소드를 호출하지 않았으면 그 스레드는 아직 새 스레드 상태에 있다고 부른다.
  • (Thred 객체의  start() 메소드를 호출하여) 스레드를 시작하면 새로운 stack이 생성되며 Runnable의 Run()메소드가  stack 맨 아래에 들어간다. 그러면 그 스레드는 이제 실행되기를 기다리고 있는 실행 가능한 상태가 된다.
  • JVM의 스레드 스케줄러에 의해 현재 실행중인 스레드로 선택 받으면 그 스레드는 실행중인 상태가 된다. 프로세서가 하나뿐인 시스템에서는 현재 실행중인 스레드가 하나밖에 있을 수 없다.
  • 스레드가 실행 중인 상태에서 봉쇄된 상태로 옮겨지는 경우도 있다. 스트림으로부터 들어오는 데이터를 기다리고 있을 때, 대기 상테로 들어갔을 때, 객체에 대한 잠금이 해제되기를 기다리고 있을 때와 같은 상황에서 스레드가 봉쇄돌 수 있다.
  • 스레드 스케줄링은 어떤 특정한 방식으로 작동한다는 보장이 없기 때문에 모든 스레드가 공평하게 기회를 부여받을 수 있으리라는 가정은 하지 말아야 한다. 스레드를 주기적으로 대기 상태로 전화시키는 방식으로 순번이 돌아가는 것에 영향을 미칠 수는 있다.


스레드 예제 소스

public class MyRunnable implements Runnable {
 public void run(){
  go();
 }
 
 public void go(){
  System.out.println("-0-;");
 }
}
                                                                                                                                     
public class ThreadTester {
 public static void main(String[] args){
  Runnable threadJob = new MyRunnable();
  Thread myThread = new Thread(threadJob);
 
  myThread.start();
 }

}

Posted by 서오석
,
  • 클래스를 만들 때 인스턴스를 만들 수 없게 하고 싶다면 (즉, 그 클래스 유형의 객체를 만들 수 없게 하고 싶다면) abstract 키워드를 사용하면 된다.
  • 추상 클래스에는 추상 메소드와 추상 메소드가 아닌 메소드를 모두 집어넣을 수 있다.
  • 클래스에 추상 메소드가 하나라도 있으면 그 클래스는 추상 클래스로 지정해야 한다.
  • 추상 메소드는 본체가 없으며 선언 부분은 세미콜론으로 끝난다.
  • 상속 트리에서 처음으로 나오는 구상 클래스에서는 반드시 모든 추상 메소드를 구현해야 한다.
  • 자바에 들어있는 모든 클래스는 직접 또는 간접적으로 Object의 하위 클래스입니다.
  • ArrayList에서 꺼내는 모든 객체는 Object 유형입니다.(즉, Object 레퍼런스 변수로만 참조 할 수 있다,)
  • 레퍼런스 변수를 캐스트해서 객체의 원래 유형으로 돌려놓을 수 있다.
  • 어떤 객체에 있는 메소드를 호출하려면 실제 객체의 유형과는 관계없이 그 메소드가 레퍼런스 변수로 쓰인 클래스(또는 인터페이스)에 들어있는 메소드여야만 한다.
  • DDD와 관련된 문제 때문에 자바에서는 다중 상속을 허용하지 않는다. 클래스는 단 하나만 확장할 수 있다.(즉 직속 상위 클래스는 하나밖에 없다.)
  • 인터페이스는 100% 순수한 추상 클래스입니다. 인터페이스에서는 추상 메소드만 정의한다.
  • 인터페이스를 만들 때는 Class 대신 interface라는 키워드를 사용한다.
  • 인터페이스를 구현할 때는 implements라는 키워드를 쓰면 된다.
  • 클래스를 만들 때 인터페이스를 여러 개 구현할 수 있다.
  • 인터페이스의 모든 메소드는 자동으로 public 메소드, 그리고 abstract 메소드가 되기 때문에 인터페이스를 구현하는 클래스에서는 인터페이스에 들어있는 모든 메소드를 구현해야한다.
Posted by 서오석
,
  • 하위 클래스는 상위클래스를 확장합니다.
  • 하위클래스는 상위 클래스에 있는 모든 public으로 지정한 인스턴스 변수와 메소드를 상속합니다. 하지만 private로 지정한 인스턴스 변수와 메소드는 상속하지 않습니다.
  • 메소드는 오버라이드할 수 있지만 인스턴스 변수는 오버라이드 할  수 없습니다. (하위클래스에서 재정의 할 수는 있지만 오버라이드하는 것과는 다르다. 그리고 사실 오버라이드 할 필요를 거의 못느낀다.)
  • 하위클래스에서 메소드를 오버라이드하면, 그리고 하위클래스의 인스턴스에 대해 그 메소드를 호출하면 오버라이드된 버전의 메소드가 호출된다.
Posted by 서오석
,
기존 분산 컴퓨팅 프로토콜 이야기

1. 연결된 상태에서 만약 클라이언트나 서버가 연결이 끊겼다면 이것에 대해서 처리가 힘들다. 특히 이전에 대한 상태 정보를 모두 잃어버린다.

2. 만약 어마어마한 양의 서버와 클라이언트가 물려있는 경우 비 활동적인 클라이언트로 인해 서버의 자원이 낭비될 수 있다.

3. 연결 지향 프로토콜은 방화벽을 통과하지 못한다.

4. 만약 프로토콜의 구조가 다른 서버, 클라이언트가 있다면 둘이 통신이 거의 불가능하다. 설사 가능하더라도 엄청 복잡하다. 만약 Client가 RMI를 사용하면 RMI를 사용하는 서버만 서버 쪽에 있는 메소드 호출이 가능하다.

이런 단점들이 있어서 나타난 것이 Web Service다.

웹 서비스는 XML기반이다. 즉 XML을 이용하면 서로 다른 시스템에서 응용프로그램끼리 공유가 가능하고 데이터 교환도 가능해진다. XML은 유니코드이기 때문에 유니코드를 인식하는 시스템에선 모두 사용이 가능하기 때문이다.

웹 서비스 이야기 (기초에서 실무까지 XML 웹서비스에서 발췌)
  1. 웹 서비스 시스템과 클라이언트는 지속적인 연결을 가지지 않는다.
    클라이언트가 웹 서비스 시스템에서 제공하는 메서드를 호출하면, 웹 서비스 시스템은 메소드의 실행 결과를 클라이언트에게 보내주고 나서 즉시 연결을 해제한다
  2. 웹 서비스는 상태 정보를 유지하지 않는다.
    클라이언트의 요청을 처리한 후 응답을 보내고 나서 즉시 연결이 해제되기 때문에 이전 클라이언트의 상태 정보를 웹 서비스는 유지하지 않는다. 이전에 접속한 클라이언트가 다시 접속하게 되면 새로운 클라이언트 접속으로 취급한다.
  3. 사용하는 프로토콜은 XML 기반의 SOAP이다.
    웹 서비스와 클라이언트 간의 프로토콜로서 사용되는 것은 XML 기반의 SOAP이다. XML기반이 아주 중요한 의미를 가지는데 이것은 곧 플랫폼 중립적인 프로토콜을 말한다.
  4. 기존의 다른 프로토콜로 구현된 분산 컴포넌트 환경을 통합시킬 수 있다.
    기존의 다른 프로토콜로 구현된 분산 컴포넌트 환경을 통합하는 것이 가능하다.인터넷상에서의 분산 컴퓨팅이 가능하다.
  5. 웹 서비스와 클라이언트는 SOAP을 사용해서 요청 및 응답을 한다. SOAP는 HTTP 전송 프로토콜에 의해서 운반될 수 있다. 방화벽은 일반적으로 HTTP 프로토콜은 통과시키기 때문에 SOAP 역시 통과될 수 있다. 이것은 큰 장점인 동시에 큰 단점으로 작용할 수 있다. 익명의 SOAP이 중요한 기능의 메소드를 실행시킬 수 있기 때문이다.




 

'개발 이야기 > Java Story' 카테고리의 다른 글

JVM GC와 메모리 튜닝 (조대협)  (0) 2010.02.11
Java jstat로 메모리 모니터링  (0) 2010.02.11
iBatis 튜토리얼 문서  (0) 2008.05.15
Xpath Syntax  (0) 2008.05.03
Core J2EE Patterns - Business Delegate  (0) 2008.04.26
Posted by 서오석
,

Static

자바에서 static으로 선언하면 해당 메서드나 변수는 정적이된다. 만약 클래스의 변수를 static으로 선언하게 되면 그 변수는 객체의 변수가 되는 것이 아니라 클래스의 변수가 된다. 클래스의 변수이기 때문에 어떤 객체라도 동일한 주소의 변수를 참조하게 된다.

static의 특징

  • 객체를 생성해도 static 변수는 메모리에 하나만 생성된다.
  • 다른 JVM에서는 선언을 하면 다른 주소나 다른 값을 참조하지만 같은 JVM이나 WAS 인스턴스에서는 같은 주소와 같은 값을 참조한다.
  • CG의 대상도 되지 않는다.
  • 지역변수에 static을 사용할 수 없다. (매서드 안에서 사용 불가능)

만약에 properties파일을 많이 사용한다면 차라리 static으로 읽어서 관리하는 것도 좋다. 왜냐하면 매번 클래스의 객체가 만들어질 때 properties를 불러오려면 부하가 많이 걸리기 때문이다.

조심해야할 것
위에 말했듯이 static은 CG 대상이 아니다. 그렇기 때문에 잘못하면 Memory Leak 현상이 발생하게 된다. 이는 시스템의 메모리를 마구 먹어서 결국은 OutofMemoryError을 발생시킬 수 있다. (Collection을 Static으로 잡고 안에 아무 데이터나 마구 넣은다음 돌려보길 바란다. 그럼 바로 GG..)

final

final은 한번 final로 지정한 값은 변하지 않는다. 변수 앞에 final이 붙으면 상수를 의미한다.


Posted by 서오석
,

XPath uses path expressions to select nodes or node-sets in an XML document. The node is selected by following a path or steps.


The XML Example Document

We will use the following XML document in the examples below.

<?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore>
<book>
  <title lang="eng">Harry Potter</title>
  <price>29.99</price>
</book>
<book>
  <title lang="eng">Learning XML</title>
  <price>39.95</price>
</book>
</bookstore>


Selecting Nodes

XPath uses path expressions to select nodes in an XML document. The node is selected by following a path or steps. The most useful path expressions are listed below:

Expression Description
nodename Selects all child nodes of the named node
/ Selects from the root node
// Selects nodes in the document from the current node that match the selection no matter where they are
. Selects the current node
.. Selects the parent of the current node
@ Selects attributes

Examples

In the table below we have listed some path expressions and the result of the expressions:

Path Expression Result
bookstore Selects all the child nodes of the bookstore element
/bookstore Selects the root element bookstore

Note: If the path starts with a slash ( / ) it always represents an absolute path to an element!

bookstore/book Selects all book elements that are children of bookstore
//book Selects all book elements no matter where they are in the document
bookstore//book Selects all book elements that are descendant of the bookstore element, no matter where they are under the bookstore element
//@lang Selects all attributes that are named lang


Predicates

Predicates are used to find a specific node or a node that contains a specific value.

Predicates are always embedded in square brackets.

Examples

In the table below we have listed some path expressions with predicates and the result of the expressions:

Path Expression Result
/bookstore/book[1] Selects the first book element that is the child of the bookstore element.

Note: IE5 and later has implemented that [0] should be the first node, but according to the W3C standard it should have been [1]!!

/bookstore/book[last()] Selects the last book element that is the child of the bookstore element
/bookstore/book[last()-1] Selects the last but one book element that is the child of the bookstore element
/bookstore/book[position()<3] Selects the first two book elements that are children of the bookstore element
//title[@lang] Selects all the title elements that have an attribute named lang
//title[@lang='eng'] Selects all the title elements that have an attribute named lang with a value of 'eng'
/bookstore/book[price>35.00] Selects all the book elements of the bookstore element that have a price element with a value greater than 35.00
/bookstore/book[price>35.00]/title Selects all the title elements of the book elements of the bookstore element that have a price element with a value greater than 35.00


Selecting Unknown Nodes

XPath wildcards can be used to select unknown XML elements.

Wildcard Description
* Matches any element node
@* Matches any attribute node
node() Matches any node of any kind

Examples

In the table below we have listed some path expressions and the result of the expressions:

Path Expression Result
/bookstore/* Selects all the child nodes of the bookstore element
//* Selects all elements in the document
//title[@*] Selects all title elements which have any attribute


Selecting Several Paths

By using the | operator in an XPath expression you can select several paths.

Examples

In the table below we have listed some path expressions and the result of the expressions:

Path Expression Result
//book/title | //book/price Selects all the title AND price elements of all book elements
//title | //price Selects all the title AND price elements in the document
/bookstore/book/title | //price Selects all the title elements of the book element of the bookstore element AND all the price elements in the document

'개발 이야기 > Java Story' 카테고리의 다른 글

JVM GC와 메모리 튜닝 (조대협)  (0) 2010.02.11
Java jstat로 메모리 모니터링  (0) 2010.02.11
iBatis 튜토리얼 문서  (0) 2008.05.15
Web Service 이야기  (0) 2008.05.08
Core J2EE Patterns - Business Delegate  (0) 2008.04.26
Posted by 서오석
,
1. 인스턴스 변수는 클래스 내에서 선언된다. 메소드 내에서 선언되는 것이 아니다.
ex)
Class Student {
    private int studentNumber = 200210937;
    private String name = "5dols";
 //
}

2. 지역 변수는 메소드 내에서 선언된다.
ex)
Class Student {
    int studentNumber = 200210937;
    String name = "5dols";
  
    public int add() {
        int addstuNumber = studentNumber + 1;
        return addstuNumber;
    }
}

3. 지역 변수는 사용하기 전에 반드시 초기화해야 한다.
Class Student {
    int studentNumber = 200210937;
    ....
}
Posted by 서오석
,

메소드와 인스턴스 핵심정리

  • 클래스에서는 객체가 아는 것과 객체가 하는 것을 정의한다.
  • 인스턴스 변수는 각체가 아는 것이다.
  • 메소드는 객체가 하는 것이다.
  • 메소드에서 인스턴스 변수를 이용하여 같은 유형의 객체가 다른 식으로 행동하게 할 수있다.
  • 전달하는 값의 개수와 유형은 반드시 메소드를 선언할 때 지정한 것과 같아야 하며 그 순서도 같아야 한다.
  • 메소드 안팎으로 전달되는 값은 상황에 따라 자동으로 더 큰 유형으로 올라갈 수 있다. 더 작은 유형으로 바꿔야 한다면 강제로 캐스팅을 해야 한다.
  • 메소드에 인자를 전달할 때는 리터럴값을 사용할 수 있고 선언된 매개변수 유형의 변수를 사용할 수 있다.
Posted by 서오석
,