Profililer의 기본 Bytecode Instrumentaion comparasion between ASM and Javassit.

posted Jun 20, 2011, 4:13 PM by Kuwon Kang


• Javassist (Java Programming Assistant) makes Java bytecode manipulation simple. It is a class library for editing bytecodes in Java; it enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors
• Javassist provides two levels of API: source level and bytecode level. If the users use the source-level API, they can edit a class file without knowledge of the specifications of the Java bytecode. The whole API is designed with only the vocabulary of the Java language. You can even specify inserted bytecode in the form of source text; Javassist compiles it on the fly. On the other hand, the bytecode-level API allows the users to directly edit a class file as other editors.
• Javassist lets you inspect, edit, and create Java binary classes.
• Javassist isn’t the only library for working with bytecode, but it does have one feature in particular that makes it a great starting point for experimenting with classworking: you can use Javassist to alter the bytecode of a Java class without actually needing to learn anything about bytecode or the Java virtual machine (JVM) architecture.
• Aspect Oriented Programming: Javassist can be a good tool for adding new methods into a class and for inserting before/after/around advice at the both caller and callee sides.
• Reflection: One of applications of Javassist is runtime reflection; Javassist enables Java programs to use a metaobject that controls method calls on base-level objects. No specialized compiler or virtual machine are needed.
• Javassist also provides lower-level API for directly editing a class file. To use this level of API, you need detailed knowledge of the Java bytecode and the class file format while this level of API allows you any kind of modification of class files.


• ASM is an all purpose Java bytecode manipulation and analysis framework. It can be used to modify existing classes or dynamically generate classes, directly in binary form. Provided common transformations and analysis algorithms allow to easily assembling custom complex transformations and code analysis tools.
• ASM offer similar functionality as other bytecode frameworks, but it is focused on simplicity of use and performance. Because it was designed and implemented to be as small and as fast as possible, it makes it very attractive for using in dynamic systems*.
• ASM is a Java class manipulation tool designed to dynamically generate and manipulate Java classes, which are useful techniques to implement adaptable systems. ASM is based on a new approach, compared to equivalent existing tools, which consists in using the “visitor” design pattern without explicitly representing the visited tree with objects. This new approach gives much better performances than those of existing tools, for most of practical needs.

Comparison between Javassist & ASM:

• Javassist source level API is much easier to use than the actual bytecode manipulation in ASM
• Javassist provides a higher level abstraction layer over complex bytecode level operations. Javassist source level API requires very less or no knowledge of actual bytecodes, so much easier & faster to implement.
• Javassist uses reflection mechanism which makes it slower compared to ASM which uses Classworking techniques at runtime.
• Overall ASM is much faster & gives better performance than Javassist. Javassist uses a simplified version of Java source code, which it then compiles into bytecode. That makes Javassist very easy to use, but it also limits the use of bytecode to what can be expressed within the limits of the Javassist source code.
• In conclusion, if someone needs easier way of dynamically manipulating or creating Java classes Javassist API should be used & where the performance is the key issue ASM library should be used.

Table 1. Class construction times

Framework        First time       Later times
Javassist             257                  5.2
BCEL                     473                  5.5
ASM                      62.4                 1.1
The Table 1 results show that ASM does live up to its billing as faster than the other frameworks, and this advantage applies both to startup time and to repeated uses.


Classworking toolkit: ASM classworking,

Class transformation with Javassist,

ASM: a code manipulation tool to implement adaptable systems,

Javassist Website,

ASM Website,

수직적 Java EE 확장성.

posted Jun 20, 2010, 9:09 PM by Kuwon Kang

July 2008

Discuss this Article

 J2EE Applicaiton의 수직적 측정에 관한 글이다. 매우 좋은 글임에는 틀림없다ㅏ.

아래는 수직적 측정시 발견된 주된 문제들을 정리한 것이다.

In other memory-centric Java applications tested in our laboratory, we found the following characteristics:

  1. Every request processing action needed big and complex objects
  2. It kept too many objects into HttpSession for every session.
  3. The HttpSession timeout was too long, and HttpSession was not explicitly invalidated.
  4. The thread pool, EJB pool or other objects pool was set too large.
  5. The objects cache was set too large.

JVM상에서 GC가 무리하게 작동하는 경우의 수가 위의 특성을 가지고 있다는 연구 결과가 있다.

많은 J2EE개발자가 격어볼 만한 일들인데.

HttpSession이 많은 오브젝트를 가지고 있다는 부분이 색다르게 느껴진다.

줄일 수 있는 방법은?

수평적 Java EE 확장성

posted Jun 20, 2010, 9:09 PM by Kuwon Kang

Scaling Your Java EE Applications - Part 2

July 2008

Discuss this Article

If you cannot satisfy the performance with one server node when concurrent users are increasing dramatically, or you cannot scale your Java applications with one JVM instance due to the limitation of garbage collection, your other choice is to run your systems in multiple JVM instances or in multiple server nodes. We called it horizontal scalability.

2번째 글이다.

수평적 확장성에 대한 연구 결과이다.

JVM GC와 메모리 튜닝

posted Jun 20, 2010, 9:08 PM by Kuwon Kang

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에서 다운로드 받을 수 있다.

<그림 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 옵션이 필요한 경우가 있기때문에 미리 자료를 참고할 필요가 있다.

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

참조 : 자바스터디 네트워크 []조대협 []

How to find bottleneck in J2EE application

posted Jun 20, 2010, 9:07 PM by Kuwon Kang

How to find bottleneck in J2EE application

자바스터디 네트워크 []

조대협 []

J2ee application을 운영하다보면, 시스템이 극도로 느려지거나, 멈춰버리는 현상이 생기고는 한데, 분명히 개발하면서 테스트할때는 문제가 없었는데, 왜 이런일이 생기고, 어떻게 대처해야하는지에 대해서 알아보도록 하자.

일반적으로 J2ee application을 서비스 하기 위해서는 아래와 같은 구조를 가지게 된다.

<그림 1. 일반적인 J2ee application의 구조>
J2ee application의 동작에 필요한 구성 요소를 나눠보면 위와 같이 Network, User Application (이하 User AP), WAS 또는 Servlet Engine(이하 통칭해서 WAS),JVM과 RDBMS 이렇게 크게 다섯가지 조각으로 나뉘어 진다. 물론 JCA를 이용해서 Legacy와 연결할 수 도 있고, RMI/CORBA를 이용하여 다른 Architecture를 구현할 수 는 있으나, 이 강좌는 어디까지나 일반론을 설명하고자 하는것임으로 범위에서는 제외하겠다.

1. Hang up과 slow down현상의 정의

먼저 용어를 정의하도록 하자.. 시스템이 느려지거나 멈추는 현상에 대해서 아래와 같이 용어를 정의하자

- Hang up : Server Instance는 실행되고 있느나, 아무런 응답이 없는 상황 (멈춤 상태)
- Slowdown : Server Instance의 response time이 아주 급격히 떨어지는 상태 (느려짐)

이 Hangup과 slowdown현상은, 대부분이 그림 1에서 설명한 다섯가지 요소중 하나 이상의 병목으로 인해서 발생한다. 즉, 이 병목 구간을 발견하고, 그 구간을 제거하면 정상적으로 시스템을 운영할 수 있게 되는것이다.

2. Slow down analysis in WAS & User AP

2-1. WAS의 기본 구조

이 병목 구간을 발견하는 방법에 앞서서, 먼저 WAS 시스템의 기본적인 내부 구조를 이해할 필요가 있다.

<그림 2. WAS 시스템의 구조>
<그림 2>는 일반적인 WAS의 구조이다.
WAS는 Client로 부터 request를 받아서, 그 Request의 내용을 분석한다 JMS인지, HTTP , RMI request인지를 분석한후 (Dispatcher) 그 내용을 Queue에 저장한다.

Queue에 저장된 내용은 WAS에서 Request를 처리할 수 있는 Working Thread들이 있을때 각각의 Thread들이 Queue에서 Request를 하나씩 꺼내서 각각의 Thread들이 그 기능을 수행한다.

여기서 우리가 주의깊게 봐야하는것은 Thread pooling이라는 것인데.
Thread를 미리 만들어놓고, Pool에 저장한체로 필요할때 그 Thread 를 꺼내서 사용하는 방식이다. (Connection Pooling과 같은 원리이다.)

WAS에서는 일반적으로 업무의 성격마다 이 Thread Pool을 나누어서 만들어놓고 사용을 한다. 즉 예를 들어서 금융 시스템의 예금 시스템과 보험 시스템이 하나의 WAS에서 동작할때, 각각의 업무를 Thread Pool을 나누어서 분리하는 방식이다. 이 방식을 사용하면 업무의 부하에 따라서 각 Thread Pool의 Thread수를 조정할 수 있으며, 만약에 Thread가 모자르거나 deadlock등의 장애사항이 생기더라도 그것은 하나의 Thread Pool에만 국한되는 이야기이기 때문에, 다른 업무에 영향을 거의 주지 않는다.

2-2. Thread Dump를 통한 WAS의 병목 구간 분석

위에서 살펴봤듯이, WAS는 기본적으로 Thread 기반으로 작동하는 Application이다. 우리가 hang up이나 slow down은 대부분의 경우가 WAS에서 현상이 보이는 경우가 많다. (WAS에 원인이 있다는 소리가 아니라.. 다른 요인에 의해서라도 WAS의 처리 속도가 느려질 수 있다는 이야기다.)

먼저 이 WAS가 내부적으로 어떤 Application을 진행하고 있는지를 알아내면 병목 구간에 한층 쉽게 접근할 수가 있다.

1) What is thread dump?

이를 위해서 JVM에서는 Java Thread Dump라는 것을 제공한다.
Thread Dump란 Java Application이 작동하는 순간의 X-Ray 사진, snapshot을 의미한다.
즉 이 Thread dump를 연속해서 수번을 추출한다면, 그 당시에 Application이 어떻게 동작하고 진행되고 있는가를 살펴볼 수 있다.

Thread dump를 추출하기 위해서는
- Unix 에서는 kill -3 pid
- Windows 계열에서는 Ctrl + break
를 누르면 stdout으로 thread dump가 추출 된다.

Thread dump는 Application을 구성하고 있는 현재 구동중인 모든 Thread들의 각각의 상태를 출력해준다.

<그림 3-2. Thread dump>
그림 3-2는 전체 Thread dump중에서 하나의 Thread를 나타내는 그림이다.
Thread Dump에서 각각의 Thread는 Thread의 이름과, Thread의 ID, 그리고 Thread 의 상태와, 현재 이 Thread가 실행하고 있는 Prorgam의 스택을 보여준다.

- Thread name
각 쓰레드의 이름을 나타낸다.
※ WAS나 Servlet Engine에 따라서는 이 이름에 Thread Queue이름등을 배정하는 경우가 있다.

- Thread ID
쓰레드의 System ID를 나타낸다. 이 강좌 나중에 이 ID를 이용해서 각 Thread별 CPU 사용률을 추적할 수 있다.

- Thread Status
매우 중요한 값중의 하나로 각 Thread의 현재 상태를 나타낸다. 일반적으로 Thread가 사용되고 있지 않을때는 wait를 , 그리고 사용중일때는 runnable 을 나타내는게 일반적이다.
그외에, IO Wait나 synchronized등에 걸려 있을때 Wait for monitor entry, MW (OS별JVM별로 틀림) 등의 상태로 나타난다.

- Program stack of thread
현재 해당 Thread가 어느 Class의 어느 Method를 수행하고 있는지를 나타낸다. 이 정보를 통해서 현재 WAS가 어떤 작업을 하고 있는지를 유추할 수 있다.

2) How to analysis thread dump

앞에서 Thread dump가 무엇이고, 어떤 정보를 가지고 있는지에 대해서 알아봤다. 그러면 이 Thread dump를 어떻게 분석을 하고, System의 Bottle neck을 찾아내는데 이용할지를 살펴보기로 하자.

System이 Hangup이나 slowdown에 걸렸을때, 먼저 Thread dump를 추출해야 한다. 이때 한개가 아니라 3~5초 간격으로 5개 정도의 dump를 추출한다. 추출한 여러개의 dump를 연결하면 각 Thread가 시간별로 어떻게 변하고 있는지를 판별할 수 있다.

먼저 각각의 Thread 덤프를 비교해서 보면, 각각의 Thread는 적어도 1~2개의 덤프내에서 연속된 모습을 보여서는 안되는게 정상이다. 일반적으로Application은 내부적으로 매우 고속으로 처리되기 때문에, 하나의 Method에 1초이상 머물러 있는다는것은 거의 있을 수 없는 일이다.
Thread Dump의 분석은 각각의Thread가 시간이 지남에 따라서 진행되지 않고 멈춰 있는 Thread를 찾는데서 부터 시작된다.

예를 들어서 설명해보자 . 아래 <그림 3-3>의 프로그램을 보면 MY_THREAD_RUN()이라는 메소드에서 부터 MethodA()aMethodB()aMethodC() 를 차례로 호출하는 형태이다.

<그림 3-3. sample code>
이 프로그램을 수행하는 중에 처음부터 Thread Dump를 추출 하면 대강 다음과 같은 형태일것임을 예상할 수 있다. 함수 호출에 따라서 Stack이 출력되다가 ◇의 단계 즉 MethodC에서 무한루프에 빠지게 되면 더이상 프로그램이 진행이 없기 때문에 똑같은 덤프를 유지하게 된다.

우리는 이 덤프만을 보고 methodC에서 무엇인가 잘못되었음을 유추할 수 있고, methodC의 소스코드를 분석함으로써 문제를 해결할 수 있다.

그렇다면 이제부터 Slow down과 Hang up현상을 유발하는 일반적인 유형과 그 Thread Dump의 모양에 대해서 알아보도록 하자.

[CASE 1] lock contention

잘 알다싶이 Java 는 Multi Threading을 지원한다. 이 경우 공유영역을 보호하기 위해서 Synchronized method를 이용하는데, 우리가 흔히들 말하는 Locking이 이것이다.

예를 들어 Thread1이 Synchronized된 Method A의 Lock을 잡고 있는 경우, 다른 쓰레드들은 그 Method를 수행하기 위해서, 앞에서 Lock을 잡은 쓰레드가 그 Lock을 반환하기를 기다려야한다. <그림 3-4>

<그림 3-4. Thread 간에 Lock을 기다리는 형태>
만약에 이 Thread 1의 MethodA의 수행시간이 아주 길다면 Thread 2,3,4는 마냥 이 수행을 아주 오랜 시간 기다려야하고, 다음 2번이 Lock을 잡는다고 해도 3,4번 Thread들은 1번과 2번 쓰레드가 끝난 시간 만큼의 시간을 누적해서 기다려야 하기때문에, 수행시간이 매우 느려지는 현상을 겪게 된다.

이처럼 여러개의 Thread가 하나의 Lock을 동시에 획득하려고 하는 상황을 Lock Contention 이라고 한다.

<그림 3-5. Lock Contention 상황에서 Thread의 시간에 따른 진행 상태>
이런 상황에서 Thread Dump를 추출해보면 <그림 3-6> 과 같은 형태를 띠게 된다.

<그림 3-6. Lock Contention에서 Lock을 기다리고 있는 상황의 Thread Dump>
그림 3-6의 덤프를 보면 12번 Thread가 org.apache.axis.utils.XMLUtils.getSAXParser에서 Lock을 잡고 있는것을 볼 수 있고, 36,15,14번 쓰레드들이 이 Lock을 기다리고 있는것을 볼 수 있다.

[CASE 1] 해결방안

이런 Lock Contention 상황은 Multi Threading환경에서는 어쩔 수 없이 발생하는 상황이기는 하지만, 이것이 문제가 되는 경우는 Synchronized Method의 실행 시간이 불필요하게 길거나, 불필요한 Synchronized문을 사용했을 때 발생한다.

그래서 이 문제를 해결하기 위해 불필요한 Sychronized Method의 사용을 자제하고, Synchronized block 안에서의 최적화된 Algorithm을 사용하는것이 필요하다.

[CASE 2] dead lock

이 Locking으로 인해서 발생할 수 있는 또 다른 문제는 dead Lock 현상이다. 두개 이상의 쓰레드가 서로 Lock을 잡고 기다리는 “환형대기조건” 이 성립되었을때, 서로 Lock이 풀리지 않고 무한정 대기하는 현상을 이야기 한다.

<그림 3-7>을 보면 Thread1은 MethodA를 수행하고 있는데, sychronized methodB를 호출하기전에 Thread2가 methodB가 끝나기를 기다리고 있다. 마찬가지로 Therad2는 Thread3가 수행하고 있는 methodC가 끝나기를 기다리고 있고, methodC에서는 Thread1에서 수행하고 있는 methodA가 끝나기를 기다리고 있다.
즉 각각의 메소드들이 서로 끝나기를 기다리고 있는 “환형 대기조건” 이기 때문에 이 Application의 3개의 쓰레드들은 프로그램이 진행이 되지 않게 된다.

<그림 3-7. 환형 대기조건에 의한 deadlock>
이러한 “환형대기조건”에 의한 deadlock은 Thread Dump를 추출해보면 <그림 3-8> 과 같은 패턴을 띠우고 있으며 시간이 지나도 풀리지 않는다.

<그림 3-8. Deadlock이 걸렸을때 시간 진행에 따른 Thread의 상태>

<그림 3-9. Deadlock이 걸린 IBM AIX Thread Dump>
DeadLock의 검출은 Locking Condition을 비교함으로써 검출할 수 있으며, 최신 JVM (Sun 1.4이상등)에서는 Thread Dump 추출시 만약 Deadlock이 있다면 해당 Deadlock을 찾아주는 기능을 가지고 있다.

<그림 3-9>를 보자. IBM AIX의 Thread 덤프이다. 1)항목을 보면 현재 8번 쓰레드가 잡고 있는 Lock은 10번과 6번 쓰레드가 기다리고 있음을 알 수 있으며. Lock은 OracleConnection에 의해서 잡혀있음을 확인할 수 있다. (아래)

<그림 3-9>의 2)번 항목을 보면 이 6번쓰레드는 OracleStatement에서 Lock을 잡고 있는데, 이 Lock을 8번 쓰레드가 기다리고 있다. (아래)

결과적으로 6번과 8번은 서로 Lock을 기다리고 있는 상황이 되어 Lock이 풀리지 않는 Dead Lock상황이 된다.

[CASE 2] 해결 방안

Dead Lock의 해결 방안은 서로 Lock을 보고 있는것을 다른 Locking Object를 사용하거나 Lock의 방향 (Synchronized call의 방향)을 바꿔줌으로써 해결할 수 있다.

User Application에서 발생한 경우에는 sychronized method의 호출 순서를 “환형대기조건”이 생기지 않도록 바꾸도록 하고.
위와 같이 Vendor들에서 제공되는 코드에서 문제가 생긴경우에는 Vendor에 패치를 요청하도록 하여 해결한다.

[CASE 3] wait for IO response

다음으로 많이 볼 수 있는 패턴은 각각의 Thread들이 IO Response를 기다리는데… IO작업에서 response가 느리게 와서 시스템 처리속도가 느려지는 경우가 있다.

<그림 3-10. Thread들이 IO Wait를 할때 시간에 따른 Thread Dump 상황>

<그림 3-11. Thread가 DB Query Wait에 걸려 있는 stack>
<그림 3-10> 상황은 DB에 Query를 보내고 response를 Thread들이 기다리고 있는 상황이다. AP에서 JDBC를 통해서 Query를 보냈는데, Response가 오지 않으면 계속 기다리게 있게 되고, 다른 Thread가 같은 DB로 보냈는데. Response가 오지 않고, 이런것들이 중복되서 결국은 사용할 수 없는 Thread가 없게 되서 새로운 request를 처리하지 못하게 되고, 기존에 Query를 보낸 내용도 응답을 받지 못하는 상황이다.

<그림 3-11>은 각각의 Thread의 stack dump인데, 그 내용을 보면 ExecuteQuery를 수행한후에, Oracle로 부터 데이타를 read하기 위해 대기 하는것을 확인할 수 있다.

이런 현상은 주로 DB 사용시 대용량 Query(시간이 많이 걸리는)를 보냈거나, DB에 lock들에 의해서 발생하는 경우가 많으며, 그외에도 Socket이나 File을 사용하는 AP의 경우 IO 문제로 인해서 발생하는 경우가 많다.

[CASE 3] 해결 방안

이 문제의 해결 방안은 Thread Dump에서 문제가 되는 부분을 발견한후에, 해당 소스코드나 시스템등에 접근하여 문제를 해결해야한다.

[CASE 4] high CPU usage

시스템이 느려지거나 거의 멈추었을때, 우리가 초기에 해볼 수 있는 조치가 무엇인가 하면, 현재 시스템의 CPU 사용률을 체크해볼 필요가 있다.

해당 시스템의 CPU 사용률이 높은 경우, 문제가 되는경우가 종종 있는데. 이런 문제에서는 CPU를 많이 사용하는 모듈을 찾아내는것이 관건이다.

이를 위해서 먼저 top 이나 glance(HP UX)를 통해서 CPU를 많이 점유하고 있는 Process를 판독한다. 만약 WAS 이외의 다른 process가 CPU를 많이 사용하고 있다면, 그 Process의 CPU 과사용 원인을 해결해야 한다. CPU 사용률이외에도, Disk IO양이 많지는 않은지.. WAS의 JVM Process가 Swap out (DISK로 SWAP되는 현상) 이 없는지 살펴보도록 한다. JVM Process가 Swapping이 되면 실행속도가 엄청나게 느려지기 때문에, JVM Process는 Swap out 되어버리면 안된다.

일단 CPU를 과 사용하는 원인이 WAS Process임을 발견했으면 프로그램상에 어떤 Logic이 CPU를 과점유하는지를 찾아내야한다.

방식을 정리해보면 다음과 같다.
WAS Process의 Thread별 CPU 사용률을 체크한다. ← 1)
Thread Dump를 추출한다. ← 2)

1)과, 2)에서 추출한 정보를 Mapping하여, 2)의 Thread Dump상에서 CPU를 과점유하는 Thread를 찾아내어 해당 Thread의 Stack을 분석하여 CPU 과점유하는 원인을 찾아낸다.

대부분 이런 요인을 분석해보면 다음과 같은 원인이 많다.

과도한 String 연산으로 인해서 CPU 사용률이 높아지는 경우

잘 알고 있다시피 String 연산은 CPU 를 아주 많이 사용한다. String과 Loop문(for,while등)의 사용은 CPU부하를 유발하는 경우가 많기 때문에 가급적이면 String Buffer를 사용하도록 하자.

과도한 RMI Cal
RMI호출은 Java Object를 Serialize하고 Deserialize하는 과정을 수반하는데, 이는 CPU를 많이 사용하는 작업이기 때문에 사용에 주의를 요한다. 특별히 RMI를 따로 코딩하지 않더라도 EJB를 호출하는것이 Remote Call일때는 기본적으로 RMI호출을 사용하게 되고, 부하량이 많을때 이 부분이 주로 병목의 원인이 되곤한다. 특히 JSP/ServletaEJB 호출되는것이 같은 System의 같은 JVM Process안이라도 WAS별로 별도의 설정을 해주지 않으면 RMI Call을 이용하는 형태로 구성이 되기 때문에, 이에 대한 배려가 필요하다.

※ WebLogic에서 Call By Reference를 위한 호출 방법 정의

참고로 WebLogic의 경우에는 Servlet/JSPaEJB를 호출하는 방식을 Local Call을 이용하기 위해서는 같은 ear 파일내에 패키징해야하고, EJB의 weblogic-ejb-jar.xml에 enable-call-by-reference를 true로 설정해줘야한다. (8.1이상)

자세한 내용은를 참고하시 바란다.

JNDI lookup

JNDI lookup은 Server의 JNDI에 Binding된 Object를 읽어오는 과정이다. 이 과정은 위에서 설명한 RMI call로 수행이되는데. 특히 EJB를 호출하기 위해서 Home과 Remote Interface를 lookup하는 과장에서 종종 CPU를 과점유하는 형태를 관찰 할 수 있다.

그래서 JNDI lookup의 경우에는 EJB Home Interface를 Caller Side(JSP/Servlet 또는 poor Java client)등에서 Caching해놓고 사용하는 것을 권장한다. 단. 이경우에는 EJB의 redeploy기능을 제약받을 수 있다.

다음은 각 OS별로 CPU 사용률이 높은 Thread를 검출해내는 방법이다.

■ Solaris에서 CPU 사용률이 높은 Thread를 검출하는 방법

1.prstat 명령을 이용해서 java process의 LWP(Light Weight process) CPU 사용률을 구한다.

% prstat -L -p [WeblogicPID] 1 1

<그림 3-6. Lock Contention에서 Lock을 기다리고 있는 상황의 Thread Dump>
2. pstack 명령어를 이용해서 native thread와 LWP 간의 id mapping을 알아낸다.
(※ 전에 먼저 java process가 lwp로 돌아야되는데, startWebLogic.sh에 LD_LIBRARY_PATH에 /usr/lib/lwp 가 포함되어야 한다.)

% pstack [WebLogicPID]

<그림 3-6. Lock Contention에서 Lock을 기다리고 있는 상황의 Thread Dump>
3. 1에서 얻은 LWP ID를 pstack log를 통해서 분석해보면 어느 Thread에 mapping되는지를 확인할 수 있다.
여기서는 LWP 8이 Thread 24과 mapping이 되고 있음을 볼 수 있다.

kill -3 [WebLogicPID]를 해서 ThreadDump를 얻어낸다.

<그림 3-6. Lock Contention에서 Lock을 기다리고 있는 상황의 Thread Dump>
Thread dump에서 nid라는 것이 있는데, 2에서 얻어낸 thread id를 16진수로 바꾸면 이값이 nid값과 같다. 즉 2에서 얻어낸 thread 24는 16진수로 0x18이기 때문에, thread dump에서 nid가 0x18인 쓰레드를 찾아서 어떤 작업을하고 있는지를 찾아내면 된다.

■ AIX Unix에서 CPU 사용률이 높은 Thread 검출해 내기

1. ps 명령을 이용하여, WebLogic Process의 각 시스템thread의사용률을 구한다.

% ps -mp [WeblogicPID] -0 THREAD

여기서 CP가 가장 높은 부분을 찾는다. 이 시스템 쓰레드가 CPU를 가장 많이 점유하고 있는 시스템 쓰레드이다. (여기서는 66723 이다.)

2. dbx 명령을 이용해서 1.에서 찾은 시스템 쓰레드의 Java Thread ID를 얻어온다.

1) % dbx -a [WebLogicPID]
2) dbx에서 “thread” 명령을 치면 Thread ID 를 Listing할 수 있다.

k-tid 항목에서 1에서 찾은 Thread ID 를 찾고, 그 k-tid에 해당하는 thread id를 찾는다. (여기서는 $t17이 된다.)

3) dbx에서 $t17 번 쓰레드의 Java Thread ID를 얻는다.
dbx에서 “th info 17” 이라고 치면 $t17번 쓰레드의 정보를 얻어온다.

pthread_t 항목에서 Java Thread ID를 얻는다. 여기서는 1011이된다.

3. Java Thread Dump에서 2에서 얻어온 Java Thread ID를 이용해서 해당 Java Thread를 찾아서 Java Stack을 보고 CPU를 많이 사용하는 원인을 찾아낸다.

1) kill -3 [WebLogicPID]
2) Thread dump를 보면 native ID 라는 항목이 있는데, 2.에서 찾은 Java Thread ID와 이 항목이 일치하는 Execute Thread를 찾으면 된다.

■ HP Unix에서 CPU 사용률이 높은 Thread 검출해내기

1. 먼저 JVM이 Hotspot mode로 작동하고 있어야 한다. (classic 모드가 아니어야 한다.) 옵션을 주지 않았으면 Hotspot 모드가 default이다.

2. glance를 실행해서 ‘G’를 누르고 WAS의 PID를 입력한다.
각 Thread의 CPU 사용률이 실시간으로 모니터링이 되는데.

여기서 CPU 사용률이 높은 Thread의 TID를 구한다.

3. kill -3 을 이용해서 Thread dump를 추출해서. 2에서 구한 TID와 Thread Dump상의 lwp_id가 일치하는 Thread를 찾으면 된다.

지금까지 Thread Dump를 이용하는 방법을 간단하게 살펴보았다. 이 방법을 이용하면 WAS와 그 위에서 작동하는 Appllication의 Slow down 이나 hangup의 원인을 대부분 분석해낼 수 있으나, Thread Dump는 어디까지나 분석을 위한 단순한 정보이다. Thread Dump의 내용이 Slow down이나 hang up의 원인이 될수도 있으나, 반대로 다른 원인이 존재하여 그 결과로 Thread Dump와 같은 Stack이 나올 수 있기 때문에, 여러 원인을 동시에 살펴보면서 분석할 수 있는 능력이 필요하다.

3. Slow down in JVM

WAS의 성능에 큰 영향을 주는것중의 하나가 JVM이다.
JVM의 튜닝 여부에 따라서 WAS상에서 작동하는 Ap의 성능을 크게는 20~30% 까지 향상시킬 수 있는데, 우리가 지금 살펴보고 있는 slow down과 hangup 을 일으키는 직접적인 요인이 되는것은 JVM의 Full GC이다.

간단하게 JVM의 메모리 구조를 검토하고 넘어가보도록 하자.

<그림 4-1. JVM의 메모리 구조>
JVM은 크게 New영역과 Old영역, 그리고 Perm영역 3가지로 분류가 된다.
Perm 영역은 Class나 Method들이 로딩되는 영역이고 성능상의 영향을 거의 미치지 않는다.

우리가 주목해야할 부분은 객체의 생성과 저장에 관련되는 New와 Old 영역인데, 모든 객체는 생성이 되자 마자 New 영역에 저장되고, 시간이 지남에 따라 이 객체들은 Old 영역으로 이동이 된다.

New 영역을 Clear하는 과정을 Minor GC라하고, Old 영역을 Clear하는 과정은 Major GC또는 Full GC라 하는데, 성능상의 문제는 이 Full 영역에서 발생한다.

Minor GC의 경우는 1초 이내에 아주 고속으로 이뤄지는 작업이기 때문에, 신경을 쓸 필요가 없지만, Full GC의 경우에는 시간이 매우 오래걸린다.
또한 Full GC가 발생할 동안은 Application이 순간적으로 멈춰 버리기 때문에 시스템이 순간적으로 Hangup 으로 보이거나 또는 Full GC가 끝나면서 갑자기 request가 몰려버리는 현상 때문에 종종 System의 장애를 발생시키는 경우가 있다.

Full GC는 통상 1회에 3~5초 정도가 적절하고, 보통 하루에 JVM Instance당 5회 이내가 적절하다고 여겨진다. (절대 값은 없다.)

Full GC가 자주 일어나는것이 문제가 될경우에는 JVM의 Heap영역을 늘려주면 천천히 일어나지만 반대로 Full GC에 소요되는 시간이 증가한다.

개당 Full GC 시간이 오래걸릴 경우에는 JVM의 Heap 영역을 줄여주면 빨리 Full GC가 끝나지만 반대로 Full GC가 자주 일어난다는 단점이 있다.

그래서 이 부분에 대한 적절한 Tuning이 필요하다.

대부분의 Full GC로 인한 문제는 JVM자체나 WAS의 문제이기 보다는 그 위에서 구성된 Application이 잘못 구성되어 메모리를 과도하게 사용하거나 오래 점유하는 경우가 있다.

예를 들어 대용량 DBMS Query의 결과를 WAS상의 메모리에 보관하거나 , 또는 Session에 대량의 데이타를 넣는것들이 대표적인 예가 될 수 가 있다.

좀더 자세한 튜닝 방법에 대해서는를 참고하기 바란다.

4. Slow down analysis in DBMS

Application이 느려지는 원인중의 많은 부분을 차지 하고 있는 것은 DBMS의 성능 문제가 있는 경우가 많다.

흔히들 DBMS Tuning을 받았더니 성능이 많이 향상되었다고 하는 경우가 많은데, 그건 그만큼 DB 설계를 제대로 하지 못했다는 이야기가 된다.

DBMS 자체 Tuning에 대한 것은 이 문서와는 논외기 때문에 제외하기로 하고, DBMS에 전송되는 각각의 SQL문장의 실행 시간을 Trace할 수 있는 것만으로도 많은 성능 향상을 기대할 수 있는데, 간단하게 SQL 문장을 실행시간은 아래 방법들을 이용해서 Trace할 수 있다.



5. Slow down analysis in Webserver & network

WAS와 DBMS 앞단에는 WebServer와 Network 이 있기 때문에 이 Layer에서 문제가 되면 속도저하를 가지고 올 수 있다. 필자의 경험상 대부분의 slow down이나 hangup은 이 부분에서는 거의 일어나지 않지만 성능상에 종종 영향을 주는 Factor가 있는데,

WebServer 와Client간의 KeepAlive

특히 WebServer의 Keep Alive설정이 그것이다.
WebBrowser와 WebServer간에는 KeepAlive 설정을 하는것이 좋은데. 그 이유는 WebBrowser에서 하나의 HTML 페이지를 로딩하기 위해서는 Image와 CSS등의 여러 파일등을 로딩하는데, KeepAlive 설정이 없으면 각각의 파일을 로딩하는것에 각각의 Connection을 open,request,download,close를 한다. 잘들알고 있겠지만 Socket을 open하고 close하는데에는 많은 자원이 소요된다. 그래서 한번 연결해놓은 Connection을 계속 이용해서 HTTP data를 주고 받는 설정이 KeepAlive이다.
이 KeepAlive 설정은 웹을 이용한 서비스 제공에서 많은 성능 변화를 주기 때문에 특별한 이유가 없는한 KeepAlive 설정을 유지하기 바란다. 설정 방법은 각 WebServer의 메뉴얼을 참고하기 바란다.

※ Apache2.0의 Keep Alive 설정은를 참고하기 바란다. Default가 KeepAlive 가 On으로 되어 있다.

WebServer와 WAS간의 KeepAlive

WebServer와 WAS간에는 WebServer에서 받은 request를 forward하기 위해서 WebServer Side에 WAS와 통신을 하기 위한 plug-in 이라는 모듈을 설치하게 된다. 이 역시 WebServer와 Client간의 통신과의 같은 원리로 KeepAlive를 설정하게 되는데, 이 역시 성능에 영향을 줄 수 있는 부분이기 때문에 가급적이면 설정하기를 권장한다.

※ WebLogic에서 Webserver와의 KeepAlive설정은을 참고하기 바란다.
Default는 KeepAlive가 True로 설정되어 있다.

OS에서 Kernel Parameter 설정

OS의 TCP/IP Parameter와, Thread와 Process등의 Kernel Parameter 설정이 운영에 있어서 영향을 미치는 경우가 있다. 이 Parameter들은 Tuning하기가 쉽지 않기 때문에, WAS또는 OS Vendor에서 제공하는 문서를 통해서 Tuning하기 바란다.

※ WebLogic의 OS별 설정 정보은를 참고하기 바란다.
6. Common mistake in developing J2EE Application

지금까지 간단하게나마 J2EE Application의 병목구간을 분석하는 부분에 대해서 알아보았다. 대부분의 병목은 Application에서 발생하는 경우가 많은데, 이런 병목을 유발하는 Application에 자주 발생하는 개발상의 실수를 정리해보도록 하자.

1) Java Programming

sycnronized block
위에서도 설명 했듯이 sychronized 메소드는 lock contention과 deadlock등의 문제를 유발할 수 있다. 꼭 필요한 경우에는 사용을 해야하지만, 이점을 고려해서 Coding해야한다.

String 연산

이미 많은 개발자들이 알고 있는 내용이겠지만 String 연산 특히 String연산중 “+” 연산은 CPU를 매우 많이 소모하게 되고 종종 slow down의 직접적인 원인이 되는 경우가 매우 많다.
String 보다는 가급적 StringBuffer를 사용하기 바란다.

Socket & file handling

Socket이나File Handling은 FD (File Descriptor)를 사용하게 되는데, 이는 유한적인 자원이기 때문에 사용후에 반드시 close명령을 이용해서 반환해야한다. 종종 close를 하지 않아서, FD가 모자르게 되는 경우가 많다.

2) Servlet/JSP Programming

JSP Buffer Size

Jsp 에서는 JSP의 출력 내용을 저장하는 buffer 사이즈를 지정할 수 있다.

<% page buffer=”12kb” %>

이 buffer size는 출력 내용을 buffering했다가 출력하는데, 만약에 쓰고자하는 내용이 Buffer size 보다 클 경우에는 여러번에 걸쳐서 socket write가 일어나기 때문에 performance에 영향을 줄 수 있으므로 가능하다면 buffersize를 화면에 뿌리는 내용의 크기를 예측해서 지정해주는것이 바람직하다. 반대로 너무 큰 버퍼를 지정해버리면 메모리가 불필요하게 낭비 될 수 있기 때문에 이점을 주의하기 바란다.

참고로 jsp page buffer size는 지정해주지 않는경우 default로 8K로 지정된다.

member variable

Servlet/JSP는 기본적으로 Multi Thread로 동작하기 때문에, Servlet과 JSP 내에서 선언된 멤버 변수들은 각 Thread간에 공유가 된다.
그래서 이 변수들을 read/write할경우에는 sychronized method로 구성해야 하는데, 이 synchronized는 속도 저하를 유발할 수 있기 때문에, member 변수로는 read 만 하는 객체를 사용하는게 좋다.

특히 Servlet이나 JSP에서 Data Base Connection을 멤버 변수로 선언하여 Thread간 공유하는 예가 있는데, 이는 별로 좋지 않은 프로그래밍 방법이고, 이런 형태의 패턴은 Servlet이 단 하나만 실행되거나 하는것과 같은 제약된 조건 아래에서만 사용해야 한다.

Out of Memory in file upload

JSP에서 File upload control을 사용하는 경우가 많다. 이 control을 구현하는 과정에서 upload되는 파일 내용을 몽땅 메모리에 저장했다가 업로드가 끝나면 한꺼번에 file에 writing하는 경우가 있는데, 이는 큰 사이즈의 파일을 업로드할때, 파일 사이즈만큼의 메모리 용량을 요구하기 때문에, 자칫하면 Out Of Memory 에러를 발생 시킬 수 있다.
File upload는 buffer를 만들어서 읽고, 파일에 쓰는 작업을 병행하도록 해야한다.

3) JDBC Programming

Connection Leak

JDBC Programming에서 가장 대표적으로 발생되는 문제가 Connection Leak이다. Database Connection을 사용한후에 close않아서 생기는 문제인데,Exception이 발생하였을때도 반드시 Connection을 close하도록 해줘야한다.

<그림. Connection close의 올바른 예>
Out of memory in Big size query result

SQL문장을 Query하고 나오는 resultset을 사용할때, 모든 resultset의 결과를 Vector나 hashtable등을 이용해서 메모리에 저장해놓는 경우가 있다. 이런 경우에는 평소에는 문제가 없지만, SQL Query의 결과가 10만건이 넘는것과 같은 대용량일때 이 모든 데이타를 메모리 상에 저장할려면 Out Of Memory가 나온다.
Query의 결과값을 처리할때는 ResultSet을 직접 리턴받아서 사용하는것이 메모리 활용면에서 좀더 바람직하다.

Close stmt & resultset

JDBC에서 Resultset이나 Statement 객체는 기본적으로 Connection을 close하게 되면 자동으로 닫히게 된다. 그러나 WAS나 Servlet Container의 경우에는 성능향상을 위해서 Connection Pooling 기법을 이용해서 Connection을 관리하기 때문에 Connection Pooling에서 Connection을 close하는것은 실제로 close하는것이 아니라 Pool에 반환하는 과정이기 때문에 해당 Connection에 연계되어 사용되고 있는 Statement나 ResultSet이 닫히지 않는다.

Connection Pooling에서 Statement와 ResultSet을 사용후에 닫아주지 않으면 Oracle에서 too many open cursor와 같은 에러가 나오게된다. (Statement는 DB의 Cursor와 mapping이 된다.)

4) EJB Programming

When we use EJB?

EJB는 분명 강력하고 유용한 개발 기술임에는 틀림이 없다. 그러나 EJB의 장점과 용도를 모르고 사용하면 오히려 안쓰는것만 못한 경우가 많다.
각 EJB 모델 (Session Bean,Entity Bean)이 어떤때 유용한지를 알고 사용하고, 정확한 Transaction Model등을 결정해서 사용해야 한다.

Reduce JNDI look up

위에서도 설명했듯이 EJB의 Home Interface를 lookup 해오는 과정은 객체의 Serialization/DeSerialization을 동반하기 때문에, 시스템 성능에 영향을 줄 수 있다. EJB Home을 한번 look up한후에는 Hashtable등에 저장해서 반복해서 Remote Call(Serialization / DeSerialization)하는 것을 줄이는게 좋다.

Do not use hot deploy in production mode

WAS Vendor 마다 WAS 운영중에 EJB를 Deploy할 수 있는 HotDeploy 기능을 제공한다. 그러나 이는 J2ee spec에 있는 구현이 아니라 각 vendor마다 개발의 편의성을 위해서 제공하는 기능이다. 운영중에 EJB를 내렸다 올리는것은 위험하다. (Transaction이 수행중이기 때문에) Hot Deploy 기능은 개발중에만 사용하도록 하자.

5) JVM Memory tuning

Basic Tuning

Application을 개발해놓고, 운영환경으로 staging할때 별도의 JVM 튜닝을 하지 않는 경우가 많다. 튜닝이 아니더라도 최소한의 메모리 사이즈와 HotSpot VM 모델 (server/client)는 설정해줘야지 어느정도의 Application의 성능을 보장 받을 수 있다. 최소한 메모리 사이즈와 VM모델정도는 설정을 해주고 운영을 하도록 하자.

7. 결론

J2EE Application의 병목구간을 확인하기 위해서는 그 문제를 발견하고 툴과 경험을 이용해서 문제의 원인을 발견하고 제거해야한다.

대부분의 WAS또는 User Application의 slow down이나 hang up은 Thread dump를 통한 분석을 통해서 대부분 발견 및 해결을 할 수 있다.

그외에 부분 JVM이나 WebServer,Network 등에 대해서는 별도의 경험과 Log 분석등을 알아내야하고 DB에 의한 slow down이나 hang up현상은 DB 자체의 분석이 필요하다.

믿을 만한 자바 벤치마킹, Part 1: 다양한 이슈

posted Jun 20, 2010, 9:04 PM by Kuwon Kang   [ updated Jun 20, 2010, 9:05 PM ]

믿을 만한 자바 벤치마킹, Part 1: 다양한 이슈

자바 코드 벤치마킹에 관련된 여러 가지 함정 이해하기

난이도 : 고급

Brent Boyer, 프로그래머, Elliptic Group, Inc.

옮긴이: 오국환
2008 년 7 월 29 일

고성능 하드웨어의 시대에서조차 프로그램 성능을 고려하지 않을 수 없습니다. 두 편의 연재 중 첫 편을 다루는 본 문서에서는 자바(Java™) 코드 벤치마킹과 관련된 여러 함정을 소개합니다. Part 2에서는 벤치마킹 관련 통계를 다루고 자바 벤치마킹을 위한 프레임워크를 제시할 예정입니다. 거의 대부분의 새 언어는 가상 기계 기반이므로, 본 연재가 다루는 일반적인 원칙은 프로그래밍 커뮤니티 전반에도 중요한 시사점을 제공합니다.

멀티메가헤르츠/멀티코어 프로세서와 멀티기가바이트의 시대에서조차 프로그램 성능은 변하지 않는 고려 사항 중 하나다. 신규 도전적인 애플리케이션으로 인해 (또는 프로그래머의 게으름이 늘어나서) 하드웨어의 성능 이점은 많이 누리게 되었지만, 코드를 벤치마킹하고 결과에서 정확한 결론을 얻기에는 여러 잠재적인 문제도 함께 갖게 되었다. 특히 다른 어떤 언어보다도 현대의 섬세한 가상 기계에서 실행하는 자바 언어를 벤치마킹하는 것은 매우 어렵다.

이 두 편의 연재에서는 프로그램 실행 시간만을 다룬다. 즉, 메모리 사용과 같은 다른 주요 실행 관련 특징은 고려하지 않는다. 이같이 제한된 성능 정의에서조차 코드를 정확히 벤치마킹하기에는 여러 함정이 도사리고 있다. 그 수나 복잡성 때문에 대부분 독자가 자체적으로 벤치마킹을 시도하면, 부정확하거나 종종 오해의 소지를 남긴다. 본 연재의 첫 부분은 이러한 이슈를 다루는 데 할애한다. 이로써 독자 고유의 벤치마킹 프레임워크를 작성하기에 필요한 지도를 펼쳐 보인다.

몇 가지 벤치마킹 이슈를 묘사하는 한 가지 성능 수수께끼로 본 논의를 시작하겠다. Listing 1의 코드를 검토해 보자(본 글의 전체 예제 코드는 참고자료의 링크를 참고하라).

protected static int global;

public static void main(String[] args) {
    long t1 = System.nanoTime();

    int value = 0;
    for (int i = 0; i < 100 * 1000 * 1000; i++) {
        value = calculate(value);

    long t2 = System.nanoTime();
    System.out.println("Execution time: " + ((t2 - t1) * 1e-6) + " milliseconds");

protected static int calculate(int arg) {
    //L1: assert (arg >= 0) : "should be positive";
    //L2: if (arg < 0) throw new IllegalArgumentException("arg = " + arg + " < 0");

    global = arg * 6;
    global += 3;
    global /= 2;
    return arg + 2;

다음 중 어느 버전의 실행 속도가 가장 빠를까?

  1. 코드를 그대로 둔다(calculate 내 arg를 테스트하지 않음).
  2. L1만 코멘트를 해제하지만 조건 확인(assertion)은 비활성화하여 실행(JVM 옵션 중 -disableassertions 사용. 이는 JVM의 기본 동작임)
  3. L1만 코멘트를 해제하지만 조건 확인은 활성화하여 실행(JVM 옵션 중 -enableassertions 사용)
  4. L2만 코멘트를 해제한다.

적어도 테스트를 전혀 하지 않는 A가 가장 빠를 것이라고 짐작할 것이다. 그리고 좋은 동적 최적화 컴파일러가 죽은 코드인 L1은 제거할 것이므로, 조건 확인을 끈 B쪽이 A에 근접한 성능을 보일 것이라 짐작하리라. 그렇지 않은가? 불행히도 이러한 짐작은 틀렸다. Listing 1의 코드는 Cliff Click이 2002 자바원(JavaOne)에서 소개한 것을 수정하였다(참고자료 참조). 그가 발표한 실행 시간은 다음과 같다.

  1. 5초
  2. 0.2초
  3. (이 경우는 결과를 제시하지 않음)
  4. 5초

물론 B의 경우가 놀랍다. 어떻게 B가 A보다 25배 빠를 수 있나?

6년이 지난 후, 다음 설정으로 Listing 1의 코드를 돌려 보았다(다른 언급이 없을 경우, 본 문서에서 다루는 모든 벤치마크 결과에 사용된 설정은 이와 같다).

  • 하드웨어: 2.2GHz 인텔 코어2듀오 E4500, 2GB RAM
  • 운영체제: 윈도(Windows®) XP SP2(2008년 3월 13일 이전 업데이트 모두 적용)
  • JVM: 1.6.0_05, 모든 테스트에서 -server 옵션 적용

이렇게 얻은 결과는 다음과 같다.

  1. 38.601 ms
  2. 56.382 ms
  3. 38.502 ms
  4. 39.318 ms

이제 B가 A와 C, D보다 명백하게 느리다. 그렇지만 여전히 결과는 의아하다. B는 A와 동등해야 했다. 현실은 B가 C보다도 느리다는 점이 놀랍다. 각 설정 별로 네 번 측정하여 1ms 이내의 범위에서 완전히 재현 가능한 결과를 얻었다.

Click은 이상한 결과를 얻은 이유를 설명했다. (이러한 결과는 복잡하기 그지 없는 JVM의 동작 때문으로 판명되었다. 버그도 하나 관련 있었다.) Click은 HotSpot JVM 설계자이고, 이와 같은 합리적인 설명을 할 수 있었다. 그러나 보통의 프로그래머인 독자가 정확한 벤치마킹을 할 수 있을 것이라고 기대할 수 있는가?

그 답은 물론 '그렇다'이다. 본 연재의 Part 2에서 필자는 한 벤치마킹 프레임워크를 제시할 예정이다. 그 프레임워크는 벤치마킹 관련 여러 함정을 처리한다. 그러니 다운로드해 안심하고 쓰시라. 이는 또한 대부분의 벤치마킹 요구에도 쉽게 적용할 수 있다. 목적 코드를 작업 오브젝트의 Callable 또는 Runnable 타입으로 패키징하고 Benchmark 클래스의 메서드 하나를 호출하기만 하면 된다. 그 외 다른 것, 성능 측정, 통계 계산, 결과 보고서 생성 등은 프레임워크가 자동으로 수행한다.

Listing 1의 main을 Listing 2의 코드로 교체하여 그 프레임워크를 적용하고, 벤치마크를 다시 수행하였다.

public static void main(String[] args) throws Exception {
    Runnable task = new Runnable() { public void run() {
        int value = 0;
        for (int i = 0; i < 100 * 1000 * 1000; i++) {
            value = calculate(value);
    } };
    System.out.println("Cliff Click microbenchmark: " + new Benchmark(task));

필자의 설정으로 이 코드를 수행하여, 다음의 결과를 얻었다.

  1. 평균 = 20.241 ms ...
  2. 평균 = 20.246 ms ...
  3. 평균 = 26.928 ms ...
  4. 평균 = 26.863 ms ...

마침내, A와 B는 본질적으로 거의 같은 수행 시간을 보인다. 인자 확인이 동일한 C와 D 역시 거의 동일한 수행 시간을 보인다.

Benchmark를 사용하면 기대한 결과를 얻는다. 아마도 이는 내부적으로 task를 여러 번 반복 수행하기 때문일 것이다. 더구나 이는 실행 프로파일이 안정화 상태에 돌입하기까지의 예열 결과는 무시하고, 오직 정확한 결과만을 수집한다. 반면 Listing 1의 코드에서는 즉시 실행 결과를 측정하는데, 이렇게 얻는 결과는 의도한 코드 자체보다 JVM 동작과 더 관련이 많을 수 있다. 상기 실험에서 보듯이, Benchmark에서는 이러한 JVM 동작의 영향을 배제하여, 신뢰할 수 있는 결과의 통계를 계산한다.

그렇지만, 이 프레임워크를 바로 사용하면 안 된다. 본 글이 다루는 일정 수준의 지식, 특별히 동적 최적화에 관련된 숨은 이슈들과 Part 2에서 다룰 몇 가지 해석 상 문제와도 친숙해져야 한다. 절대 맹목적으로 수치를 신뢰하면 안 된다. 수치를 얻은 방법도 알아야 한다.


원칙적으로 코드 실행 시간을 측정하는 것은 단순하다.

  1. 시작 시각을 기록한다.
  2. 코드를 실행한다.
  3. 종료 시각을 기록한다.
  4. 시각 차를 계산한다.

대부분의 자바 프로그래머는 아마도 본능적으로 Listing 3과 같은 코드를 작성할 것이다.

long t1 = System.currentTimeMillis();;    // task is a Runnable which encapsulates the unit of work
long t2 = System.currentTimeMillis();
System.out.println("My task took " + (t2 - t1) + " milliseconds to execute.");

장시간 수행하는 작업에서는 Listing 3의 접근법이 대체로 무난하다. 예를 들어, task 실행에 1분이 걸린다면, 필자가 아래에서 다루려는 정밀도는 그다지 이슈가 되지 않는다. 그러나 task 실행 시간이 감소할수록, 벤치마킹 결과는 부정확해진다. 벤치마킹 프레임워크는 어떤 task든 자동으로 처리해야 하기 때문에, Listing 3의 실행 결과를 task의 내용과 관계없이 보장해야 한다.

한 가지 문제는 정밀도다. System.currentTimeMillis의 이름이 의미하듯이, 이 메서드는 명목상 밀리초 수준의 정밀도를 보장한다(참고자료 참조). 만일 결과가 ±1밀리초의 임의 오류를 포함한다고 가정하고 실행 시간 측정에서 1퍼센트 이내의 오류를 허용한다면, System.currentTimeMillis는 200ms 이내의 작업 실행 시간 측정에는 부적합하다(시간차 측정에는 2ms까지 더해진 두 번의 오류를 포함하기 때문이다).

현실적으로 System.currentTimeMillis는 ~10-100배까지 부정확한 정밀도를 제공할 수도 있다. 본 메서드의 Javadocs을 보면 다음과 같다.

반환값의 시간 단위는 밀리초지만, 값의 정밀도는 기반 운영체제에 따라 달라서, 단위가 이보다 커지는 경우도 있습니다. 예를 들어, 많은 운영체제에서는 시간을 10밀리초 단위로 계측합니다.

알려진 정밀도는 표 1과 같다.

정밀도플랫폼소스(참고자료 참조)
55 ms윈도 95/98Java Glossary
10 ms윈도 NT, 2000, XP 단일 프로세서Java Glossary
15.625 ms윈도 XP 다중 프로세서Java Glossary
~15 ms윈도(아마도 XP)Simon Brown
10 ms리눅스 2.4 커널Markus Kobler
1 ms리눅스 2.6 커널Markus Kobler

따라서 Listing 3의 코드는 대략 10초 이내에 수행되는 작업에는 부적합할 수 있다.

System.currentTimeMillis는 장시간 실행되는 작업에도 영향을 미칠 수 있다. 이 때 유념해야 할 이슈는 System.currentTimeMillis가 벽시계 시각을 반영하도록 되어 있다는 점이다. 이는 표준시에서 섬머타임으로의 변화 또는 NTP(Network Time Protocol) 동기화 등으로 시각값이 앞뒤로 갑자기 튈 수 있다는 뜻이다. 드물기는 하더라도, 이러한 시각값 보정으로 인해 오류투성이의 벤치마크 결과를 얻을 수도 있다.

JDK 1.5에서는 훨씬 높은 정밀도의 API인 System.nanoTime (참고자료 참조)을 제공한다. 이 메서드는 임의의 오프셋 이후의 나노초를 돌려 준다. 이와 관련된 몇 가지 유념할 핵심은 다음과 같다.

  • 이는 시간차 측정에만 유용하다.
  • 정확도와 정밀도(참고자료 참조)는 최소한 System.currentTimeMillis보다는 낫다(수준이 거의 같을 수도 있다).
  • 현대적인 하드웨어와 운영체제에서는 마이크로초 범위 내에서 정확도와 정밀도를 제공할 수 있다.

결론: 벤치마킹에서는 System.nanoTime이 더 나은 결과를 보장하므로, 반드시 이 메서드를 사용하라. 그러나 벤치마킹 코드에서는 여전히 이 메서드가 System.currentTimeMillis보다 그다지 나을 것이 없을 가능성까지 고려해야 한다.

JDK 1.5는 또한 ThreadMXBean 인터페이스(참고자료 참조)를 제공한다. 이 인터페이스에는 몇 가지 기능이 있지만, 특별히 벤치마킹과 관련된 getCurrentThreadCpuTime 메서드가 있다(참고자료 참조). 비록 가능성일 뿐이지만, 이 메서드로는 단순히 (벽시계) 경과 시간이 아닌 현재 스레드의 실제 CPU 사용 시간을 측정할 수 있다. 이렇게 측정한 사용 시간은 벽시계 경과 시간 대비 작거나 같다.

불행히도 getCurrentThreadCpuTime은 다음 몇 가지 문제점을 안고 있다.

  • 이 메서드는 독자들이 사용하는 플랫폼에서 지원되지 않을지도 모른다.
  • 지원하는 플랫폼마다 이 메서드의 의미가 다를 수도 있다(예를 들어, I/O를 사용하는 스레드에 I/O 처리 관련 CPU 시간이 포함될 수도 있고, I/O 처리 관련 CPU 시간은 OS 스레드에 대신 포함될 수도 있다).
  • ThreadMXBean Javadocs는 불길한 경고를 포함한다. "일부 자바 가상 기계 구현에 따라 스레드 CPU 측정을 활성화하면, 값비싼 비용을 치를 수도 있다." (이는 OS에 특화된 이슈다. 일부 OS에서는 스레드 CPU 측정에 필요한 마이크로어카운팅(microaccounting)을 항상 켜 두는 경우도 있어서, getCurrentThreadCpuTime이 추가적인 성능 저하를 유발하지는 않는다. 다른 대부분의 경우 기본으로 이것이 꺼져 있는데, 이를 활성화하면 그 프로세스의 모든 스레드 또는 시스템 내의 모든 스레드에서 성능 저하가 일어날 수도 있다.)
  • 정밀도가 불명확하다. (명목상 나노초의 정밀도로 결과를 돌려 주므로 이는 자연스럽게 System.nanoTime과 동일한 정확도와 정밀도를 갖는다고 생각할 수 있다. 그러나 이렇게 언급하는 문서는 어디에도 찾을 수 없고, 이 메서드가 훨씬 더 부정확하다는 보고서가 하나 있을 뿐이다(참고자료 참조). getCurrentThreadCpuTime을 사용한 필자의 경험으로는 nanoTime 대비 이 메서드가 더 작은 평균 실행 시간을 산출하였다. 필자의 데스크톱 설정에서 실행 시간은 0.5에서 1퍼센트 가량 적었다. 불행히도, 측정 결과의 일관성이 떨어지기는 한다. 예를 들어, 표준편차가 세 배씩 벌어지기도 했다. N2 솔라리스 10 기계에서 실행 시간은 5에서 10퍼센트 적었다. 이 차이가 증가하는 경우는 없었고, 때로는 아주 큰 감소를 보이기도 했다.)
  • 최악의 경우로 현재 스레드가 사용하는 CPU 시간이 실제 작업 수행 시간과 서로 연관성이 없을 수도 있다. 호출 쪽 스레드(CPU 시간을 측정하는 현재 스레드)를 가진 작업이 스레드 풀(thread pool)만 생성하여 한 움큼의 부분 작업을 풀로 보낸 후 풀이 종료할 때까지 대기하기만 한다고 생각해 보자. 스레드를 호출하기 위해 사용하는 CPU 시간은 미미하다. 반면 작업을 마치기 위해 걸린 시간은 훨씬 길다. 따라서 전적으로 오해를 살만한 실행 시간을 보고할 수도 있다.

이러한 이슈들 덕분에 getCurrentThreadCpuTime을 기본 적용하여 일반적인 목적의 벤치마킹 프레임워크를 작성하는 것은 너무나도 위험하다. Part 2에서 소개하는 Benchmark 클래스에서는 특별한 설정을 통해 선택적으로 이 메서드를 사용하도록 한다.

이상 소개한 시간 측정 API에 대해 한 가지 추가 주의 사항을 덧붙이면 다음과 같다. 이 API들에는 실행 오버헤드가 따른다. 이 오버헤드는 측정치 왜곡을 막으면서 API가 호출 가능한 빈도에 영향을 준다. 이러한 영향의 정도는 플랫폼에 따라 다르다. 예를 들어, 최근 윈도 시스템에서는 System.nanoTime이 마이크로초 이내에 실행되는 OS 함수 호출을 포함한다. 즉, 1퍼센트 이내의 측정 정확성을 유지하려면, 매 100마이크로초 이내에 한 번 이상 이 API가 호출되면 곤란하다. (반대로, System.currentTimeMillis는 글로벌 변수를 읽기만 하면 되므로, 나노초 이내에 아주 빨리 실행된다. 측정 정확성을 위해서라면 이 API를 좀 더 자주 호출하는 편이 낫다. 물론, 글로벌 변수가 매우 자주 — 표 1에 따르면 약 10에서 15밀리초마다 — there's no point in calling it more frequently.) 갱신되는 것은 아니므로, 이 API도 마냥 자주 호출할 수는 없다.) 반면에, 대부분의 솔라리스와 몇몇 리눅스(Linux®) 기계에서는 System.nanoTime이 System.currentTimeMillis보다 대체로 더 빨리 실행된다.


성능 관련 수수께끼에서, Benchmark가 정상적인 결과를 도출한 것은 초기 성능에 대비해 task의 성능 안정화 단계의 실행 프로파일을 측정한 덕분이다. 대부분의 자바 구현은 아주 복잡한 성능 수명 주기를 갖는다. 보통 초기 실행 성능은 상대적으로 느리며, 안정화 상태에 돌입하기까지 한 동안 성능이 대폭 (대개 불연속적으로) 향상된다. 독자가 안정화 상태에서 성능 측정이 필요하다면, 안정화 상태로 이끄는 모든 요소를 이해할 필요가 있다.

JVM은 전형적으로 클래스를 처음 사용할 때만 로딩한다. 따라서 task의 첫 실행 시간에는 사용하는 모든 클래스를 로딩하는 (미리 클래스를 로딩하지 않았다면) 시간도 포함한다. 대체로 클래스 로딩은 디스크 I/O나 파싱, 검증(verification)과 연관이 있어, task의 첫 실행 시간을 대폭 부풀린다. 보통 task를 여러 번 반복 수행하여 이러한 효과를 어느 정도 중화시킬 수는 있다. (필자는 여기서 항상 대신 대개라는 표현을 사용했다. task에 복잡한 분기 동작이 있어 모든 클래스를 처음 실행에서 다 사용하지 않을 수도 있기 때문이다. 충분한 시간을 두고 코드를 실행하면, 희망사항이긴 하나 가능한 모든 분기를 다 수행하여 관련 클래스도 모두 로딩할 것이다.)

별도로 작성한 클래스로더를 쓰게 되면, 쓰레기(garbage)가 된 클래스를 JVM이 언로딩(unloading)할 수 있는 문제가 생긴다. 이것이 성능 상에 중대한 장애물이 되는 것은 아니지만, 벤치마크 중에 이런 일이 생기면 이상적인 결과를 얻기는 어렵다.

벤치마크 전후에 ClassLoadingMXBean의 getTotalLoadedClassCount와 getUnloadedClassCount 메서드(참고자료 참조)를 호출하면, 벤치마크 도중에 클래스 로딩/언로딩이 일어나는지 확인할 수 있다. 클래스 수에 변화가 있다면, 안정화 상태에 돌입하지 않은 것이다.

현대적인 JVM은 JIT(Just-in-time) 컴파일 수행 전에 프로파일링 정보를 얻고자 (대개 순수 인터프리트 방식으로) 얼마간 코드를 수행한다(참고자료 참조). 이것이 벤치마킹에서 의미하는 바는 안정화 상태의 실행 프로파일에 도달하기 전에 작업을 여러 번 반복 수행해야 할 수도 있다는 점이다. 한 예로, 썬의 클라이언트/서버 HotSpot JVM의 현재 기본 동작은 코드 영역을 포함하는 메서드를 JIT 컴파일하기에 앞서, 1500회(클라이언트) 또는 1만 회 (서버) 정도 해당 코드 영역을 실행해야 한다.필자는 코드 영역(code block)이라는 표현을 사용했다. 코드 영역은 전체 메서드뿐 아니라 한 메서드 내의 코드 일부분을 가리킬 수도 있다. 한 예로, 많은 JVM은 섬세하게도 반복 수행할 코드 영역을 포함하는 메서드를 단 한 번만 호출하더라도 그 코드 영역에 "요주의" 코드를 포함하는지 판별할 수 있다. 이 점에 대해서는 본 문서의 스택 내 치환 절에서 상세히 다루겠다.

따라서 안정화 상태의 성능 벤치마킹에는 다음과 같은 것이 필요하다.

  1. 모든 클래스를 로딩하기 위해 task를 한 번 수행한다.Execute task once to load all classes.
  2. 안정화 상태의 프로파일에 도달한 것을 보장받기 위해 충분히 여러 번 task를 반복 수행한다.
  3. 실행 시간 예상치를 얻기 위해 몇 번 더 task를 반복 수행한다.
  4. 단계 3의 결과로 충분히 큰 누적 실행 시간을 얻기 위해 필요한 task의 반복 횟수 n을 계산한다.
  5. task를 n번 더 실행하여 전체 실행 시간 t를 측정한다.
  6. 실행 시간을 t/n으로 계산한다.

task의 n번(n >= 1) 수행을 측정하는 숨은 목적은 누적 실행 시간이 매우 커서 앞서 언급한 시간 측정 오류를 무시할 수 있도록 하기 위함이다.

여기서 단계 2는 다소 모호하다. JVM이 작업 최적화를 완료한 시점을 어떻게 알 수 있다는 말인가?

실행 시간을 측정하여, 그 측정 결과가 어느 일관된 값으로 수렴하는지를 보고 판단할 수도 있다. 그럴 듯한 말이다. 하지만 JVM이 여전히 프로파일링 중이며 단계 5에 돌입하자 갑자기 그 프로파일링을 JIT에 적용한다고 가정해 보자. 이 방법은 실패다. 다음 단계에서 문제를 일으킬 수 있다.

더군다나 수렴 여부는 어떻게 정량화할 수 있단 말인가?

요즘 썬의 HotSpot JVM은 그저 프로파일링 단계를 한 번 거친 후 가능할 경우 컴파일을 한다. 
역최적화는 무시하더라도, 핫스팟에서 프로파일링 코드의 오버헤드가 지나치게 심각해 연속 컴파일(continuous compiling)은 현재 되지 않는다(참고자료 참조).

이 프로파일링 오버헤드 문제의 해법이 있다. 예를 들어 JVM은 두 가지 버전의 메서드, 즉 프로파일링 코드를 담지 않은 빠른 것과 프로파일링 코드가 들어 있는 느린 것을 유지할 수 있다(참고자료 참조). JVM은 주로 빠른 메서드를 사용하지만 종종 성능에 심각하게 영향을 미치지 않고 프로파일링 정보를 유지하는 느린 메서드로 교체하기도 한다. 또는 놀고 있는 코어를 사용할 수 있을 때마다 JVM이 느린 메서드를 동시에(concurrently) 실행할 수도 있다. 이 같은 기법 덕분에 연속 컴파일이 앞으로 표준이 될 수도 있다.

(Benchmark 클래스가 사용하는) 다른 방법 중 하나는 단순하게 미리 정해진 합리적으로 긴 시간 동안 작업을 지속적으로 수행하는 것이다. 10초의 예열 시간이면 충분하다(Click의 보고서 33쪽을 보라). 물론 이 방법보다 측정 결과가 일정한 값에 수렴할 때까지 실행 시간을 측정하는 편이 더 믿을 만할 수도 있다. 그러나 적어도 이 방법은 더 단순하게 구현할 수 있다. 또한 이 방법은 더 쉽게 매개변수로 표현할 수 있다. 사용자가 직관적으로 개념을 이해하고 더 긴 시간의 예열로 (더 긴 시간의 벤치마킹 시간 비용으로) 더 신뢰성 있는 결과를 얻을 수 있다는 점을 쉽게 납득할 수 있다.

JIT 컴파일이 일어나는 시점을 판단할 수 있다면, 안정화 상태의 성능에 도달했다는 더 큰 확신을 얻을 수도 있다. 특별히 안정화 상태에 돌입했다고 생각하여 벤치마킹을 시작했지만 벤치마킹 중에 컴파일이 일어난 것을 알았다면 벤치마킹을 중단하고 다시 시도할 수도 있다.

필자의 지식으로는 JIT 컴파일 수행 여부를 확인할 수 있는 완벽한 방법은 없다. 가장 좋은 방법이라면 벤치마킹 전후로CompilationMXBean.getTotalCompilationTime 을 호출하는 것이다. 다만, CompilationMXBean 구현에 오류가 많아, 이 방법도 문제가 있을 수 있다. 또 다른 기술은 -XX:+PrintCompilation 옵션을 사용하여 stdout 출력 결과물을 파싱하는 (또는 그냥 눈으로 확인하거나) 방법이다(참고자료 참조).


예열 이슈 이외에도 JVM의 동적 컴파일로 인해 벤치마킹 관련 다른 여러 유의 사항이 발생한다. 이러한 유의 사항은 미묘하다. 더 나쁜 것은 이러한 유의 사항을 다루는 책임은 전적으로 벤치마킹 프로그래머에게 달려 있다는 점이다. 벤치마킹 프레임워크에서 이러한 이슈를 다루기 위해 할 수 있는 일은 별로 없다(이 글의 캐싱과 준비 절에서 벤치마크 프로그래머가 책임져야 할, 대부분 보통의 상식에 해당하는 몇 가지 이슈를 언급한다).

한 가지 유의 사항이 바로 역최적화(deoptimization)다(참고자료 참조). JVM은 컴파일한 메서드 사용을 중단하고 동일 메서드를 다시 컴파일하기 앞서 일정 기간 동안 인터프리트(interpret) 상태로 돌아간다. 이러한 현상은 먼저 최적화한 동적 컴파일러가 더 이상 적절하지 않다고 판단할 때 일어난다. 한 예가 동일 호출 변형(monomorphic call transformation)을 무효화하는 클래스 로딩이다. 또 다른 예가 드물게 일어나는 트랩(uncommon traps)이다. 코드 영역을 컴파일할 때 보통은 가장 가능성이 높은 코드 수행 경로를 따라 컴파일하고, (예외 경로와 같이) 수행 가능성이 떨어지는 코드 분기는 남겨 둔다. 그러다, 드물게 수행되어야 할 트랩이 자주 수행되는 코드로 판명이 되면, 그제서야 남겨둔 코드가 핫스팟(hotsopt) 경로가 되어 다시 컴파일한다.

따라서 앞 절의 충고를 따라 안정화 상태의 성능에 도달한 것으로 보이더라도, 성능이 갑작스럽게 변할 수 있다는 점을 명심해야 한다. 이것이 바로 벤치마크 내에서 JIT 컴파일을 탐지해야 하는 주요 이유 중 하나다.

또 다른 유의 사항은 특정 코드 구조 최적화를 돕는 고급 JVM 기능 중 하나인 스택 내 치환이다(참고자료 참조). Listing 4의 코드를 살펴 보자.

private static final int[] array = new int[10 * 1000];
static {
    for (int i = 0; i < array.length; i++) {
        array[i] = i;

public static void main(String[] args) {
    long t1 = System.nanoTime();

    int result = 0;
    for (int i = 0; i < 1000 * 1000; i++) {    // outer loop
        for (int j = 0; j < array.length; j++) {    // inner loop 1
            result += array[j];
        for (int j = 0; j < array.length; j++) {    // inner loop 2
            result ^= array[j];

    long t2 = System.nanoTime();
    System.out.println("Execution time: " + ((t2 - t1) * 1e-9) +
        " seconds to compute result = " + result);

JVM이 메서드 호출을 센다면 main은 단지 한 번만 수행되기 때문에 컴파일 버전의 main을 절대 사용할 수 없다. 이 문제를 해결하기 위해, JVM은 메서드 내부의 코드 영역 실행도 셀 수 있다. 특별히, Listing 4의 코드라면, JVM은 각 루프가 얼마나 많이 반복 수행되는지 추적할 수 있다(루프의 닫는 괄호는 "역방향 분기"를 유발한다). 기본적으로 어떤 루프든 1만 번 반복하면 전체 메서드를 컴파일한다. 단순한 JVM이라면 main이 다시 호출되지 않는다고 하여 이 컴파일된 코드를 절대로 사용하지 않을 것이다. 그러나 OSR을 사용하는 JVM은 똑똑해서 메서드 호출 도중에도 현재 코드를 새로 컴파일한 코드로 치환할 수 있다..

첫 눈에도 OSR이 멋지지 않은가? 이는 마치 JVM이 어떠한 코드 구조도 다룰 수 있으며 최적의 성능까지 제공하는 것으로 보인다. 그러나 안타깝게도 OSR에는 널리 알려지지 않은 단점이 있다. OSR을 사용한 코드 품질이 차선이라는 것이다. 예를 들면, OSR은 간혹 루프 호이스팅(loop-hoisting), 배열 경계 검사 생략(array-bounds check elimination), 또는 루프 되돌림(loop unrolling) 등을 하지 않는다(참고자료 참조). OSR이 사용되면 최고의 성능을 벤치마킹할 수 없다는 뜻이다.

최적의 성능을 원한다면, OSR이 발생할 수 있는 위치를 파악하여 OSR을 가급적 피할 수 있도록 코드 구조를 재구성해야 한다. 전형적인 방법은 주요 내부 루프를 독립된 메서드로 옮기는 것이다. 예를 들면,Listing 4의 코드를 Listing 5와 같이 다시 쓰는 것이다.

public static void main(String[] args) {
    long t1 = System.nanoTime();

    int result = 0;
    for (int i = 0; i < 1000 * 1000; i++) {    // sole loop
        result = add(result);
        result = xor(result);

    long t2 = System.nanoTime();
    System.out.println("Execution time: " + ((t2 - t1) * 1e-9) +
        " seconds to compute result = " + result);

private static int add(int result) {    // method extraction of inner loop 1
    for (int j = 0; j < array.length; j++) {
        result += array[j];
    return result;

private static int xor(int result) {    // method extraction of inner loop 2
    for (int j = 0; j < array.length; j++) {
        result ^= array[j];
    return result;

Listing 5에서 add와 xor 메서드는 각각 100만 회 수행되므로, 최적의 형태로 JIT 컴파일해야 한다. 이 특정 코드의 경우 필자의 설정으로 처음 세 번의 실행에서 실행 시간 측정치가 각각 10.81, 10.79, 10.80초였다. 반면, Listing 4의 코드에서는(모든 루프가 main 안에 있고 OSR을 유발함) 두 배의 실행 시간이 걸렸다(첫 세 번의 실행에서 21.61, 21.61, 21.6초 소요).

OSR과 관련된 마지막 조언은 다음과 같다. 이는 프로그래머가 게을러서 모든 것을 main과 같이 단일 메서드에 넣을 때나 주의해야 할 벤치마킹에서의 성능 문제라는 것이다. 실제 애플리케이션에서는 프로그래머가 자연스럽게 메서드를 잘게 나눈다. 나아가, 성능이 문제가 되는 코드는 장시간 수행되고 중요한 메서드를 여러 번 호출한다. 따라서 현실 세계 코드는 OSR 성능 문제와는 대개 관련이 없다. 독자의 애플리케이션에서도 대체로 그 문제를 염려할 필요가 없을 것이다. 그렇지 않다면(그것이 문제가 되지 않을 것이라고 증명할 수 없다면) 그저 고상한 코드로 변형하도록 해라. 기본적으로 Benchmark는 통계치를 얻으려고 여러 번 작업을 반복 수행하므로 성능 이슈로서의 OSR을 제거하는 부수적인 효과도 얻는다.

또 다른 미묘한 주의 사항은 죽은 코드 삭제다(참고자료 참조). 몇 가지 환경에서 컴파일러는 일부 코드가 결과에 절대 영향을 미치지 못할 것이라고 판단할 수 있으므로, 컴파일러가 그 코드를 삭제할 수 있다. Listing 6은 정적으로 (즉 javac가 컴파일 시점에) 이런 일을 수행하는 대표적인 예를 보여 준다.

private static final boolean debug = false;

private void someMethod() {
    if (debug) {
        // do something...

javac는 Listing 6에서 if (debug) 영역 내의 코드가 절대 실행되지 않을 것이라는 사실을 알고, 그 코드를 제거한다. 동적 컴파일러는 특히 메서드 인라이닝(method inlining)이 일어날 때, 여러 방법으로 죽은 코드를 판별한다. 벤치마킹 중 DCE 관련 문제는 실행되는 코드가 전체 코드 대비 단지 작은 일부분만 실행될 수도 있다는 것이다. 즉 전체적인 계산은 아예 일어나지도 않을 수 있다는 말이다. 이는 결국 짧은 실행 시간을 유도하지만 궁극적으로 잘못된 결과값을 얻을 뿐이다.

컴파일러가 죽은 코드라고 판명하는 모든 영역에 대해 잘 설명해 놓은 것을 찾을 수 없었다(참고자료 참조). 단순히 도달할 수 없는 코드는 물론 죽은 코드다. 그러나 JVM은 종종 적극적으로 여러 DCE 정책을 적용한다..

예를 들면, Listing 4의 코드를 다시 살펴보자. main은 result 값을 계산할 뿐 아니라 출력하기 위해 result를 사용한다. 만일 약간 코드를 변경하여 println에서 result를 제거한다고 가정해 보자. 이 경우라면, 적극적인 컴파일러는 result를 계산할 필요가 없다고 판단할지 모른다.

이것은 더 이상 이론적인 주의 사항이 아니다. Listing 7의 코드를 보자.

public static void main(String[] args) {
    long t1 = System.nanoTime();

    int result = 0;
    for (int i = 0; i < 1000 * 1000; i++) {    // sole loop
        result += sum();

    long t2 = System.nanoTime();
    System.out.println("Execution time: " + ((t2 - t1) * 1e-9) +
        " seconds to compute result = " + result);

private static int sum() {
    int sum = 0;
    for (int j = 0; j < 10 * 1000; j++) {
        sum += j;
    return sum;

필자의 설정으로는 일관되게 Listing 7의 코드를 4.91초 내에 수행했다. 이 코드를 수정하여 println 구문에서 result 참조를 삭제하고 System.out.println("Execution time: " + ((t2 - t1) * 1e-9) + " seconds to compute result"); 라고 바꿔 보자. 이제 이 코드는 일관되게 0.08초 이내에 수행된다. 명백하게 DCE가 모든 계산을 삭제해 버렸다(DCE의 또 다른 예는 참고자료 참조).

벤치마킹을 원하는 계산을 DCE가 삭제하지 않도록 하려면, 그 계산에서 어떤 결과를 구하여 어떤 식으로든 (예를 들어, Listing 7의 println처럼) 그 결과를 사용해야 한다. Benchmark 클래스는 이것을 지원한다. 독자의 작업이 Callable이면 필요한 계산이 반드시 수행되도록 call() 메서드가 돌려 주는 계산 결과를 반드시 사용하도록 하라. 독자의 작업이 Runnable이면 필요한 계산이 반드시 수행되도록 작업의toString 메서드에서 계산된 내부 상태를 사용하도록 하라(Object의 toString 메서드를 오버라이드해야 한다). 이 원칙만 잘 지킨다면, Benchmark 클래스에서 DCE를 완전히 예방할 수 있다.

OSR처럼 (정해진 시간 동안 코드를 실행만 하고 결과는 사용하지 않는 경우가 아니라면) DCE 역시 현실 세계의 애플리케이션에서 대체로 이슈가 되지 않는다. 그러나 OSR과 달리 DCE는 대충 작성된 벤치마크에서 중대한 이슈가 될 수 있다. OSR에서는 적당히 부정확한 결과를 얻을 뿐이지만 DCE라면 총체적으로 잘못된 결과를 얻게 된다.


전형적인 JVM은 쓰레기 수집(garbage collection, GC)과 객체 마무리(object finalization, OF)의 두 가지 형태로 자원을 회수한다. 프로그래머의 관점에서 GC/OF는 대체로 비결정적이다. 이는 전적으로 프로그래머의 제어 영역 밖에 있을 뿐 아니라 JVM이 필요하다고 생각하는 어느 시점에든 발생할 수 있다.

작업과 연관된 GC/OF 시간은 벤치마크 결과에 포함되어야 한다. 예를 들어, 초기 실행 시간은 짧지만 점차 엄청난 GC 시간을 유발한다면, 그 작업을 빠르다고 주장할 수 없다. (어떤 작업은 객체를 생성할 필요가 없는 경우도 있다. 이 경우 대신 이미 생성된 객체를 참조할 뿐이다. 배열 엘리먼트에 접근하되 소요 시간만 측정하는 벤치마크라면, 배열을 생성할 필요는 없으며 대신 다른 어딘가에서 생성된 배열 객체를 그 작업에서는 참조만 하게 된다.)

그러나 동일한 JVM 세션에서 목표 작업의 GC/OF를 다른 코드에 의한 GC/OF와 구별할 필요가 있다. 단 한 가지 방법은 벤치마킹 시작 전에 JVM의 메모리를 정리하는 것이다. 또한 측정이 끝나기 전에 그 작업에 의한 GC/OF도 말끔한 종료를 보장하도록 최대한 노력해야 한다.

System 클래스는 gc와 runFinalization 메서드를 제공한다. 이는 JVM의 정리에 사용할 수 있다. 이 메서드들에 대해 javadoc에서 다음과 같이 말하는 것을 유의하라. "제어가 이 메서드 호출에서 돌아올 때면, 자바 가상 머신은 GC/OF를 수행하기 위해 최선을 다해야 한다."

Part 2에서 제공할 Benchmark 클래스는 GC/OF를 다음과 같은 방법으로 다룬다.

  1. 어떤 측정이든 시작 전에 cleanJvm이라고 이름 붙인 메서드를 호출한다. 이 메서드는 메모리 사용량이 안정화되고 마무리할 객체가 남지 않을 때까지 적극적으로 System.gcSystem.runFinalization을 필요한 만큼 많이 반복 호출한다.
  2. 기본적으로 60회 실행을 측정한다. 각 측정은 최소 1초 이상 지속된다(필요하면 각 측정에서 한 작업을 여러 번 실행하여, 이 시간 이상 소요되도록 한다). 따라서 총 실행 시간은 적어도 1분 이상은 되도록 한다. 이는 GC/OF의 실행 주기가 60회 측정 중에 골고루 분산되어 전체 동작을 정확히 측정할 수 있는 시간이다.
  3. 모든 측정이 끝나면, 마지막으로 cleanJvm을 호출한다. 그러나 이번에는 소요 시간도 함께 측정한다. 최종 정리 작업이 작업 전체 수행 시간의 1퍼센트 이상이 되면, 벤치마크 결과에서 GC/OF 비용이 이번 측정에서 정확히 고려되지 않았을 가능성을 경고한다.
  4. 각 측정에서 GC/OF는 마치 잡음같이 동작하므로, 신뢰성 있는 결론 도출에는 통계를 사용한다.

한 가지 유의점은 다음과 같다. 필자가 Benchmark를 작성했을 때, Listing 8에서 보는 코드처럼 각 측정 내부에 GC/OF 비용을 고려하도록 노력했다.

protected long measure(long n) {
    cleanJvm();    // call here to cleanup before measurement starts

    long t1 = System.nanoTime();
    for (long i = 0; i < n; i++) {;
    cleanJvm();    // call here to ensure that task's GC/OF is fully included
    long t2 = System.nanoTime();
    return t2 - t1;

측정 루프 내에서 System.gc와 System.runFinalization을 호출하면 왜곡된 GC/OF 비용을 측정할 수 있는 문제가 있다. 특별히 System.gc는 stop-the-world 수집기(역자 주: 모든 자바 스레드를 중지하고 쓰레기를 수집)를 써서 모든 세대(generation)에 걸쳐 전체적으로 쓰레기를 수집한다(참고자료 참조). (이것이 기본 JVM 동작이긴 하나, JVM 옵션 중에 -XX:+ExplicitGCInvokesConcurrent와 -XX:+DisableExplicitGC.도 있긴 하다.) 이와 대조적으로 애플리케이션에서 주로 사용하는 쓰레기 수집기가 서로 완전히 다르게 동작할 수도 있다. 예를 들면, 설정상 쓰레기 수집기가 동시 동작할 수 있고, 적은 노력으로 (특히 젊은 세대 중에) 부분적으로만 이리 저리 수집할 수도 있다. 이와 비슷하게, 마무리 처리기(finalizer)는 보통 백그라운드 작업으로 처리되어, 그 비용이 시스템의 유휴 시간에 대체로 흡수되어 버린다.


하드웨어/운영체제의 캐시로 인해 종종 벤치마킹이 복잡할 수도 있다. 한 가지 단순한 예가 파일 시스템 캐싱이다. 이 캐싱은 하드웨어나 OS에서 일어난다. 파일에서 바이트를 읽는 소요 시간을 벤치마킹한다면, 벤치마크 코드는 동일한 파일을 여러 번 반복하여 읽게 된다(같은 벤치마크를 여러 번 수행하는 것도 마찬가지다). 그러면, 첫 읽기 이후 I/O 시간이 급격히 감소한다. 랜덤 파일 읽기 속도를 벤치마킹하려면, 가급적 캐싱을 방지하기 위해 다른 파일을 읽도록 해야 한다.

CPU의 메인 메모리 캐싱은 매우 중요하여 특별한 주의를 요한다(참고자료 참조). 지금까지 약 20년 간 CPU 속도는 기하급수적으로 빨라졌다. 반면 메인 메모리 속도는 완만하게 빨라졌다. 이들 간의 속도 불일치를 개선하기 위해 현대적인 CPU는 광범위하게 캐싱을 사용한다(CPU 상의 대부분의 트랜지스터가 캐싱 용도로 할당되어 있다). CPU 캐싱을 적절히 활용하는 프로그램은 그렇지 않은 프로그램 대비 인상적으로 나은 성능을 보인다(대부분의 현실 세계 작업량이란 CPU의 이론적인 성능의 일부분만을 사용할 뿐이다).

프로그램이 CPU 캐싱을 적절히 활용하기에는 여러 요소가 작용한다. 예를 들면, 현재의 JVM은 메모리 접근을 최적화하기 위해 많은 노력을 들였다. 힙(heap) 크기를 재조정하고, 힙의 값을 CPU 레지스터로 올리고, 스택을 할당하고, 객체 폭발(object explosion, 참고자료 참조)을 수행한다. 그러나 주요한 요소 중 하나는 단지 데이터 집합 크기다. 작업의 데이터 크기가 n이라고(예를 들어, 길이 n의 배열을 사용한다고) 하자. 단순히 단일 변수 n에 따른 벤치마킹으로 내린 결론은 상당한 오류를 내포할 수도 있다. 따라서 여러 n 값에 걸쳐 벤치마킹을 해야 한다. J. P. Lewis와 Ulrich Neumann의 문서에 훌륭한 예가 있다(참고자료 참조). 함수 인자 n에 따라(여기서는 배열 크기) C 언어 대비 자바의 FFT 성능 그래프를 도출하여, n 값에 따라 C 언어 대비 두 번 빠르고 두 번 느린 식으로 자바 성능이 요동치는 점을 발견하였다.


벤치마킹 함정은 독자가 개발하는 벤치마킹으로 시작하고 끝나지 않는다. 벤치마킹 프로그램 수행 전에 시스템의 여러 부분을 함께 다루어야 한다.

저사양 하드웨어, 주로 랩톱에서 전원 관리(예를 들면, 고급 전원 관리(Advanced Power Management, APM) 또는 고급 설정 및 전원 인터페이스(Advanced Configuration and Power Interface, ACPI)가 벤치마킹 도중에 상태를 바꾸지 않도록 해야 한다. 컴퓨터가 동면 모드로 진입하듯이 급격히 전력 상태가 변하면, 벤치마킹 결과를 얻지 못하거나 잘못된 결과를 얻을 수도 있다. 그러나 좀 더 주의가 필요한 또 다른 전력 상태 변화가 있다. 주로 CPU 위주의 벤치마킹을 생각해 보자. 이 때 OS는 하드 드라이브의 전원을 끈다. 벤치마킹 종료 시점에 하드 드라이브를 쓰려고 하면, I/O 비중이 더 커질 수 있다. 또 다른 예로 인텔 스피드스텝(SpeedStep) 또는 이와 비슷하게 CPU 전력 사용량을 동적으로 조절하는 기술을 사용하는 시스템을 들 수 있는데, 이러한 효과를 중지하도록 OS 설정을 조정해야 한다.

벤치마킹 중에는 (CPU 부하를 모니터링할 목적이 아니라면) 다른 프로그램을 수행하지 말아야 한다. 불필요한 백그라운드 프로세스는 중단하는 편이 좋고, 벤치마킹 도중에 실행될 수도 있는 스케쥴된 프로세스(스크린 세이버 또는 바이러스 스캐너 등)는 막아야 한다.

윈도는 ProcessIdleTask API를 제공한다. 이는 벤치마킹 전에 대기 중인 노는 프로세스(idle process)를 실행하도록 한다. 이 API는 다음과 같이 커맨드라인에서 실행할 수 있다.

Rundll32.exe advapi32.dll,ProcessIdleTasks

한 동안 이 명령을 실행한 적이 없다면 이 명령을 실행할 때 몇 분까지도 걸릴 수 있다(연속적으로 실행하면 주로 몇 초 이내에 종료한다).

십여 가지 JVM 옵션이 벤치마킹에 영향을 끼친다. 몇 가지 관련 있는 옵션은 다음과 같다.

  • JVM 타입: 서버(-server) 대 클라이언트(-client)
  • 사용 가능한 충분한 메모리 보장(-Xmx)
  • 쓰레기 수집기 유형(고급 JVM은 다양한 튜닝 옵션을 제공한다. 그러나 주의해야 한다.)
  • 클래스 쓰레기 수집을 허용할 지 여부(-Xnoclassgc). 기본적으로 클래스 GC는 일어난다. -Xnoclassgc 사용이 좋지 않다는 의견도 있다.
  • 이스케이프 분석(escape analysis) 수행 여부(-XX:+DoEscapeAnalysis)
  • 대용량 페이지 힙 지원 여부(-XX:+UseLargePages)
  • 스레드 스택 크기 변경(예를 들어, -Xss128k)
  • JIT 컴파일러 항상 사용(-Xcomp), 항상 사용하지 않음(-Xint), 또는 핫스팟에서만 사용(-Xmixed, 이것이 기본 옵션이며 최고 성능 옵션이다.)
  • JIT 컴파일 전 누적할 프로파일 양(-XX:CompileThreshold), 백그라운드 JIT 컴파일(-Xbatch), 단계별 JIT 컴파일(-XX:+TieredCompilation)
  • 편향된 잠금(biased locking) 수행 여부(-XX:+UseBiasedLocking). JDK 1.6 이상에서는 자동으로 수행한다.
  • 실험적인 최신 성능 기법 활성화 여부(-XX:+AggressiveOpts)
  • 조건 검사(assertion) 활성화 여부(-enableassertions와 -enablesystemassertions)
  • 엄격한 네이티브 호출 검사 활성화 여부(-Xcheck:jni)
  • NUMA 멀티 CPU 시스템에서 메모리 위치 최적화 활성화(-XX:+UseNUMA)

벤치마킹은 대단히 힘들다. 명백하든 미묘하든 많은 요소가 결과에 영향을 줄 수 있다. 정밀한 결과를 얻으려면, 이러한 주의 사항을 다루는 벤치마킹 프레임워크를 사용하면서 가이드라인을 철저히 준수할 필요가 있다. 이제 Part 2에서는 믿을만한 자바 벤치마킹 프레임워크에 대해 배워 보자.

J2ee performance tips

posted Jun 18, 2010, 7:17 PM by Kuwon Kang

The following detailed tips have been extracted from the raw tips page
Performance planning for managers (Page last updated February 2001, Added 2001-03-21, Author Jack Shirazi, Publisher OnJava). Tips:

  • Include budget for performance management.
  • Create internal performance experts.
  • Set performance requirements in the specifications.
  • Include a performance focus in the analysis.
  • Require performance predictions from the design.
  • Create a performance test environment.
  • Test a simulation or skeleton system for validation.
  • Integrate performance logging into the application layer boundaries.
  • Performance test the system at multiple scales and tune using the resulting information
  • Deploy the system with performance logging features.
Balancing Network Load with Priority Queues (Page last updated December 2001, Added 2002-02-22, Author Frank Fabian, Publisher Dr. Dobb's). Tips:

  • Hardware traffic managers redirect user requests to a farm of servers based on server availability, IP address, or port number. All traffic is routed to the load balancer, then requests are fanned out to servers based on the balancing algorithm.
  • Popular load-balancing algorithms include: server availability (find a server with available processing capability); IP address management (route to the nearest server by IP address); port number (locate different types of servers on different machines, and route by port number); HTTP header checking (route by URI or cookie, etc).
  • Web hits should cater for handling peak hit rate, not the average rate.
  • You can model hit rates using gaussian distribution to determine the average hit rate per time unit (e.g. per second) at peak usage, then a poisson probability gives the probability of a given number of users simulatneously hitting the server within that time unit. [Article gives an example with gaussian fitted to peak traffic of 4000 users with a standard deviation of 20 minutes resulting in an average of 1.33 users per second at the peak, which in turn gives the probabilities that 0, 1, 2, 3, 4, 5, 6 users hitting the server within one second as 26%, 35%, 23%, 10%, 3%, 1%, 0.2%. Service time was 53 milliseconds, which means that the server can service 19 hits per second without the service rate requiring requests being queued.]
  • System throughput is the arrival rate divided by the service rate. If the ratio becomes greater than one, requests exceed the system capability and will be lost or need to be queued.
  • If requests are queued because capacity is exceeded, the throughput must drop sufficiently to handle the queued requests or the system will fail (the service rate must increase or arrival rate decrease). If the average throughput exceeds 1, then the system will fail.
  • Sort incoming requests into different priority queues, and service the requests according to the priorities assigned to each queue. [Article gives the example where combining user and automatic requests in one queue can result in a worst case user wait of 3.5 minutes, as opposed to less than 0.1 seconds if priority queues are used].
  • [Note that Java application servers often do not show a constant service time. Instead the service time often increases with higher concurrency due to non-linear effects of garbage collection].
Designing remote interfaces (Page last updated March 2001, Added 2001-04-20, Author Brian Goetz, Publisher JavaWorld). Tips:

  • Remote object creation has overheads: several objects needed to support the remote object are also created and manipulated.
  • Remote method invocations involve a network round-trip and marshalling and unmarshaling of parameters. This adds together to impose a significant latency on remote method invocations.
  • Different object parameters can have very different marshalling and unmarshaling costs.
  • A poorly designed remote interface can kill a program's performance.
  • Excessive remote invocation network round-trips are a huge performance problem.
  • Calling a remote method that returns multiple values contained in a temporary object (such as a Point), rather than making multiple consecutive method calls to retrieve them individually, is likely to be more efficient. (Note that this is exactly the opposite of the advice offered for good performance of local objects.)
  • Avoid unnecessary round-trips: retrieve several related items simultaneously in one remote invocation, if possible.
  • Avoid returning remote objects when the caller may not need to hold a reference to the remote object.
  • Avoid passing complex objects to remote methods when the remote object doesn't necessarily need to have a copy of the object.
  • If a common high-level operation requires many consecutive remote method calls, you need to revisit the class's interface.
  • A naively designed remote interface can lead to an application that has serious scalability and performance problems.
  • [Article gives examples showing the effect of applying the listed advice].
Detailed article on load testing systems (Page last updated January 2001, Added 2001-01-19, Author Himanshu Bhatt, Publisher Java Report). Tips:

  • Internet systems should be load-tested throughout development.
  • Load testing can provide the basis for: Comparing varying architectural approaches; Performance tuning; Capacity planning.
  • Initially you should identify the probable performance and scalability based on the requirements. You should be asking about: numbers of users/components; component interactions; throughput and transaction rates; performance requirements.
  • Factor in batch requirements and performance characteristics of dependent (sub)systems. Note that additional layers, like security, add overheads to performance.
  • Logging and stateful EJB can degrade performance.
  • After the initial identification phase, the target should be for a model architecture that can be load-tested to feedback information.
  • Scalability hotspots are more likely to exist in the tiers that are shared across multiple client sessions.
  • Performance measurements should be from presentation start to presentation completion, i.e. user clicks button (start) and information is displayed (completion).
  • Use load-test suites and frameworks to perform repeatable load testing.
J2EE Application server performance (Page last updated April 2001, Added 2001-04-20, Author Misha Davidson, Publisher Java Developers Journal). Tips:

  • Good performance has sub-second latency (response time) and hundreds of (e-commerce) transactions per second.
  • Avoid using the SingleThreadModel interface for servlets: write thread-safe code instead.
  • ServletRequest.getRemoteHost() is very inefficient, and can take seconds to complete the reverse DNS lookup it performs.
  • OutputStream can be faster than PrintWriter. JSPs are only generally slower than servlets when returning binary data, since JSPs always use a PrintWriter, whereas servlets can take advantage of a faster OutputStream.
  • Excessive use of custom tags may create unnecessary processing overhead.
  • Using multiple levels of BodyTags combined with iteration will likely slow down the processing of the page significantly.
  • For read-only queries involving large amounts of data, avoid EJB objects and use JavaBeans as an intermediary to access manipulate and store the data for JSP access.
  • Use stateless session EJBs to cache and manage infrequently changed data. Update the EJB occasionally.
  • Use a dedicated session bean to perform and cache all JNDI lookups in a minimum number of requests.
Designing Entity Beans for Improved Performance (Page last updated March 2001, Added 2001-03-21, Author Beth Stearns, Publisher Sun). Tips:

  • Remember that every call of an entity bean method is potentially a remote call.
  • Designing with one access method per data attribute should only be used where remote access will not occur, i.e. entities are guaranteed to be in the same container.
  • Use a value object which encapsulates all of an entity's data attributes, and which transfers all the data in one network transfer. This may result in large objects being transferred though.
  • Group entity bean data attributes in subsets, and use multiple value objects to provide remote access to those subsets. and
Performance tuning report in German. Thanks to Peter Kofler for extracting the tips. (Page last updated November 2001, Added 2001-07-20, Author Sebastian Ritter, Publisher Ritter). Tips:

  • use externalization instead of serialisation,,11952_1370691,00.html
Common issues affecting Web performance (Page last updated June 2002, Added 2002-07-24, Author Drew Robb, Publisher EarthWeb). Tips:

  • Symptoms of network problems include slow response times, excessive database table scans, database deadlocks, pages not available, memory leaks and high CPU usage.
  • Causes of performance problems can include the application design, incorrect database tuning, internal and external network bottlenecks, undersized or non-performing hardware or Web and application server configuration errors.
  • Root causes of performance problems come equally from four main areas: databases, Web servers, application servers and the network, with each area typically causing about a quarter of the problems.
  • The most common database problems are insufficient indexing, fragmented databases, out-of-date statistics and faulty application design. Solutions include tuning the index, compacting the database, updating the database and rewriting the application so that the database server controls the query process.
  • The most common network problems are undersized, misconfigured or incompatible routers, switches, firewalls and load balancers, and inadequate bandwidth somewhere along he communication route.
  • The most common application server problems are poor cache management, unoptimized database queries, incorrect software configuration and poor concurrent handling of client requests.
  • The most common web server problems are poor design algorithms, incorrect configurations, poorly written code, memory problems and overloaded CPUs.
  • Having a testing environment that mirrors the expected real-world environment is very important in achieving good performance.
  • The deployed system needs to be tested and continually monitored.
Article on using smart proxies. (Page last updated November 2000, Added 2001-01-19, Author M. Jeff Wilson, Publisher JavaWorld). Tips:

  • Use smart proxies to monitor the performance of RMI calls.
Paper detailing the "Best Practices for Developing High Performance Web and Enterprise Applications" using IBM's WebSphere. All the tips are generally applicable to servlet/EJB development, as well as other types of server development. (Page last updated September 2000, Added 2001-01-19, Author Harvey W. Gunther, Publisher IBM). Tips:

  • Do not store large object graphs in javax.servlet.http.HttpSession. Servlets may need to serialize and deserialize HttpSession objects for persistent sessions, and making them large produces a large serialization overhead.
  • Use the tag "<%@ page session="false"%>" to avoid creating HttpSessions in JSPs.
  • Minimize synchronization in Servlets to avoid multiple execution threads becoming effectively single-threaded.
  • Do not use javax.servlet.SingleThreadModel.
  • Use the HttpServlet Init method to perform expensive operations that need only be done once.
  • Minimize use of System.out.println.
  • Access entity beans from session beans, not from client or servlet code.
  • Reuse EJB homes.
  • Use Read-Only methods where appropriate in entity-beans to avoid unnecessary invocations to store.
  • The EJB "remote programming" model always assumes EJB calls are remote, even where this is not so. Where calls are actually local to the same JVM, try to use calling mechanisms that avoid the remote call.
  • Remove stateful session beans (and any other unneeded objects) when finished with, to avoid extra overheads in case the container needs to be passivated.
  • Beans.instantiate() incurs a filesystem check to create new bean instances. Use "new" to avoid this overhead.
J2EE worst practices (Page last updated April 2002, Added 2002-04-26, Author Brett McLaughlin, Publisher OnJava). Tips:

  • Directory servers are optimized for frequent reads, with few writes. If you frequently add data to a directory server, performance degrades.
  • Stateless session beans are soooo much faster.
Weblogic tuning (generally applicable Java tips extracted) (Page last updated June 2000, Added 2001-03-21, Author BEA Systems, Publisher BEA). Tips:

  • Try to avoid excessive cycling (creation/deletion or activation/passivation) of beans.
J2EE Performance tuning (Page last updated October 2001, Added 2001-10-22, Author James McGovern, Publisher Java Developers Journal). Tips:

  • Call HttpSession.invalidate() to clean up a session when you no longer need to use it.
  • For Web pages that don't require session tracking, save resources by turning off automatic session creation using: <%@ page session="false"%>
  • Implement the HttpSessionBindingListener for all beans that are scoped as session interface and explicitly release resources implementing the method valueUnbound().
  • Timeout sessions more quickly by setting the timeout or using session.setMaxInactiveInterval().
  • Keep-Alive may be extra overhead for dynamic sites.
  • Use the include directive <%@ include file="copyleft.html" %> where possible, as this is a compile-time directive (include action <jsp:include page="copyleft.jsp" /> is a runtime directive).
  • Use cache tagging where possible.
  • Always access entity beans from session beans.
  • If only using an entity bean for data access, use JDBC directly instead.
  • Use read-only in the deployment descriptor.
  • Cache access to EJB homes.
  • Use local entity beans when beans are co-located in the same JVM.
  • Proprietary stubs can be used for caching and batching data.
  • Use a dedicated remote object to generate unique primary keys.
Overview of common application servers. (Announced at I've extracted the performance related features (Page last updated October 2001, Added 2001-10-22, Author Pieter Van Gorp, Publisher Van Gorp). Tips:

  • Caching. JNDI caching. Distributed caching with synchronization.
  • Optimized subsystems (RMI, JMS, JDBC drivers, JSP tags & cacheable page fragments).
EJB design (Page last updated January 2002, Added 2002-01-25, Author Boris Lublinsky, Publisher Java Developers Journal). Tips:

  • Some application server implementations (e.g., WebSphere) automatically convert remote communications to local communications to make them faster.
  • Low granularity (i.e. fine-grained) methods in an EJB typically leads to poor performance of the overall system.
  • Local interfaces in EJB 2.0 is one attempt to improve overall performance: local interfaces provide for beans in the same container to interact locally without involving RMI.
  • The most effective way to improve the overall performance of EJB-based applications is to minimize the amount of method invocations, making the communications overhead negligible compared with the execution time. This can be achieved by implementing coarse-grained methods.
  • Entity beans should not be simply mapped to database tables. Treating entity beans as such fine-grained objects which are effectively wrappers on table rows leads to increased network communications and heavier database communications than if entity beans are treated as coarse-grained components.
  • For optimal performance, entity beans should be designed to: have large granularity, which usually means they should contain multiple Java classes and support multiple database tables; be associated with a certain amount of persistent data, typically multiple database tables, one of which should define the primary key for the whole bean; support meaningful business methods and encapsulate business rules to access the data.
  • Don't use client transactions in the EJB environment since long-running transactions that can cause database lockup.
  • Entity beans are transactional resources due to their stateful nature, but application server vendors often rely on the underlying database to lock and resolve access appropriately. Although this approach greatly improves performance, it provides the potential for database lockup.
Command objects for RMI. (Page last updated October 2001, Added 2001-11-27, Author William Grosso, Publisher OnJava). Tips:

  • Use Command objects to automatically queue or retry RMI calls.
Caching RMI stubs. (Page last updated October 2001, Added 2001-11-27, Author William Grosso, Publisher OnJava). Tips:

  • Remote method calls are much slower than local calls, at least 1000 times slower.
  • Reduce the number of remote calls made by an application to improve performance.
  • Cache remote objects locally where possible, rather than repeatedly fetching them.
  • Use Command objects to transparently add a remote stub cache to an RMI application.
  • Caching stubs keeps them from being garbage collected, and may prevent an RMI server from closing. Use a policy to expire stubs and delete them from the cache.
Website performance. (Page last updated October 2001, Added 2001-11-27, Author Gordon Benett, Publisher Intranet Journal). Tips:

  • Some e-commerce consultants cite an attention span on the order of eight seconds as the threshold for abandoning a slow retail site.
  • Where broadband connections are the norm, pages that don't appear instantly stand a good chance of never being seen: slow pages might as well be no pages.
  • Systems can only be designed to meet performance goals if those goals have been identified. Determine what range of response times will be acceptable.
  • Try to understand the performance impacts of your design decisions. However the performance of some design choices can be hard to predict and may remain unclear before testing.
  • Test the system under conditions that simulate real patterns of use.
  • Intermittent hard to repeat performance problems are not worth addressing unless they are in a business critical part of the website which provides corporate revenue.
  • Use a rapid, iterative development process in combination with frequent performance testing.
  • Try to plan up-front rather than have to rely on late-phase tuning.
Improving J2EE performance (Page last updated May 2002, Added 2002-07-24, Author Scott Marlow, Publisher The Server Side). Tips:

  • Set performance goals before development starts.
  • If supporting clients with slow connections, consider compressing data for network communication.
  • Minimize the number of network round trips required by the application.
  • For applications to scale to many users, minimize the amount of shared memory that requires updating.
  • Cache data to minimize lookup time, though this can reduce scalability if locks are required to access the cache.
  • If there are more accesses than updates to a cache, share the access lock amongst all the accessors, though be aware that this reduces the window for updators to lock the cache.
  • For optimum performance, zero shared memory provides a cache per user.
  • Be methodical to ensure that changes for performance do actually improve performance.
  • Eliminate memory leaks before tuning execution speed.
  • Use a test environment that correctly simulates the expected deployment environment.
  • Simulate the expected client activity, and compare the performance against your expected goals.
  • Consider which metrics to measure, such as: Max response time under heavy load; CPU utilization under heavy load; How the application scales as additional users are added.
  • Profile the application to find the bottlenecks. Correct bottlenecks by making one change at a time and testing for improvement.
  • Generate stack traces to look for bottlenecks which are multi-thread conflicts (waiting for locks).
  • Improving the performance of a method that is called 1000 times and takes a tenth of a second on average each call, is better than improving the performance of a method that is only called 10 times but takes 1 second each call.
  • Don?t cache data unless you know how and when to invalidate the cached entries.
Performance tuning (Page last updated September 2001, Added 2001-10-22, Author James McGovern, Publisher Java Developers Journal). Tips:

  • Avoid stateful sessions.
Why CMP is better than BMP (Page last updated April 2002, Added 2002-04-26, Author Tyler Jewell, Publisher Weblogic Developers Journal). Tips:

  • Use CMP except in specific cases when BMP is necessary: fields use stored procedures; persistence is not simple JDBC (e.g. JDO); One bean maps to multiple tables; non-standard SQL is used.
  • CMP can make many optimizations: optimal locking; optimistic transactions; efficient lazy loading; efficiently combining multiple queries to the same table (i.e. multiple beans of the same type can be handled together); optimized multi-row deletion to handle deletion of beans and their dependents.
Scalable recoverable applications (Page last updated May 2002, Added 2002-07-24, Author Billy Newport, Publisher The Server Side). Tips:

  • A database caching layer in the servlet helps performance. An EJB caching layer is difficult to achieve.
  • A load balancing message queue may be needed for a high rate of messages (>500/sec).
Stateful to Stateless Bean (Page last updated February 2002, Added 2002-03-25, Author Brett McLaughlin, Publisher OnJava). Tips:

  • Stateless session beans are much more efficient than stateful session beans.
  • Stateless session bean have no state. Most containers have pools of stateless beans. Each stateless bean instance can serve multiplw clients, so the bean pool can be kept small, and doesn't need to change in size avoiding the main pooling overheads.
  • A separate stateful bean instance must exist for every client, making bean pools larger and more variable in size.
  • [Article discusses how to move a stateful bean implementation to stateless bean implementtaion].
J2EE design patterns to improve performance (Page last updated June 2001, Added 2001-06-18, Author Daniel H. Steinberg, Publisher JavaWorld). Tips:

  • Combine multiple remote calls for state information into one call using a value object to wrap the data (the Value Object pattern, superceded by local interfaces in EJB 2.0).
Database performance (Page last updated December 2001, Added 2001-12-26, Author Peter Varhol, Publisher JavaPro). Tips:

  • Thoughtful page design makes for a better user experience by enabling the application to seem faster than it really is.
EJB performance tips (Page last updated November 2001, Added 2001-12-26, Authors Ravi Kalidindi and Rohini Datla, Publisher PreciseJava). Tips:

  • EJB calls are expensive. A method call from the client could cover all the following: get Home reference from the NamingService (one network round trip); get EJB reference (one or two network roundtrips plus remote creation and initialization of Home and EJB objects); call method and return value on EJB object (two or more network rountrips: client-server and [mutliple] server-db; several costly services used such as transactions, persistence, security, etc; multiple serializations and deserializations).
  • If you don't need EJB services for an object, use a plain Java object and not an EJB object.
  • Use Local interfaces (from EJB2.0) if you deploy both EJB Client and EJB in the same JVM. (For EJB1.1 based applications, some vendors provide pass-by-reference EJB implementations that work like Local interfaces).
  • Wrap multiple entity beans in a session bean to change multiple EJB remote calls into one session bean remote call and several local calls (pattern called SessionFacade).
  • Change multiple remote method calls into one remote method call with all the data combined into a parameter object.
  • Control serialization by modifying unnecessary data variables with 'transient' key word to avoid unnecessary data transfer over network.
  • Cache EJBHome references to avoid JNDI lookup overhead (pattern called ServiceLocator).
  • Declare non-transactional methods of session beans with 'NotSupported' or 'Never' transaction attributes (in the ejb-jar.xml deployment descriptor file).
  • Transactions should span the minimum time possible as transactions lock database rows.
  • Set the transaction time-out (in the ejb-jar.xml deployment descriptor file).
  • Use clustering for scalability.
  • Tune the EJB Server thread count.
  • Use the HttpSession object rather than a Stateful session bean to maintain client state.
  • Use the ECperf benchmark to help differentiate EJB server performances.
  • Tune the Stateless session beans pool size to minimize the creation and destruction of beans.
  • Use the setSessionContext() or ejbCreate() method to cache bean specific resources. Release acquired resources in the ejbRemove() method.
  • Tune the Stateful session beans cache size to and time-out minimize activations and passivations.
  • Allow stateful session beans to be removed from the container cache by explicitly using the remove() method in the client.
  • Tune the entity beans pool size to minimize the creation and destruction of beans.
  • Tune the entity beans cache size to minimize the activation and passivation of beans (and associated database calls).
  • Use the setEntityContext() method to cache bean specific resources and release them from the unSetEntityContext() method.
  • Use Lazy loading to avoid unnecessary pre-loading of child data.
  • Choose the lowest cost transaction isolation level that avoids corrupting the data. Transaction levels in increasing cost are: TRANSACTION_READ_UNCOMMITED, TRANSACTION_READ_COMMITED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE.
  • Use the lowest cost locking available from the database that is consistent with any transaction.
  • Create read-only entity beans for read only operations.
  • Use a dirty flag where supported by the EJB server to avoid writing unchanged EJBs to the database.
  • Commit the data after the transaction completes rather than after each method call (where supported by EJB server).
  • Do bulk updates to reduce database calls.
  • Use CMP rather than BMP to utilize built-in performance optimization facilities of CMP.
  • Use ejbHome() methods for global operations (from EJB2.0).
  • Tune the connection pool size to minimize the creation and destruction of database connections.
  • Use JDBC directly rather than using entity beans when dealing with large amounts of data such as searching a large database.
  • Combine business logic with the entity bean that holds the data needed for that logic to process.
  • Tune the Message driven beans pool size to optimize the concurrent processing of messages.
  • Use the setMesssageDrivenContext() or ejbCreate() method to cache bean specific resources, and release those resources from the ejbRemove() method.
Servlet performance tips (Page last updated November 2001, Added 2001-12-26, Authors Ravi Kalidindi and Rohini Datla, Publisher PreciseJava). Tips:

  • Use the servlet init() method to cache static data, and release them in the destroy() method.
  • Use StringBuffer rather than using + operator when you concatenate multiple strings.
  • Use the print() method rather than the println() method.
  • Use a ServletOutputStream rather than a PrintWriter to send binary data.
  • Initialize the PrintWriter with the optimal size for pages you write.
  • Flush the data in sections so that the user can see partial pages more quickly.
  • Minimize the synchronized block in the service method.
  • Implement the getLastModified() method to use the browser cache and the server cache.
  • Use the application server's caching facility.
  • Session mechanisms from fastest to slowest are: HttpSession, Hidden fields, Cookies, URL rewriting, the persistency mechanism.
  • Remove HttpSession objects explicitly in your program whenever you finish the session.
  • Set the session time-out value as low as possible.
  • Use transient variables to reduce serialization overheads.
  • Disable the servlet auto reloading feature.
  • Tune the thread pool size.
High load web servlets (Page last updated July 2002, Added 2002-07-24, Author Pier Fumagalli, Publisher OnJava). Tips:

  • Hand off requests for static resources directly to the web server by specifying the URL, not by redirecting from the servlet.
  • Use separate webservers to deliver static and dynamic content.
  • Cache as much as possible. Make sure you know exactly how much RAM you can spare for caches, and have the right tools for measuring memory.
  • Load balance the Java application using multiple JVMs.
  • Use ulimit to monitor the number of file descriptors available to the processes. Make sure this is high enough.
  • Logging is more important than the performance saved by not logging.
  • Monitor resources and prepare for spikes.
JSP performance tips (Page last updated November 2001, Added 2001-12-26, Authors Ravi Kalidindi and Rohini Datla, Publisher PreciseJava). Tips:

  • Use the jspInit() method to cache static data, and release them in the jspDestroy() method.
  • Use the jspInit() method to cache static data.
  • Use StringBuffer rather than using + operator when you concatenate multiple strings.
  • Use the print() method rather than the println() method.
  • Use a ServletOutputStream rather than a PrintWriter to send binary data.
  • Initialize the PrintWriter with the optimal size for pages you write.
  • Flush the data in sections so that the user can see partial pages more quickly.
  • Minimize the synchronized block in the service method.
  • Avoid creating a session object with the directive <%@ page session="false" %>
  • Increase the buffer size of System.out with the directive <%@ page buffer="12kb" %>
  • Use the include directive instead of the include action when you want to include another page.
  • Minimize the scope of the 'useBean' action.
  • Custom tags incur a performance overhead. Use as few as possible.
  • Use the application server's caching facility, and the session and application objects (using getAttribute()/setAttribute()). There are also third-party caching tags available.
  • Session mechanisms from fastest to slowest are: session, Hidden fields, Cookies, URL rewriting, the persistency mechanism.
  • Remove 'session' objects explicitly in your program whenever you finish the session.
  • Reduce the session time-out as low as possible.
  • Use 'transient' variables to reduce serialization overheads.
  • Disable the JSP auto reloading feature.
  • Tune the thread pool size.
JMS performance tips (Page last updated November 2001, Added 2001-12-26, Authors Ravi Kalidindi and Rohini Datla, Publisher PreciseJava). Tips:

  • Start the consumer before you start the producer so that the initial messages do not need to queue.
  • Use a ConnectionConsumer to process messages concurrently with a ServerSessionPool.
  • Close resources (e.g. connections, session objects, producers, consumers) when finished with them.
  • Use separate transactional sessions and non-transactional sessions for transactional and non-transactional messages.
  • Tune the Destination parameters: a smaller capacity increases message throughput; a higher redelivery delay and lower redelivery limit reduces the overhead.
  • Choose non-durable (NON_PERSISTENT) messages wherever appropriate to avoid the persistency overhead.
  • Set the TimeToLive value as low as feasible (default is for messages to never expire).
  • Receive messages asynchronously with a MessageListener implementation.
  • Choose the message type that minimizes memory overheads.
  • Use 'transient' variables to reduce serialization overheads.
Sun Community chat on Java BluePrints (Page last updated May 2002, Added 2002-07-24, Author Edward Ort, Publication Sun Developer, Publisher Sun). Tips:

  • For very large transactions, use transaction attribute TX_REQUIRED for EJB methods to have all the method calls in a call chain use the same transaction.
  • Make tightly coupled components local to each other. Put remote beans primarily as facades across subsystems.
  • The page-by-page pattern is designed to handle cases where the result set is large, and the end-user is not interested in seeing all of the results. There is really no upper threshold for the size of result set in the pattern.
Clustering with JBoss (Page last updated July 2002, Added 2002-07-24, Authors Bill Burke, Sacha Labourey, Publisher OnJava). Tips:

  • A hardware- or software-based HTTP load-balancer usually sits in front of the application servers within a cluster. The load balancer can decrypt HTTPS requests and distribute load.
  • HTTP session replication is expensive for a J2EE application server. If you can live with forcing a user to log in again after a server failure, then an HTTP load-balancer probably provides all of the fail-over and load-balancing functionality you need.
  • If you are storing things other than EJB Home references in your JNDI tree, then you may need clustered JNDI.
  • 24/7 availability needs the ability to hot-deploy and undeploy new applications and new versions, and to apply patches, without bringing down the application server for maintenance.
  • Smart proxies can be used to implement load-balancing and fail-over for EJB remote clients. These proxies manage a list of available RMI connections one of which it will use to service an invocation.
Speeding web page downloads using compression (Page last updated July 2002, Added 2002-07-24, Author Steven Chau, Publication, Publisher Tips:

  • Browsers sending "Accept-Encoding: gzip" will accept gziped compressed pages. Return the page compressed with "Content-Encoding: gzip" using GZIPOutputStream.
  • Use a servlet filter to transparently compress pages to browsers that accept compressed pages.
Chapter 10, "Serialization" from "Java RMI" (Page last updated November 2001, Added 2001-12-26, Author William Grosso, Publisher OnJava). Tips:

  • Use transient to avoid sending data that doesn't need to be serialized.
  • Serialization is a generic marshalling mechanism, and generic mechanisms tend to be slow.
  • Serialization uses reflection extensively, and this also makes it slow.
  • Serialization tends to generate many bytes even for small amounts of data.
  • The Externalizable interface is provided to solve Serialization's performance problems.
  • Externalizable objects do not have their superclass state serialized, even if the superclass is Serializable. This can be used to reduce the data written out during serialization.
  • Use Serializable by default, then make classes Externalizable on a case-by-case basis to improve performance.
Web application scalability. (Page last updated June 2000, Added 2001-05-21, Author Billie Shea, Publisher STQE Magazine). Tips:

  • Web application scalability is the ability to sustain the required number of simultaneous users and/or transactions, while maintaining adequate response times to end users.
  • The first solution built with new skills and new technologies will always have room for improvement.
  • Avoid deploying an application server that will cause embarrassment, or that could weaken customer confidence and business reputation [because of bad response times or lack of calability].
  • Consider application performance throughout each phase of development and into production.
  • Performance testing must be an integral part of designing, building, and maintaining Web applications.
  • There appears to be a strong correlation between the use of performance testing tools and the likelihood that a site would scale as required.
  • Automated performance tests must be planned for and iteratively implemented to identify and remove bottlenecks.
  • Validate the architecture: decide on the maximum scaling requirements and then performance test to validate the necessary performance is achievable. This testing should be done on the prototype, before the application is built.
  • Have a clear understanding of how easily your configurations of Web, application, and/or database servers can be expanded.
  • Factor in load-balancing software and/or hardware in order to efficiently route requests to the least busy resource.
  • Consider the effects security will have on performance: adding a security layer to transactions will impact response times. Dedicate specific server(s) to handle secure transactions.
  • Select performance benchmarks and use them to quantify the scalability and determine performance targets and future performance improvements or degradations. Include all user types such as "information-gathering" visitors or "transaction" visitors in your benchmarks.
  • Perform "Performance Regression Testing": continuously re-test and measure against the established benchmark tests to ensure that application performance hasn?t been degraded because of the changes you?ve made.
  • Performance testing must continue even after the application is deployed. For applications expected to perform 24/7 inconsequential issues like database logging can degrade performance. Continuous monitoring is key to spotting even the slightest abnormality: set performance capacity thresholds and monitor them.
  • When application transaction volumes reach 40% of maximum expected volumes, it is time to start executing plans to expand the system
Web Load Test Planning (Page last updated April 2001, Added 2001-05-21, Author Alberto Savoia, Publisher STQE Magazine). Tips:

  • The only reliable way to determine a system?s scalability is to perform a load test in which the volume and characteristics of the anticipated traffic are simulated as realistically as possible.
  • It is hard to design and develop load tests that come close to matching real loads.
  • Characterize the anticipated load as objectively and systematically as possible: use existing log files where possible; characterize user sessions (pages viewed - number and types; duration of session; etc). Determine the range and distribution of variations in sessions. Don't use averages, use representative profiles.
  • Estimate target load and peak levels: estimate overall and peak loads for the server and expected growth rates.
  • Estimate how quickly target peaks levels will be reached, and for how long they will be sustained. The duration of the peak is important and the server must be designed to handle it.
  • The key elements of a load test design are: test objective (e.g. can the server handle N sessions/hr peak load level?); pass/fail criteria (e.g. pass if response times stay within define values); script description (e.g. user1: page1, page2, ...; user2: page1, page3, start transaction1, etc); scenario description (which scripts at which frequency, and how load increases).
Performance chapter (chapter 20) from "Professional JSP 2nd Edition" (Page last updated August 2001, Added 2001-10-22, Author Simon Brown, Robert Burdick, Darko Cokor, Jayson Falkner, Ben Galbraith, RodJohnson, Larry Kim, Casey Kochmer, Thor Kristmundsson, Sing Li, Dan Malks, Mark Nelson, Grant Palmer, Bob Sullivan, Geoff Taylor, John Timney, Sameer Tyagi, Geert Van Damme, Steve Wilkinson, Publisher The Server Side). Tips:

  • The user's view of the response time for a page view in his browser depends on download speed and on the complexity of the page. e.g. the number of graphics. A poorly-designed highly graphical dynamic website could be seen as 'slow' even if the web downloads are individually quite fast.
  • No web application can handle an unlimited number of requests; the trick in optimization is to anticipate the likely user demand and ensure that the web site can gracefully scale up to the demand while maintaining acceptable levels of speed.
  • Profile the server to identify the bottlenecks. Note that profiling can be done by instrumenting the code with measurement calls if a profiler is unavailable.
  • One stress test methodology is: determine the maximum acceptable response time for getting a page; estimate the maximum number of simultaneous users; simulate user requests, gradually adding simulated users until the web application response delay becomes greater than the acceptable response time; optimize until you reach the desired number of users.
  • Pay special attention to refused connections during your stress test: these indicate the servlet is overwhelmed.
  • There is little performance penalty to using an MVC architecture.
  • Use resource pools for expensive resources (like database connections).
  • Static pages are much faster than dynamic pages, where the web server handles static pages separately.
  • Servlet filtering has a performance cost. Test to see if it is an acceptable cost.
  • Ensure that the webserver is configured to handle the expected number of user for example: enough ready sockets; enough disk space; enough CPU.
  • Use the fastest JVM you have access to.
Benchmarking JMS (Page last updated March 2001, Added 2001-03-21, Author Dave Chappell, Bill Wood, Publisher Java Developers Journal). Tips:

  • Scaling middleware exposes a number of issues such as threading contention, network bottlenecks, message persistence issues, memory leaks, and overuse of object allocations.
  • [Article dicusses questions to ask when setting up benchmarks for messaging middleware].
  • Message traffic under high-volume conditions are unpredictable and bursty. Messages can be produced far faster than they can be consumed, causing congestion. This condition requires the message sends to be throttled with flow control (could be an exception, or an automatic resend).
  • When testing performance, run overnight and over weekends to generate longer term trends. Some concerns are: testing without a real network connection can give false measures; low user simulation can be markedly different from high user simulations; network throughput may be large than the deployed environment; nonpersistent message performance is dependent on processor and memory; disk speed is crucial for persistent messages.
  • [Article provides a benchmark harness for testing JMS].
Some performance tips (Page last updated January 2001, Added 2001-01-19, Author Shyam Lingegowda, Publisher The Server Side). Tips:

  • Factor out constant computations from loops. For Servlets, push one time computations into the init() method.
Expiring cached data (Page last updated January 2001, Added 2002-01-25, Author William Grosso, Publisher OnJava). Tips:

  • Servlet sessions expire after a settable timeout, but screens that automatically refresh can keep a session alive indefinitely, even when the screen is no longer in use.
Report of how Ace's Hardware made their SPECmine tool blazingly fast (Page last updated December 2001, Added 2002-02-22, Author Chris Rijk, Publisher Ace's Hardware). Tips:

  • Tranform your data to minimize the costs of searching it.
  • If your dataset is small enough, read it all into memory or use an in-memory database (keeping the primary copy on disk for recovery).
  • An in-memory datavase avoids the following overheads: no need to pass data in from a separate process; less memory allocation by avoiding all the data copies as it's passed between processes and layers; no need for data conversion; fine-tuned sorting and filtering possible; other optimizations become simpler.
  • Pre-calculation makes some results faster by making the database data more efficient to access (by ordering it in advance for example), or by setting up extra data in advance, generated from the main data, to make calculating the results for a query simpler.
  • Pre-determine possible data values in queries, and use boolean arrays to access the chosen values.
  • Pre-calculate all formatting that is invariant for generated HTML pages. Cache all reused HTML fragments.
  • Caching many strings may consume too much memory. IF memory is limited, it may be more effective to generate strings as needed.
  • Write out strings individually, rather than concatenating them and writing the result.
  • Extract common strings into an identical string object.
  • Compress generated html pages to send to the user, if their browser supports compressed html. This is a heavier load on the server, but produces a significantly faster transfer for limited bandwidth clients.
  • Some pages are temporarily static. Cache these pages, and only re-generate them when they change.
  • Caching can significantly improve the responsiveness of a website.
Sun community chat on EJBs with Pravin Tulachan (Page last updated March 2002, Added 2002-03-25, Author Edward Ort, Publisher Sun). Tips:

  • CMP (container managed persistence) is generally faster than BMP (bean managed persistence).
  • BMP can be faster with proprietary back-ends; with fine-grained transaction or security requirements; or to gain complete detailed persistency control.
  • Scalability is improved by passing primary keys rather than passing the entities across the network.
  • EJB 2.0 CMP is far faster than EJB 1.1 CMP. EJB 1.1 CMP was not necessarily capable of scaling to high transaction volumes.
  • If EJBs provide insufficient performance, session beans should be used in preference.
  • Don't make fine-grained method calls across the network. Use value object and session facade design patterns instead.
J2EE best practices. (Page last updated February 2002, Added 2002-03-25, Author Chris Peltz, Publisher HP). Tips:

  • Executing a search against the database calls one of the finder() methods. finder() methods must return a collection of remote interfaces, not ValueObjects. Consequently the client would need to make a separate remote call for each remote interface received, to acquire data. The SessionFacade pattern suggests using a session bean to encapsulate the query and return a collection of ValueObjects, thus making the request a single transfer each way.
  • The Value Object Assembler pattern uses a Session EJB to aggregate all required data as various types of ValueObjects. This pattern is used to satisfy one or more queries a client might need to execute in order to display multiple data types.
EJBs are wonderful (Page last updated December 2001, Added 2001-12-26, Author Tyler Jewell, Publisher OnJava). Tips:

  • The out-of-the-box configuration for Entity EJB engines, such as WebLogic, are designed to handle read-write transactional data with the best possible performance.
  • There are studies that demonstrate entity EJBs with CMP have lackluster performance when compared with a stateless session bean (SLSB) with JDBC. [Author points out however that SLSB/JDBC combination is less robust, less configurable, and less maintainable].
  • Configure separate deployments for each entity bean for different usage patterns (e.g. typical 85% read-only, 10% read-write, 5% batch update), and partition the presentation layer to use the appropriate corresponding deployment (e.g. read requests use the read-only deployment).
EJB performance tips (Page last updated December 2001, Added 2001-12-26, Author Krishna Kothapalli and Raghava Kothapalli, Publisher JavaPro). Tips:

  • Design coarse-grained EJB remote interfaces to reduce the number of network calls required.
  • Combine remote method calls into one call, and combine the data required for the calls into one transfer.
  • Reduce the number of JNDI lookups: cache the home handles.
  • Use session bean wrapper for returning multiple data rows from an entity bean, rather than returning one row at a time.
  • Use session beans for database batch operations, entity beans typically operate only one row at a time.
  • Use container-managed persistence (CMP) rather than bean-managed persistence (BMP).
  • Use entity beans when only a few rows are required for the entity, and when rows need to be frequently updated.
  • Use the lowest impact isolation (transaction) level consistent with maintaining data coherency. Highest impact down: TRANSACTION_SERIALIZABLE, TRANSACTION_REPEATABLE_READ, TRANSACTION_READ_COMMITED, TRANSACTION_READ_UNCOMMITED.
  • Correctly simulate the production environment to tune the application, and use profiling and other monitroing tools to identify bottlenecks.
  • Tune the underlying system, e.g. TCP/IP parameters, file limits, connection pool parameters, EJB pools sizes, thread counts, number of JVMs, JVM heap size, shared pool sizes, buffer sizes, indexes, SQL queries, keep/alive parameters, connection backlogs.
  • Use clustering to meet higher loads or consider upgrading the hardware.
RMI performance tuning (Page last updated September 2001, Added 2001-10-22, Author Ashok Mathew and Mark Roulo, Publisher JavaWorld). Tips:

  • Use netperf to measure network bandwidth.
  • Consider altering the TcpWindowSize parameter.
  • Configure RMI garbage collection by setting the properties sun.rmi.dgc.client.gcInterval and sun.rmi.dgc.server.gcInterval.
  • Send groups of objects together rather than one object at a time.
  • Implementing Externalize can speed up transfers.
  • Pack data to reduce the number and amount of reads and writes, and the amount of data transferred.
  • Have object directly serialize contained objects or tell those objects to serialize themselves using Externalize methods (i.e. chain Externalize methods for all contained objects).
  • Use special codes to handle special cases such as singleton or reusable objects.
  • Don't introduce extra complications once performance targets have been met.
Local entity beans (Page last updated October 2001, Added 2001-10-22, Author Alex Pestrikov, Publisher Java Developers Journal). Tips:

  • Local entity beans do not need to be marshalled, and do not incur any marshalling overhead for method calls either: parameters are passed by reference.
  • Local entity beans are an optimization for beans which it is known will be on the same JVM with their callers.
  • Facade objects (wrappers) allow local entity beans to be called remotely. This pattern incurs very little overhead for remote calls, while at the same time optimizing local calls between local beans which can use local calls.
Chapter 4 of "Enterprise Java Performance", "Local/Remote Issues". (Page last updated 2000, Added 2000-10-23, Authors Steven Halter & Steven Munroe, Publisher Sun). Tips:

  • RMI over IIOP has a higher overhead than plain RMI.
  • Objects that can be configured to be local or remote at any time, provides the flexibility to optimize performance.
  • Large grained remote calls [i.e. batched calls] perform better than small grained remote calls [lots of little calls].
  • Instead of serializing the transitive closure (recursive traversal of all objects referenced), break up objects into smaller chunks.
  • Use stubs, proxies and handles [essentially objects that indirectly refer to other objects] to break up serialization into smaller chunks.
  • Unless the application is put together with care, the remote method call costs may dominate.
  • Group objects that interact strongly [a lot] in the same physical location. The closer they are, the more efficient their interaction.
  • Cache in the client any read-only objects, for the whole session. Replicate any data needed so that queries run locally in the client.
  • Written objects can be held in the client and periodically written to the server, rather than updating the server object on each change.
  • Good partitioning of objects in distributed applications limits interactions between objects in different partitions and takes advantage of local method access for objects within each partition.
  • Application partitioning is best addressed early in the design.
How to use java.rmi.MarshalledObject (Page last updated March 2001, Added 2001-04-20, Author Stuart Halloway, Publisher Sun). Tips:

  • MarshalledObject lets you postpone deserializing objects. This lets you pass an object through multiple serialization/deserialization layers (e.g. passing an object through many JVMs), without incurring the serialization/deserialization overheads until absolutely necessary.
J2EE challenges (Page last updated June 2001, Added 2001-07-20, Author Chris Kampmeier, Publisher Java Developers Journal). Tips:

  • Thoroughly test any framework in a production-like environment to ensure that stability and performance requirements are met.
  • Each component should be thoroughly reviewed and tested for its performance and security characteristics.
  • Using the underlying EJB container to manage complex aspects such as transactions, security, and remote communication comes with the price of additional processing overhead.
  • To ensure good performance use experienced J2EE builders and use proven design patterns.
  • Consider the impact of session size on performance.
  • Avoid the following common mistakes: Failure to close JDBC result sets, statements, and connections; Failure to remove unused stateful session beans; Failure to invalidate HttpSession.
  • Performance requirements include: the required response times for end users; the perceived steady state and peak user loads; the average and peak amount of data transferred per Web request; the expected growth in user load over the next 12 months.
  • Note that peak user loads are the number of concurrent sessions being managed by the application server, not the number of possible users using the system.
  • Applications that perform very little work can typically handle many users for a given amount of hardware, but can scale poorly as they spend a large percentage of time waiting for shared resources.
  • Applications that perform a great number of computations tend to require much more hardware per user, but can scale much better than those performing a small number of computations.
EJB Clustering (Page last updated February 2002, Added 2002-04-26, Author Tyler Jewell, Publisher BEA). Tips:

  • Four locations that can provide clustering logic for an EJB are: the JNDI naming server where the home stub is bound, the container, the home stub, and the remote stub.
J2EE Application servers (Page last updated April 2001, Added 2001-04-20, Authors Christopher G. Chelliah and Sudhakar Ramakrishnan, Publisher Java Developers Journal). Tips:

  • A scalable server application probably needs to be balanced across multiple JVMs (possibly pseudo-JVMs, i.e. multiple logical JVMs running in the same process).
  • Performance of an application server hinges on caching, load balancing, fault tolerance, and clustering.
  • Application server caching should include web-page caches and data access caches. Other caches include caching servers which "guard" the application server, intercepting requests and either returning those that do not need to go to the server, or rejecting or delaying those that may overload the app server.
  • Application servers should use connection pooling and database caching to minimize connection overheads and round-trips.
  • Using one thread per user can become a bottleneck if there are a large number of concurrent users.
Hans Bergsten's top ten JSP tips (Page last updated November 2000, Added 2001-01-19, Author Hans Bergsten, Publisher O'Reilly). Tips:

  • The include directive (<%@ include file="" %>) is faster than the include action (<jsp:include page="pagename.jsp" flush="true"/>).
  • redirects are slower than forwards because the browser has to make a new request.
  • Database access is typically very expensive in terms of server resources. Use a connection pool to share database connections efficiently between all requests, but don't use the JDBC ResultSet object itself as the cache object.
Moving from JSP to EJB (Page last updated June 2001, Added 2001-06-18, Author Patrick Sean Neville, Publisher Java Developers Journal). Tips:

  • Entity EJBs should contain aggregate get/set methods that return chunks of data rather than fine-grained get/set methods for individual attributes, to reduce unnecessary database, transactional, and network communication overheads.
  • Avoid stateful session beans as they are resource-heavy, since one instance is maintained for each client.
  • Under heavy loads, entity beans should do more than merely represent a table in a database. If you are merely retrieving and updating data values, consider using JDBC within session beans instead.
  • If you have one large database host but only a small Web and middleware host, consider moving much of your logic into stored procedures and calling them via JDBC in session beans.
  • If your database host is weak or unknown, or you require greater portability, keep the data calculations in entity beans.
  • Consider using a single stateless session bean to provide access to other EJBs (this is a façade pattern). This optimizes multiple EJB references and calls by keeping them in-process.
  • Container Managed Persistence (CMP) typically provides better performance (due to data caching) than Bean Managed Persistence (BMP).
Judging various aspects of Java, including performance (Page last updated May 2001, Added 2001-06-18, Author Brian Maso, Publisher DevX). Tips:

  • J2EE defines component models with high scalability potential. Maximizing scalability requires sticking to stateless session beans and handling all database interactions programmatically (through pooled JDBC connections).
  • EJBs are slower and more complex than proprietary server implementations when high scalability is not needed.
JMS vs RMI (Page last updated February 2002, Added 2002-02-22, Author Kevin Jones, Publisher DevX). Tips:

  • RMI calls marshall and demarshall parameters, adding major overhead.
  • Every network communication has several overheads: the distance between the sender and the receiver adds a minimum latency (limited by the speed the signal can travel along the wire, about two-thirds of the speed of light: London to New York would take about 3 milliseconds); each network router and switch adds time to respond to data, on the order of 0.1 milliseconds per device per packet.
  • Part of most network communications consists of small control packets, adding significant overhead.
  • One RMI call does not generally cause a noticeable delay, but even tens of RMI calls can be noticeable to the users.
  • Beans written with many getXXX() and setXXX() methods can incur an RMI round trip for every data attribute.
  • Messaging is naturally asynchronous, and allows an application to decouple network communications from ongoing processing, potentially avoiding threads from being blocked on communications.
Pseudo Sessions for JSP, Servlets and HTTP (Page last updated March 2001, Added 2001-03-21, Author Budi Kurniawan, Publisher OnJava). Tips:

  • Use pseudo sessions rather than HttpSessions to improve web server scalability.
  • Pseudo sessions reside on file instead of in memory, thus both decreasing memory and allowing sessions to be distributed across multiple servers.
  • Pseudo sessions do not use cookies, instead they alter URLs to encode the session, and so reduce the generation of session objects by cookie-declining browsers.
Clustering for J2EE and Java application servers. Looks at Bluestone Total-e-server, Sybase Enterprise Application Server, SilverStream Application Server, and WebLogic Application Server. (Page last updated February 2001, Added 2001-03-21, Author Abraham Kang, Publisher JavaWorld). Tips:

  • Clustering should allow failover if a machine/process crashes. For stateful sessions, this requires state replication.
  • Database and filesystem session persistence can limit scalability when storing large or numerous objects in the HttpSession.
  • To scale the static portions of your Website, add Web servers; to scale the dynamic portions of your site, add application servers.
Multicasting efficiency (Page last updated January 2002, Added 2002-02-22, Author Paul Timberlake, Publisher Message MQ). Tips:

  • When dealing with large numbers of active listeners, multicast publish/subscribe is more efficient than broadcast or multiple individual connections (unicast).
  • When dealing with large numbers of listeners with only a few active, or if dealing with only a few listeners, multicasting is inefficient. This scenario is common in enterprise application integration (EAI) systems. Inactive listeners require all missed messages to be resent to them in order when the listener becomes active.
  • A unicast-based message transport, such as message queuing organized into a hub-and-spoke model, is more efficient than multicast for most application integration (EAI) scenarios.
Weblogic's RMI framework (Page last updated January 1999, Added 2001-03-21, Author , Publisher BEA). Tips:

  • Use a single, multiplexed, asynchronous, bidirectional connection for RMI client-to-network traffic instead of the standard reference implementation using multiple sockets.
  • Try to improve the serialization mechanism for faster RMI [Externalization is better].
  • Use local calls for objects located in the same JVM.
  • Minimize distributed garbage collection.
  • Use smart stubs which provide data caching and localized execution in addition to the normal remote execution and data fetching capabilities.
HTTP sessions vs. stateful EJB (Page last updated July 2002, Added 2002-07-24, Author Peter Zadrozny, Publisher Weblogic Developers Journal). Tips:

  • The comparative costs of storing data in an HTTP session object are roughly the same as storing the same data in a stateful session bean.
  • Failure to remove an EJB that should have been removed (from the HTTP session) carries a very high performance price: the EJB will be passivated which is a very expensive operation.
Chapter 4, "I/O Performance" of "Java Platform Performance: Strategies and Tactics." (Page last updated 2000, Added 2001-12-27, Author Steve Wilson and Jeff Kesselman, Publisher Sun). Tips:

  • Default Serialization is slow.
  • Use the transient keyword to define fields to avoid having those fields serialized. Examine serialized objects to determine which fields do not need to be serialized for the application to work.
Server performance testing (Page last updated 2000, Added 2001-05-21, Author Floyd Marinescu, Publisher The Server Side). Tips:

  • Use session beans as a façade to your entity beans to encapsulate the workflow of one entire usecase in one network call to one method on a session bean (and one transaction).
Optimizing entity beans (Page last updated May 2001, Added 2001-05-21, Author Akara Sucharitakul, Publisher Sun). Tips:

  • Use container-managed persistence when you can. An efficient container can avoid database writes when no state has changed, and reduce reads by retrieving records at the same time as find() is called.
  • Minimize database access in ejbStores. Use a "dirty" flag to avoid writing tee bean unless it has been changed.
  • Always cache references obtained from lookups and find calls. Always define these references as instance variables and look them up in the setEntityContext (method setSessionContext for session beans).
  • Avoid deadlocks. Note that the sequence of ejbStore calls is not defined, so the developer has no control over the access/locking sequence to database records.
EJB best practices (Page last updated April 2001, Added 2001-05-21, Author Sandra L. Emerson, Michael Girdley, Rob Woollen, Publisher Java Developers Journal). Tips:

  • To avoid resources being held unnecessarily for long periods, a transaction should never encompass user input or user think time.
  • Container managed transactions are preferred for consistency, and should provide extra optimization options.
  • Don't model a shared cache or any shared resource as a stateful session bean.
  • Stateless session beans are easier to scale than stateful session beans. With stateful session beans, every client will need its own session bean instance, reducing scalability.
  • Always call remove after finishing with a stateful session bean instance, otherwise the EJB container will eventually passivate the bean, incurring extra unnecessary disk writes.
J2EE clustering (Page last updated August 2001, Added 2001-08-20, Author Abraham Kang, Publisher JavaWorld). Tips:

  • To support distributed sessions, make sure: all session referenced objects are serializable; store session state changes in a central repository.!skip=542
Avoiding memory leaks in EJBs (Page last updated April 2001, Added 2001-05-21, Author Govind Seshadri, Publisher IT World). Tips:

  • Make sure that any beans which have session scope implement the HttpSessionBindingListener interface
  • Explicitly release any resources that may be used within the bean by implementing the valueUnbound() callback.
  • Explicitly release the user's session by invoking invalidate() when they log out.
  • Try setting the session invalidation interval to a smaller value than the default 30 minutes.
  • Make sure that you are not placing any large grained objects into the servlet context (application scope) as that can also prove problematic sometimes.
"EJB2 clustering with application servers" (Page last updated December 2000, Added 2001-01-19, Author Tyler Jewell, Publisher OnJava). Tips:

  • [Article discusses multiple independent ways to load balance EJBs]
Experiences building a servlet (Page last updated June 2001, Added 2001-06-18, Author Asif Habibullah, Jimmy Xu, Publisher Java Pro). Tips:

  • Keep the size of the client tier small so that downloads are fast.
  • Use the servlet init() and destroy() methods to start and stop limited and expensive resources, such as database connections.
  • Make the servlets thread-safe and use connection pooling.
  • Use PreparedStatements rather than plain Statement objects.
  • Use database stored procedures.
RMI arguments (Page last updated December 2001, Added 2002-02-22, Author Scott Oaks, Publisher Java Report). Tips:

  • Some application servers can automatically pass parameters by reference if the communicating EJBs are in the same JVM. To ensure that this does not break the application, write EJB methods so that they don't modify the parameters passed to them.
Servlet 2.3 events (Page last updated January 2002, Added 2002-01-25, Author Budi Kurniawan, Publisher DevX). Tips:

  • The Servlet 2.3 specification adds application and session events. [Event driven applications can often be scaled more easily than process driven applications].
Choosing a J2EE application server, emphasizing the importance of performance issues (Page last updated February 2001, Added 2001-02-21, Author Steve Franklin, Publisher DevX). Tips:

  • Application server performance is affected by: the JDK version; connection pooling availability; JDBC version and optimized driver support; caching support; transactional efficiency; EJB component pooling mechanisms; efficiency of webserver-appserver connection; efficiency of persistence mechanisms.
Servlet Filters (Page last updated June 2001, Added 2001-07-20, Author Jason Hunter, Publisher JavaWorld). Tips:

  • Servlet Filters provide a standardized technique for wrapping servlet calls.
  • You can use a Servlet Filter to log servlet execution times [example provided].
  • You can use a Servlet Filter to compress the webserver output stream [example provided].
Implementing clustering on a J2EE web server (JBoss+Jetty) (Page last updated September 2001, Added 2001-10-22, Author Bill Burke, Publisher OnJava). Tips:

  • The different EJB commit options affect database traffic and performance. Option 'A' (read-only local caching) has the smallest overhead.
  • Hardware load balancers are a simple and fast solution to distributing HTTP requests to clustered servers.
EJB2.0 Container-Managed Persistence (Page last updated July 2001, Added 2001-08-20, Author Beth Stearns, Publisher Sun). Tips:

  • EJB 2.0 Container-Managed Persistence provides local interfaces which can avoid the performance overheads of remote interfaces.
Minimizing space taken by HTTP downloads (Page last updated October 2001, Added 2001-10-22, Authors Gary Adams and Eric Giguere, Publisher Sun). Tips:

  • Use HttpConnection.getLength() to determine the number of bytes needed to to hold the data from a download.
  • Use a ByteArrayOutputStream to accumulate results if the content length is indeterminate.
  • The best performance is obtained from a 1.1 compliant webserver using persistent connections.
Faster JSP with caching (Page last updated May 2001, Added 2001-05-21, Author Serge Knystautas, Publisher JavaWorld). Tips:

  • The (open source) OSCache tag library provides fast in-memory caching.
  • Cache pages or page sections for a set length of time, rather than update the page (section) with each request.
  • Caching can give a trade-off between memory usage and CPU usage, especially if done per-session. This trade-off must be balanced correctly for optimal performance.
Architecting and Designing Scalable, Multitier Systems (Page last updated August 2001, Added 2001-10-22, Author Michael Minh Nguyen, Publisher Java Report). Tips:

  • Separate the UI controller logic from the servlet business logic, and let the controllers be mobile so they can execute on the client if possible.
  • Validate data as close to the data entry point as possible, preferably on the client. This reduces the network and server load. Business workflow rules should be on the server (or further back than the front-end).
  • You can use invisible applets in a browser to validate data on the client.
Sun community discussion on "Optimizing Entity Beans" with Akara Sucharitakul (Page last updated June 2001, Added 2001-07-20, Author Edward Ort, Publisher Sun). Tips:

  • Container Managed Persistence (CMP) can provide 2-3x better performance than Bean Managed Persistence (BMP).
Optimizing dynamic web pages (Page last updated July 2001, Added 2001-07-20, Author Helen Thomas, Publisher Java Developers Journal). Tips:

  • Dynamic generation of web pages is more resource intensive than delivering static web pages, and can cause serious performance problems.
  • Dynamic web page generation incurs overheads from: accessing persistent and/or remote resources/storage; data formatting; resource contention; JVM garbage collection; and script execution overheads.
  • Dynamic content caching tries to mitigate Dynamic web page generation overheads by reusing content that has already been generated to service a request.
  • JSP cache tagging solutions allow page and fragment level JSP output to be automatically cached.
  • On highly personalized sites page-level caching results in low cache hit rates since each page instance is unique to a user.
  • Component-level caching applies more extensively when components are reused in many pages, but requires manual identification of bottleneck components.
Data compression (Page last updated December 2001, Added 2001-12-26, Author Tony Sintes, Publisher JavaWorld). Tips:

  • [Article covers how to add zip compression to RMI communications].!skip=470
Stateful vs Stateless EJBs (Page last updated May 2001, Added 2001-05-21, Author Chuck Caveness, Doug Pardee, Publisher IT World). Tips:

  • Stateless session beans can support multiple clients, thus increasing scalability.
Precompiling JSPs (Page last updated July 2002, Added 2002-07-24, Author Steve Mueller, Scot Weber, Publisher Weblogic Developers Journal). Tips:

  • Precompile your JSPs one way or another to avoid the first user having a slow experience.
Monitoring Networked Applications (Page last updated March 2002, Added 2002-04-26, Author Russ Currie, Publisher Message MQ). Tips:

  • Use network probes to break down how the network is being used by the various networked applications on it.
Deciding whether EJB is appropriate. (Page last updated September 2001, Added 2001-10-22, Author Ed Roman, Publisher The Server Side). Tips:

  • An HTTP layer is not always necessary. Connecting directly to EJBs is faster and provides automatic load balancing.
Tuning tips intended for Sun's "Web Server" product, but actually generally applicable. (Page last updated 1999, Added 2000-10-23, Author ? - a Sun document, Publisher Aikido). Tips:

  • Use more server threads if multiple connections have high latency.
  • Use keep-alive sockets for higher throughput.
  • Increase server listen queues for high load or high latency servers.
  • Avoid or reduce logging.
  • Buffer logging output: use less than one real output per log.
  • Avoid reverse DNS lookups.
  • Write time stamps rather than formatted date-times.
  • Separate paging and application files.
  • A high VM heap size may result in paging, but could avoid some garbage collections.
  • Occasional very long GCs makes the VM hang for that time, leading to variability in service quality.
  • Doing GC fairly often and avoiding paging is more efficient.
  • Security checks consume CPU resources. You will get better performance if you can turn security checking off.
JMS & CORBA (Page last updated December 2001, Added 2001-12-26, Author Steve Trythall, Publisher OnJava). Tips:

  • Asynchronous messaging is a proven communication model for developing large-scale, distributed enterprise integration solutions. Messaging provides more scalability because senders and receivers of messages are decoupled and are no longer required to execute in lockstep.
Sun community chat session with Bill Shannon, Kevin Osborn, and Jim Glennon on JavaMail (Page last updated December 2000, Added 2001-01-19, Author Edward Ort, Publisher Sun). Tips:

  • You might see a performance increase by using multiple connections to your mail server. You would need to get multiple Transport objects and call connect and sendMessage on each of them, using multiple threads (one per connection) in your application.
  • JavaMail 1.2 includes the ability to set timeouts for the initial connection attempt to the server.
  • JavaMail tries to allow you to make good and efficient use of the IMAP protocol. Fetch profiles are one technique to allow you to get batches of information from the server all at once, instead of single pieces on demand. Used properly, this can make quite a difference in your performance.
Various tips. For web servers? (Page last updated 2000, Added 2000-10-23, Author ?, Publisher ?). Tips:

  • Compression uses significant system resources. Don't use it on a server unless necessary.
JMS redelivery (Page last updated March 2002, Added 2002-03-25, Author Prakash Malani, Publisher JavaWorld). Tips:

  • Both auto mode (Session.AUTO_ACKNOWLEDGE) and duplicate delivery mode (Session.DUPS_OK_ACKNOWLEDGE) guarantee delivery of messages, but duplicate okay mode can have a higher throughput, at the cost of the occasionally duplicated message.
  • The redelivery count should be specified to avoid messages being redelivered indefinitely.
iPlanet Web Server guide to servlets, with a section at the end on "Maximizing Servlet Performance". (Page last updated July 2000, Added 2001-02-21, Author ?, Publisher Sun). Tips:

  • Try to optimize the servlet loading mechanism, e.g. by listing the servlet first in loading configurations.
Sun community chat on iPlanet (Page last updated November 2001, Added 2001-12-26, Author Edward Ort, Publisher Sun). Tips:

  • Optimal result caching (caching pages which have been generated) needs tuning, especially the timeout setting. Make sure the timeout is not too short.

1-7 of 7