'분류 전체보기'에 해당되는 글 239건

  1. 2010.03.22 Item 21. 전략을 표현할 때 함수 객체를 사용하자.
  2. 2010.03.22 Item 22. static 맴버 클래스를 많이 사용하자.
  3. 2010.03.22 Item 23. 새로 작성하는 코드에서는 원천(raw) 타입을 사용하지 말자.
  4. 2010.03.22 Item 24. 컴파일 경고 메시지가 없게 하자.
  5. 2010.03.11 Item 10. toString 메소드는 항상 오버라이드 하자.
  6. 2010.03.11 Item 9. equals 메소드를 오버라이드 할 때는 hashcode 메소드도 항상 같이 오버라이드 하자.
  7. 2010.03.10 Item 8. equals 메소드를 오버라이딩 할 때는 보편적 계약을 따르자.
  8. 2010.03.08 Item 7. 파이널라이저(Finalizer)의 사용을 피하자
  9. 2010.03.08 Item 6. 쓸모 없는 객체 참조를 제거하자.
  10. 2010.03.08 Item 5. 불필요한 객체의 생성을 피하자.
  11. 2010.03.04 Item 4. private 생성자를 사용하여 인스턴스 생성을 못하게 하자.
  12. 2010.03.04 Item 3. private 생성자나 enum 타입을 사용해서 싱글톤의 특성을 유지하자.
  13. 2010.03.04 Item 2. 생성자의 매개변수가 많을 때는 빌더를 고려하자.
  14. 2010.03.04 Item 1. 생성자 대신 static factory method 사용을 고려하자.
  15. 2010.02.18 리눅스에서 FTP로 사용하기
  16. 2010.02.18 Java GC의 원리
  17. 2010.02.11 How to find bottleneck in J2EE application (조대협)
  18. 2010.02.11 JVM GC와 메모리 튜닝 (조대협)
  19. 2010.02.11 Java jstat로 메모리 모니터링
  20. 2010.02.02 vmstat 항목 설정 보기
  21. 2010.02.02 Crontab 설정 하기
  22. 2010.02.02 자바로 정렬하기
  23. 2009.10.07 Apache Log에 I/O byte 나오게 하기
  24. 2009.08.30 오라클 10g의 계층구조
  25. 2009.08.25 jdbc 사용 예제 1

자바에서는 함수 포인터를 제공하지 않는다. 그러나 객체 참조를 사용해서 비슷한 효과를 얻을 수 있다.
- 호출된 객체의 메소드에서 다른 객체의 메소드를 수행하도록 객체를 정의하는 것이 가능하다. 그런 메소드 하나만 달랑 외부에 제공하는 클래스의 인스턴스는 사실상 메소드 포인터의 역할을 한다.

전략 패턴을 사용할 때는 만약에 모든 인스턴스가 기능이 동일할 때 불필요한 객체 생성 비용을 절감하기 위해 싱글톤이어야 한다.

Posted by 서오석
,

중첩 클래스에는 네가지 종류가 있는데, static 맴버 클래스, static이 아닌 맴버 클래스, 익명 클래스, 지역클래스이다. 이 모두를 내부클래스(inner class)라고 한다.

구문적으로 봐선 static과 static이 아닌 맴버 클래스의 유일한 차이점은, static 맴버 클래스의 선언부에 static 수식어가 있다는 것이다. 간단한 예를 들면 이런 것이다.

 static 맴버 클래스  static이 아닌 맴버 클래스
 

public class A{
    private a;

    static class A_1{
    }
    static class A_2{
    }
}
  

public class A{
    private a; 

    class A_1{
    }
    class A_2{
    }
}

구문적으로는 유사하지만 이 두 종류의 중첩 클래스는 많이 다른다.

static이 아닌 멤버 클래스의 각 인스턴스는 자신을 포함하는 외곽 클래스의 인스턴스와 은연 중에 연관이 된다. 따라서 static이 아닌 멤버 클래스의 인스턴스 메소드 내부에서 외곽 클래스의 인스턴스 메소드를 호출하거나, 또는 this 키워드를 사용해서 외곽 클래스의 인스턴스에 대한 참조를 얻을 수 있다.
private static맴버 클래스의 경우 외곽클래스를 나타내는 객체 컴포넌트 표현에 사용하는데 적합다. 불필요하게 외부에 객체 참조를 제공하지 않아 리소스 낭비가 없어진다.
만일 외곽 클래스의 인스턴스를 사용할 필요가 없는 멤버 클래스를 선언한다면, 항상 static 수신자를 선언부에 추가하여 static 맴버 클래스로 만들자.

익명 클래스는 함수 객체를 생성하는데 많이 사용된다. 또한 Runnalbe, Thread, TimerTask 등의 인스턴스와 같은 프로세스 객체를 생성하는데도 많이 사용한다. 그리고 static 팩토리 메소드 내부에서도 사용된다.

지역클래스는 지역 변수가 선언 될 수 있는 곳이면 어디든 선언될 수 있으며, 지역 변수와 동일한 유효 범위를 갖는다. 또한 다른 종류의 중첩 클래스와 동일한 속성을 갖는다. 맴버 클래스처럼 이름을 가질 수 있어 반복적으로 사용될 수 있으며 static이 아닌 상황일 때에 한해서 익명 클래스 처럼 외곽 인스턴스를 가지며 static 멤버를 포함할 수 없다.

만일 중첩 클래스가 메소드 외부에서 접근할 필요가 있거나 코드가 너무 길어서 메소드 내부에 두기 적합하지 않다면 멤버 클래스를 사용하자. 만일 멤버 클래스의 각 인스턴스가 외곽 클래스의 인스턴스를 참조할 필요가 있다면 static 이 아닌 멤버 클래스로 만들고 그렇지 않다면 static으로 만든다. 클래스가 어떤 메소드 내부에 속한다는 가정하에, 만일 한 곳에서만 그 클래스의 인스턴스를 생성할 필요가 있고 그 클래스의 특성을 나타내는 타입이 이미 존재한다면, 익명 클래스로 만들고 그렇지 않으면 지역클래스로 만든다.
Posted by 서오석
,

우선 용어정의 부터 하자.

 용어  사용 예
 매개화 변수타입(Parameterized type) List<String> 
 실 타입 매개변수(Actual type parameter) String 
 제네릭 타입(Generic type) List<E> 
 형식 타입 매개변수(Formal type parameter)
 언바운드 와일드 카드 타입
(Unbounded wildcard type)
 List<?>
 원천 타입(Raw type)  List
 바운드 타입 매개변수
(Bounded type parameter)
 <E extends Number>
 재귀적 타입 바운드(Recursive type bound) <T extends Comparable<T>> 
 바운드 와일드 카드 타입
(Bounded wildcard type)
List<? extends Number> 
 제네릭 메소드(Generic method) static <E> List<E> asList(E[] a) 
 타입 토큰(Type token) String.class 

하나 이상의 타입 매개 변수를 선언하고 있는 클래스나 인터페이스를 제네릭 클래스, 또는 제네릭 인터페이스라고 부른다. 각 제네릭 타입에서는 매개변수화 타입들을 정의한다.
정의 하는 법은 클래스나 인터페이스 이름 다음에 <> 를 사용해서 매개변수들을 나타내는데. 실 타입 매개변수들은 제네릭 타입의 형식 타입 매개변수와 각각 대응된다. 
각 제네릭 타입에서는 raw 타입을 정의하는데 원천 타입이란 실 타입 매개변수가 없이 사용되는 제네릭 타입 이름을 말한다. (ex - List 이렇게)
이 경우 타입 매개변수를 주지 않고 커렉션 타입이나 다른 제네릭 타입을 사용할 수 있지만 그렇게 해서는 안된다. 왜냐면 원천 타입을 사용하면 제네릭의 장점인 타입 안전과 표현력 모두를 포기하는 것이기 때문이다. 
이걸 막지 않은 이유는 호환성 때문인데 새 코드에서는 되도록 원천 타입을 사용해선 안된다.

간단한 예로  List와 같은 원천 타입을 ㅅ용하면 타입의 안정성을 상실하지만 List<Object>과 같은 매개변수 타입을 사용하면 그렇지 않다.

향후에 작성하는 새 코드에는 원천 타입을 사용하지 않는다는 규칙에도 두가지 예외가 있는데 런타임 시에는 제네릭 타입의 정보가 없어진다는 것 때문에 가능하다.
첫 번째로 원천 타입은 클래스 리터럴의 형태로 사용해야 한다. 자바 명세서에 보면 원천 타입을 매겨변수화 타입과 사용할 수 없다. 즉 String[].class, int.class는 모두 적법하지만 , List<String>.class와 List<?>.class는 허용되지 않는다.
두 번째로 instanceof 연산자와 관련된 것이다. 제네릭 타입의 정보는 런타임 시에 없어지므로 언바운드 와일드 카드 타이이 아닌 다른 매개변수화 타입에 대해 instanceof 연산자를 사용할 수 없다.
Posted by 서오석
,

개발하면서 경고메시지가 나오는데 가능한 모든 uncheck 경고 메시지를 없애자.
만일 모든 경고 메시지를 없앤다면, 코드의 타입 안전이 보장되므로 대단히 좋은 일이다.

- @Suppress-Warnings("unchecked") 주석을 사용해서 경고 메시지를 안 나타나게 억제할 수 있다.
하지만 해당 코드가 타입 안전을 보장하는지 확실치 않은 상태에서 경고 메시지를 억제하면, 우리 스스로 안전 불감증을 갖는 셈이 되어 경고 메시지 없이 코드는 컴파일 되겠지만 런타임 시에는 여전히 ClassCastException 예외를 발생시킬 수 있다. 그리고 SuppressWarnings 주석은 가급적 제일 작은 범위로 사용하자.
절대로 하나의 클래스 전체에 대해 Suppress-Warnings 수적을 사용하지 말자. 그렇게하면 중요하지 않음 메시지들 때문에 정말 중요한 메시지를 알아보기 어렵기 때문이다.

- @Suppress-Warnings("unchecked") 주석을 사용할 때는 그 이유를 주석으로 추가하자 . 그렇게 하면 다른 사람들이 코드를 이해하는데 도움이 될 것이다.

즉.
unchecked 경고 메시지는 중요하므로 무시하지 말자. 모든 unchecked 경고 메시지는 런타임 시에 ClassCastException 예외가 생길 수 있다는 것을 나타낸다.

Posted by 서오석
,

java.lang.Object 클래스는 toString 메소드를 구현하고 있다. 근데 이 메소드에서 반환하는 문자열은 우리가 생각하는 형태가 아니다. 예를 들어 PhoneNumber@163b91 형태로 반환하게 된다. 이러면 보기도 힘들고 그닥 좋지 않다.

toString 메소드를 잘 구현하면 좀더 클래스를 편하게 사용할 수 있다.

근데 이 기능은 예전에 이클립스 3.4에서는 플러그인을 깔아서 했어야 하는데 3.5부터는 이클립스에서 자동으로 오버라이딩 메소드를 만들어주는 기능이 있어서 그냥 그걸 쓰면 된다.
Posted by 서오석
,

equals 메소드를 오버라이드 하는 모든 클래스에서는 반드시 hashcode 메소드도 오버라이드 해야 한다. 그렇지 않으면 Obejct.hashcode 메소드의 보편적 계약을 위반하게 된다.

자바 API 문서의 계약 사항은 다음과 같다.

  • 애플리케이션 실행 중에 같은 객체에 대해 한 번 이상 호출 되더라도 hashCode 메소드는 같은 정수를 일관성 있게 반환해야 한다.
  • equals(Object) 메소드 호출 결과 두 객체가 동일하다면, 두 객체 각각에 대해 hashCode 메소드를 호출 했을 때 같은 정수 값이 나와야 한다.
  • equals(Object) 메소드 호출 결과 두 객체가 다르다고 해서 두 객체 각각에 대해 hashCode 메소드를 호출 했을 때 반드시 다른 정수 값이 나올 필요는 없다. 그러나 같지 않은 객체들에 대해 hashCode 메소드에서 서로 다른 정수 값을 반환하면 이 메소드를 사용하는 해시 컬렉션들의 성능을 향상시킬 수 있음을 알아야 한다.

중요한 것은 hashCode의 오버라이딩에 실패했을 때 두번째 조항 즉 동일한 객체들은 같은 해시 코드 값을 가져야 한다는 것이 위배된다.

 import java.util.*;

public final class PhoneNumber {
    private final short areaCode;
    private final short prefix;
    private final short lineNumber;

    public PhoneNumber(int areaCode, int prefix,
                       int lineNumber) {
        rangeCheck(areaCode,    999, "area code");
        rangeCheck(prefix,      999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode  = (short) areaCode;
        this.prefix  = (short) prefix;
        this.lineNumber = (short) lineNumber;
    }

    private static void rangeCheck(int arg, int max,
                                   String name) {
        if (arg < 0 || arg > max)
           throw new IllegalArgumentException(name +": " + arg);
    }

    @Override public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber)o;
        return pn.lineNumber == lineNumber
            && pn.prefix  == prefix
            && pn.areaCode  == areaCode;
    }

위 소스에서는 hashCode에 대한 오버라이딩 메소드가 없기 때문에 문제가 된다.
아래와 같이 클래스를 HashMap과 함께 사용한다고 해보자

     public static void main(String[] args) {
        Map<PhoneNumber, String> m
            = new HashMap<PhoneNumber, String>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
    }
}

이 시점에서 m.get(new PhoneNumber(707, 867, 5309), "Jenny");을 호출하면 "Jenny"가 반환될 것 같지만 실제로는 null이 반환된다. 여기서 두개의 PhoneNumber 인스턴스가 개입되어 있는데 하나는 HashMap에 삽입할 때 키로 사용되었고 첫번째와 동일한 값을 갖는 두번째 인스턴스는 검색 키에서 사용되었다. 즉 PhoneNumber 클래스에서 hashCode 메소드를 오버라이드 하지 않아서 두 인스턴스가 서로 다른 해시 코드 값을 갖게 된 것이다.
 하지만 모든 객체가 다 똑같은 해시 코드를 갖게 되서는 안된다. 좋은 해시 메소드는 동일하지 않은 객체들에 대해 서로 다른 해시 코드를 만든다. 바로 이것이 hashCode 계약의 세번째 조항의 의미이다.

 @Override public int hashCode() {
      int result = 17;
      result = 31 * result + areaCode;
      result = 31 * result + prefix;
      result = 31 * result + lineNumber;
      return result;
  }

이 메소드에서는 PhoneNumber 인스턴스의 세가지 중요한 필드만으로도 간단히 연산한 결과를 반환하므로 동일한 PhoneNumber 인스턴스들은 똑같은 해시 코드 값을 갖는다. 이런 형태로 구현해야 한다.

Posted by 서오석
,

equals 메소드의 오버라이딩의 경우 생각보다 간단하지 않다. 아래 조건 중 어느 하나라도 만족하면 그냥 상속받은 그대로 사용하는 것이 낫다.

  • 클래스의 각 인스턴스가 본래부터 유일한 경우
    • 인스턴스가 갖는 값보다 활동하는 개체임을 나타내는 것이 더 중요한 Thread와 같은 클래스가 해당되며 이런 것들은 놀리적인 비교가 의미가 없다. 그래서 그냥 Object equals를 사용하면 된다.
  • 두 인스턴스가 논리적으로 같은지 검사하지 않아도 되는 클래스
    • 굳이 두 인스턴스를 비교할 필요 없으면 만들지 말자.
  • 수퍼 클래스에서 equals 메소드를 이미 오버라이딩 했고 그 메소드를 그대로 사용해도 좋은 경우.
    • Set 인터페이스를 구현하는 대부분의 클래스들은 AbstractList로부터, Map의 경우는 AbstractMap에서 상속 받아 사용한다.
  • private이나 패키지 전용 클래스라서 이 클래스의 equals 메소드가 절대 호출되지 않아야 할 경우.
    • 우연히 호출될 수 있는 그런 상황에서는 다음과 같이 equals메소드를 반드시 오버라이딩해서 호출되지 않도록 한다.

열거형(enum) 같은 경우 오버라이드를 할 필요가 없다. 그리고 equals를 오버라이딩 하는 이유는 객체 참조만으로 인스턴스가 동일한지 여부를 판단하는게 아니라, 인스턴스가 갖는 값을 비교해서 논리적으로 같은지 판단할 필요가 있을 때 하는 것이다.

뭐.. 굳이 오버라이딩을 해서 구현하겠다면 아래의 규칙은 지켜야 한다.

  • 재귀적(Reflexive) : null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 반드시 true를 반환해야 한다.
  • 대칭적(Symmetric) : null이 아닌 모든 참조 값 x와 y에 대해, y.equals(x) 가 true를 반환한다면 x.equals(y)도 반드시 true를 반환해야 한다.
  • 이행적(Transitive) : null이 아닌 모든 참조 값 x, y, z에 대해, 만일 x.equals(y)가 true를 반환하고 y.equals(z)가 true를 반환한다면 x.equals(z)도 반드시 true를 반환해야 한다.
  • 일관적(Consistent) : null이 아닌 모든 참조 값 x와 y에 대해, equals 메소드에서 객체 비교 시 사용하는 정보가 변경되지 않는다면, x.equals(y)를 여러 번 호출하더라도 일관성 있게 true 또는 false를 반환해야 한다.
  • null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 반드시 false를 반환해야 한다.

 

 

Posted by 서오석
,

 파이널라이저는 예측 불가에다가 위험하기도 하며 일반적으로는 불필요하다.
파이널라이저의 경우 신속하게 실행된다는 보장이 없다. 즉 객체가 사용할 수 없게 되는 시점부터 파이널라이저가 실행되는 시점까지 긴 시간이 소요될 수 있다.
 즉 파이널라이저 내부에서 실행 시간이 매우 중요한 작업을 절대 하면 안된다. 예를 들어 파일을 닫는 경우이다.
이 경우 파일이 언제 닫힐 지 몰라 어플리케이션에 문제가 생길 수 도 있다.

파이널라이저의 실행 시점이 늦는 것은 이론 상의 문제만은 아니다. 클래스에 파이널라이저를 사용하면 간혹 인스턴스들의 매모리 회수와 재활용이 지연될 수 있다. 즉 사용을 하지 말자 되도록이면 try catch fanally 로 구문으로 확실하게 실행시키도록 하자.

Posted by 서오석
,

자바는 가비지컬랙션이 알아서 메모리를 회수해 주는데 이게 만능이 아니다.
아래 코드를 보자.

 import java.util.*;

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * Ensure space for at least one more element, roughly
     * doubling the capacity each time the array needs to grow.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}


저걸 보면 메모리 누수가 생기는 부분을 찾을 수 있다 어디냐면 pop() 부부인데
elements를 꺼네오고 나서 아무런 행동을 하지 않기 때문에 해당 공간은 메모리에 그대로 남아있게 되고
그러다보면 프로그램이 메모리 부족으로 죽을 수 도 있다.

그래서 저 부분을 이렇게 수정해주어야 한다.

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result =  elements[--size];
        elements[size] = null;
        return result;
    }
이렇게 하면 필요없는 참조를 null로 만들게 되어 가비지컬랙션이 해당 부분의 메모리를 수거해 간다.

매모리 누출이 흔히 생기는 이유는 객체 참조를 놔눠서 생기는 부분 말구 또 따른 이유는 캐시이다. 객체 참조를 캐시에 저장하면 저장했다는 것을 잊어버리고 객체가 더 이상 필요 없을 때 까지 캐시에 내벼려 두기 쉽다. 그리고 마지막으로 리스너와 콜백을 사용할 경우 매모리 누수가 발생할 수 있다.
Posted by 서오석
,

불변 클래스의 불필요한 객체 생성을 막으려면 생성자 보다는 static factory method를 사용하는 것이 좋다.

아래 예를 들어보자

 import java.util.*;

public class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods omitted

    // DON'T DO THIS!
    public boolean isBabyBoomer() {
        // Unnecessary allocation of expensive object
        Calendar gmtCal =
            Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        Date boomEnd = gmtCal.getTime();
        return birthDate.compareTo(boomStart) >= 0 &&
               birthDate.compareTo(boomEnd)   <  0;
    }
}


위의 코드를 보면 Calendar 객체와 TimeZone 객체 및 두 개의 Date 인스턴스를 불필요하게 생성한다.

윗 부분을 아래와 같이 고치면 좀 더 성능이 좋아진다.

 import java.util.*;

class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods

    /**
     * The starting and ending dates of the baby boom.
     */
    private static final Date BOOM_START;
    private static final Date BOOM_END;

    static {
        Calendar gmtCal =
            Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_START = gmtCal.getTime();
        gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
        BOOM_END = gmtCal.getTime();
    }

    public boolean isBabyBoomer() {
        return birthDate.compareTo(BOOM_START) >= 0 &&
               birthDate.compareTo(BOOM_END)   <  0;
    }
}


근데 위에 성능이 좋아지긴 하지만 항상 그런 건 또 아니다.
이유는 Calender 인스턴스의 생성비용이 많이 들기도 하지만 isBabyBoomer 메소드가 아예 호출되지 않는다면 BOOM_START와 BOOM_END가 쓸데없이 초기화되기 때문이다. 근데 이걸 배제하려면 lazy initialization을 해야하는데 그닥 권장하지 않는다.

자바에서는 오토박싱(autoboxing)이란 것으로 불필요한 객체를 생성하게 되는데 이건 기본형 데이터를 이에 대응되는 박스화 기본현 클래스 객체로 자동 변환을 해주는 기능이다. 이와 반대로 하는 것이 오토언박싱(autounboxing)이다.

Long sum = 0L;  <- 오토박싱된다.

문제는 이러면 엄청나게 느려질 수 있다. 그러니까 의도하지 않은 오토박싱이 생기지 않도록 주의해야 한다.

Posted by 서오석
,

유틸리티 클래스 들은 인스턴스를 생성하지 못하게 설계되어 있다. 인스턴스 생성이 의미가 없기 때문인데, 그런 클래스라고 해도 명시적으로 지정한 생성자가 없을 때는 컴파일러가 default 생성자를 만들어준다.
이 경우 클래스 사용자 입장에서는 이 생성자가 다른 것과 차이가 없으며 javadoc 프로그램으로 생성하는 api 문서에도 나타나므로 인스턴스 생성이 가능한 클래스로 오인될 수 있다.
그러므로 생성자 호출을 통한 인스턴스 생성을 방지하고 api문서에도 나타나지 않도록 할 수 있는 방법이 필요하다.

이걸 간단하게 없애주는 거싱 있다. 디폴트 생성자는 명시적으로 지정한 생성자가 전혀 없을 때만 자동으로 만들어진다. 따라서 private 생성자를 정의하면 인스턴스 생성이 불가능한 클래스를 만들 수 있다.

 public class IdiomCode {
 
  //디폴트 생성자가 자동으로 생성되는 것을 방지
  private IdiomCode(){
   throw new AssertionError();
  }
}

이렇게 처리하면 된다.
Posted by 서오석
,

싱글톤(singleton)은 정확히 하나의 인스턴스만 생성되는 클래스이다.
싱글톤을 구현하는 방법은 3가지 방법이 있는데 가장 좋은 방법만 아래 예제와 함께 설명을 하도록 하겠다.

// Enum singleton - the preferred approach - page 18
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}


싱글톤을 구현하는 방법 중 enum형을 이용해서 하는 방법이 좋다. 하나의 요소를 갖는 enum타입을 만들면 된다.

이 방법은 public 필드 방법과 기능적으로 동일하지만 더 간단하며 복잡한 직렬화 리플렉션 상황에서도 직력화가 자동으로 지원되고 인스턴스가 여러개 생기지 않도록 확실하게 보장해준다.
Posted by 서오석
,

여러 필드의 값을 받는 클래스의 경우 텔리스코핑 생성자 패턴을 사용하면 매번 인스턴스를 생성할 때 원하지 않는 매개변수에도 초기값을 주어야 한다.
매개변수가 많은 생성자의 경우 두번째 대안으로 자바빈즈 패턴(getter, setter를 사용하는 것)이 있는데 이것은 여러 번의 메소드 호출로 나누어져 인스턴스로 생성되므로, 생성 과정을 거치는 동안 자바빈 객체가 일관된 상태를 유지하지 못할 수 있다.
 또한 자바빈즈 패턴은 불변 클래스를 만들 수 있는 가능성을 배제함으로 thread에서 안정성을 유지하려면 개발을 할 때 추가적인 노력이 필요하다는 단점이 있다.

이래서 나온게 빌더 패턴이다.

예제는 다음과 같다

 
public class NutFacts {
 
 private final int servingSize;
 private final int calories;
 private final int fat;
 private final int sodium;
 
 public static class Builder {
  
  private final int servingSize; //필수 값
  
  private int calories; //선택값
  private int fat;  //선택값
  private int sodium; //선택값
  
  public Builder(int servingSize){
   this.servingSize = servingSize;
  }
  
  public Builder calories(int val){
   calories = val;
   return this;
  }
  
  public Builder fat(int val){
   fat = val;
   return this;
  }
  
  public Builder sodium(int val){
   sodium = val;
   return this;
  }
  
  public NutFacts build(){
   return new NutFacts(this);
  }
 }
 
 private NutFacts(Builder builder){
  servingSize = builder.servingSize;
  calories = builder.calories;
  fat = builder.fat;
  sodium = builder.sodium;
 }

}

 public class item2 {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub

  NutFacts drink = new NutFacts.Builder(3).calories(23).build();
 }

}


그냥 자바 빈즈 패턴의 경우 getter와 setter를 만들어서 썼기 때문에 코드가 길어진다. 하지만 아래 class item2 클래스를 보면 자신이 집어넣을 필수 파라미터(Builder)를 설정하고 뒤에 옵셔널한 파라미터(calories)를 설정하도록 한다.

생성자가 static factory method에서 많은 매개 변수를 갖게 될 클래스를 설계할 때는 빌더 패턴이 좋은 선택이다. 특히 선택 매개변수가 대부분인 경우 그렇다. 그리고 그 간 사용하던 텔리스코핑 생성자 패턴보다 빌더를 사용하면 코드의 가독성이 좋고 작성도 쉬워진다. 또한 빌더는 자바빈즈 패턴보다 훨씬 안전하다.

위의 예제보다 더 좋은 예가 있는 곳을 찾았다.
아래 링크를 따라가면 마린의 속성 정보를 가지고 빌더패턴을 적용한 소스가 있다.
http://dbjavayo.tistory.com/entry/h2-생성자의-매ㅑ개변수가-많을-때는-빌더builder를-고려하자


Posted by 서오석
,

클래스 사용자가 크 클래스의 인스턴스를 생성하는 방법 중 유용하게 사용할 수 있는 방법은 클래스에 public static factory 메소드를 두는 것이다. 이것은 static 메소드로써 그 클래스의 인스턴스 하나를 생성하여 반환한다.

즉. 아래와 같은 형태로 개발을 하는 것이다.
 public static Bollean valueOf(boolean b){
return b ? Boolean.TRIE : Boolean.FALSE;
}

장점
생성자 대신 static factory method를 제공할때의 장점
1. 자기 나름의 이름을 가질수 있다.
자바 클래스는 동일한 시그니처를 갖는 생성자를 하나만 가질 수 있게 되어 있다. 그래서 개발을 할 때 파라미터의 순서만 바꿔서 두 개의 생성자를 만드는 경우가 있는데 이런 때는 나중에 실수를 할 가능성이 크다. static factory method는 자신의 이름을 가질 수 있기 때문에 생성자와 같은 제약을 받지 않는다. 하나으 ㅣ클래스에 동일한 시그니처를 갖는 여러개의 생성자가 필요한 경우 생성자 대신 static factory method를 사용하자.

2. 호출할때마다 매번 새로운 객체를 생성할 필요가 없어진다.
불변 클래스의 경우 이미 생성된 인스턴스를 다시 사용할 수 있으며, 불필요하게 중복된 인스턴스들이 생성되는 것을 방지하기 위해 이미 생성된 인스턴스들을 저장했다가 반복 사용할 수 있다. static factory method는 여러번 호출되더라도 이미 생성된 동일 객체를 반환할 수 있으므로 클래스에서는 언제든지 인스턴스들의 존재를 직접 제어할 수 있다.
3. 자신의 클래스 인스턴스만 반환하는 생성자와 달리, 자신이 반환하는 타입의 어떤 서브타입 객체도 반환할 수 있다.

4. 매개변수화 타입의 인스턴스를 생성하는 코드를 간결하게 해준다.
parameterized 클래스의 생성자를 호출할 때는 type parameter를 지정하는데 그럼 이렇게 코딩해야 한다.
 Map<String, List<String>> map = new HashMap<String, List<String>>();
이렇게 하면 타이핑할 분량도 많아지고 소스도 복잡해진다. 그래서 이것을 static factory를 사용해서 아래와 같이 바꿔 사용한다.
     public static <K,V> HashMap<K, V> newInstance(){
     return new HashMap<K, V>();
    }

사용시
        Map<String, List<String>> m = UsingPoi.newInstance();
* UsingPoi는 클래스 이름.

단점
1.인스턴스 생성을 위한 static factory 메소드만 가지고 있으면서, public이나 protected 생성자가 없는 클래스의 경우는 서브 클래스를 가질수 없다

2. 다른 static 메소드와 햇갈릴수 있다.
이 부분은 클래스나 인터페이스 주석으로 표기를 하거나 공통적인 작명 규칙을 만들고 지킴으로써 단점을 줄일 수 있다.
  • valueOf - 자신의 매개변ㅅ와 같은 값을 갖는 인스턴스를 반환한다.
  • of - valueOf를 줄인 형태의 이름이면 EnumSet에서 사용한다.
  • getInstace - 매개변수에 나타난 인스턴스를 반환하지만 매개변수와 같은 값을 갖지 않는 경우도 있다. (싱글톤)
  • newInstance - getInstance와 유사하나 반환되는 각 인스턴스가 서로 다르다.
  • getType - getInstance와 유사하나 factory method가 다른 클래스에 있을 때 사용한다.

Posted by 서오석
,

1. FTP 실행

~]ftp 접속할서버ip [port]

ex) ftp 123.123.213.0 21

접속할 서버의 IP가 아니라 만약 리눅스의 hosts에 host명이 등록되어있다면 그걸 써도 된다.
접속되면 id,와 pw를 물어보는데 계정을 치고 들어가면

fip> 이렇게 나온다.
여기서부터는 ftp 명령어를 사용하면 된다.

2. 명령어
 명령어 설명 
 !  Shell을 실행한다. Unix 로그인 유저의 디폴트 쉘을 실행하게 된다. 이 쉘을 종료하려면 exit를 치면 ftp로 돌아오게 된다.
 ! 명령어  Shell명령어를 실행한다. Unix프롬프트 상에서의 명령어를 실행하게 된다.
내부적으로 Shell을 구동시켜 실행하게 된다
 ascii  화일 전송모드를 ASCII화일로 변경한다. 디폴트로 설정되어 있다.
 binary  화일 전송모드를 Binary화일로 변경한다.
 bye, quit  ftp를 종료한다
 cd  리모트 디렉토리를 변경한다
 lcd  로컬 디렉토리를 변경한다
 chmod  리모트 시스템의 화일의 모드를 변경한다.
 close  ftp 세션을 종료한다. ftp는 종료하지 않는다.
 dir  리모드 시스템의 화일의 리스트를 얻는다.
 open  리모트 IP와 포트를 주고 ftp세션을 연다.
 prompt  Interactive mode를 토글시킨다.
화일 하나 하나를 물어보고 전송할지 물어보지 않고 전송할 지를 결정한다.
 put  로컬의 화일을 리모트로 전송한다.
 mput  로컬의 여러 화일을 리모트로 전송한다.
 get  리모트의 화일을 로컬로 가져온다.
 mget  리모트의 여러 화일들을 로컬로 가져온다.
 user  다른 사용자로 세션을 연다.
 pwd  현재의 리모트 디렉토리 Path를 보여준다.
 mdir  리모트 디렉토리내의 조건에 맞는 화일을 보여준다.
mdir *.c
 delete  리모트 시스템의 화일을 삭제한다.

'서버 이야기 > Linux 이야기' 카테고리의 다른 글

VI에서 인코딩 변환해서 보기  (0) 2010.12.02
Linux screen 사용하기  (0) 2010.11.30
vmstat 항목 설정 보기  (0) 2010.02.02
Crontab 설정 하기  (0) 2010.02.02
리눅스 명령어 모음  (0) 2009.08.05
Posted by 서오석
,
레퍼런스 : http://gosilkroad.com/30000025219


GC (Gabage Collector)

Sun JVM 의 Heap Memory 영역은 다음과 같은 구조를 가진다.

 

[ Sun MicroSystems Memory 구조도 ]

위의 그림을 보면 메모리 영역은 다음과 같이 커다랗게 3부분으로 나뉘어진다.

1. Young Area
   - Eden
   - Survivor Spaces 2 block (S0, S1)
   - Virtual
2. Tenured Area (Old Area 라고도 부른다.)
   - Tenured
   - Virtual
3. Permanent Area
   - Perm
   - Virtual

* 보통 메모리를 명확하게 java -Xms1024m -Xmx1024m -XX:MaxPermSize=128m 형식으로 할당하기 때문에
  Virtual 영역은 없다고 보면 된다.

Java 실행시 Heap Size 를 주게되는데 설정된 Heap Size 를 설정된 Ratio 별로 나누어서 할당하게된다.
(Ratio 중에는 Young : Tenured 영역 분할 Ratio, Young 중에서 Eden : Survivor 영역 Ratio 가 있다.
자세한 사항은 이번 글에서는 넘어간다. -_-;)

그럼 위의 3가지 영역을 설명하면서 GC 되는 절차를 알아보자.

1. Young Area
 보통 객체 생성시 new 라는 keyword 를 이용하는데 이때 객체가 생성되는 부분이 Young 의 Eden 부분이다.
 Eden 부분에 객체가 생성되면서 메모리를 할당받게 되고 Eden Area 의 설정된 % 이상으로 메모리가 할당되면
 Young GC 가 일어나게 된다. (GC 의 종류도 참 많다. --;)
 이때 Young GC 가 일어나면 어떤 현상이 벌어지는지 눈 똑바로 뜨고 글을 읽어라.
 
 - Young GC
 Survivor 영역은 그림에서 보면 두개로 나뉘어져 있다. 각각을 S0, S1 이라 부른다.
 Eden 의 설정치 만큼 메모리가 할당되게 되면 Young GC 가 일어나는데 Young GC 란 Eden + S0 (처음 Young GC 일때)
 의 영역을 GC 를 실행한다. 이때 GC 대상에 포함되지 않는 (즉, Reference 가 살아있는 객체) 객체는
 S1 영역으로 옮겨지고 나머지 Week Reference 객체는 GC 된다.
 그후 다시 Eden 에 객체가 생성되고 메모리가 또 차게되면 이번에는 Eden + S1 이 GC 의 대상이고 이때 동일하게
 Reference 가 살아있는 객체는 S0 영역으로 옮겨지고 나머지 Week Reference 는 GC 된다.
 
 그럼 이말을 잘 풀어서 보면, Eden 은 무조건 GC 대상이고 Survivor 의 두 Area 가 돌아가면서 GC 대상이 되는것이다.
 Young GC 가 될때는 Eden + Survivor Area 중 한곳의 메모리를 전부 비우게 되며 Refrence 가 남아있는 객체를 다른 한곳의
 Survivor Area 로 할당하게 되는것이다. 즉, Survivor Area 의 S0 or S1 둘중에 한곳은 항상 비어있다고 보면 된다.
 
 그럼 Singleton Pattern 형식처럼 항상 메모리에 상주하게 구현한 객체들은 S0 와 S1 사이를 왔다리 갔다리 한다는 말인가?
 흠... -_-;
 정답은 안그렇다.
 
 java option 에 TenuringThreshold (default 32) 값에 의해서 S0, S1 을 몇번이상 왔다리 갔다리하면 이것은 오래 남을 객체라
 판단하고 Tenured Area (Old Area) 으로 보내진다.
 또한 Eden 10M, S0 1M, S1 1M 라고 했을때 Young GC 가 Eden + S0 가 일어날때 GC 후의 값이 5M 라면 어떻게 될까?
 정답은 S1 에 1M 를 할당하고 남은것은 Tenured Area 로 보내진다.

2. Tenured Area
 Old Area 라고도 부르며 Young Area 에서 넘어온 객체들이 할당되어진다.
 Tenured Area 역시 설정된 % 이상으로 메모리가 할당되면 GC 가 일어나게 되는데 이것이 Old GC 이다.
 Old GC 가 일어나도 Old 영역이 줄어들지 않는다면 Full GC 가 일어날 수 있으며 Memory Leak 을 생각해 볼 수 있다.

3. Permanent Area
 Permanent Area 는 Class 를 Loading 하는데 사용한다. 보통 Perm Size 는 64 이며 크게 잡을경우 128 을 잡기도 한다.
 개발 환경에서는 Hot Deploy 가 편하지만 운영환경이라면 Hot Deploy 하는것이 좋지않다.
 Hot Deploy 하게되면 Permanent Area 로 Class 가 Loading 되게되며, 기존의 Loading 되어있던 Old version 이 바로 Unloading 되는것은
 아니기 때문이다. (Permanent Area 의 경우 GC 라 하지않고 Unloading 이라 부른다.)
 문제는 수행중에 필요에 의해서 Unloading 이 발생하게 되는데 수시로 Hot Deploy 가 일어나서 Permanent Area 가 Full 나게되면
 Full GC 가 일어나게 된다.
 Loading 해야될 Class 보다 적은 Permanent Size 를 할당할 경우 Full GC 도 수시로 일어나지만 OOME (Out Of Memory Error) 가
 발생하며 WAS 가 down 될 수도 있다.
 

** Full GC 란 ?
 Old GC 후에 Tenured Area 이 줄어들지 않았을 경우 발생 할 수 있으며 또한 Permanent Area 가 Full 나면 일어난다.
 Full GC 가 일어나면 보통 수초에서 수십초의 시간을 소요하게 되며, 이때 JVM 은 Full GC 이외의 일은 하지 않는다.
 즉, 일반 상용 사이트의 Web Application Server 가 Full GC 가 일어나면 수초에서 수십초간 WAS 가 정지되는
 상황에 놓이는 것이다. 그러나 사이트가 정지되는 일은 별로 일어나지 않는데 이유는 일반 상용 사이트는 HA (High Availibilty)
 구성을 해놓기 때문에 한쪽 WAS 가 먹통이 되면 L/B (Load Balancer) 가 알아서 Load Banlancing 을 하게됨으로 일반 Client 가 사용하기에는
 아무런 문제가 발생하지 않는다.

 Full GC 가 일어난다는 것은 메모리 할당이 잘못됐거나, Memory Leak 발생하거나 하는 경우이므로 튜닝 포인트를 찾아내야한다.

Posted by 서오석
,

[출처] 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이상)

자세한 내용은
http://www.j2eestudy.co.kr/lecture/lecture_read.jsp?table=j2ee&db=lecture0201_1&id=1&searchBy=subject&searchKey=deploy&block=0&page=0를 참고하시 바란다.

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에 대량의 데이타를 넣는것들이 대표적인 예가 될 수 가 있다.

좀더 자세한 튜닝 방법에 대해서는http://www.j2eestudy.co.kr/lecture/lecture_read.jsp?db=lecture0401_1&table=j2ee&id=1를 참고하기 바란다.


4. Slow down analysis in DBMS


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

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

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

http://eclipse.new21.org/phpBB2/viewtopic.php?printertopic=1&t=380&start=0&postdays=0&postorder=asc&vote=viewresult

http://www.j2eestudy.co.kr/qna/bbs_read.jsp?table=j2ee&db=qna0104&id=5&searchBy=subject&searchKey=sql&block=0&page=0


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 설정은
http://httpd.apache.org/docs-2.0/mod/core.html#keepalive를 참고하기 바란다. Default가 KeepAlive 가 On으로 되어 있다.

WebServer와 WAS간의 KeepAlive

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

※ WebLogic에서 Webserver와의 KeepAlive설정은
http://e-docs.bea.com/wls/docs81/plugins/plugin_params.html#1143055을 참고하기 바란다.
Default는 KeepAlive가 True로 설정되어 있다.

OS에서 Kernel Parameter 설정

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

※ WebLogic의 OS별 설정 정보은
http://e-docs.bea.com/platform/suppconfigs/configs81/81_over/overview.html를 참고하기 바란다.


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 자체의 분석이 필요하다.

 


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

Hostname 과 IP 가져오기  (0) 2010.11.18
Java GC의 원리  (0) 2010.02.18
JVM GC와 메모리 튜닝 (조대협)  (0) 2010.02.11
Java jstat로 메모리 모니터링  (0) 2010.02.11
iBatis 튜토리얼 문서  (0) 2008.05.15
Posted by 서오석
,
레퍼런스 : http://blog.naver.com/loves0508/3585185

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


1.GC란 무엇인가?


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


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


1) JVM 메모리 영역

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


<그림 1. 메모리 foot print>


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


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


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


<표 1. Java 메모리 영역>



2) GC 알고리즘

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


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

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

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

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

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


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


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


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


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


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


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


○ Full GC

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

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


<그림 4. Full GC>




3. GC가 왜 중요한가?


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


4. 다양한 GC 알고리즘


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

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

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

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

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

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


<그림 7. Parallel GC 개념도>


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

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

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

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

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

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

3) Concurrent GC

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

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


<그림 8. Concurrent GC 개념도>


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

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

4) Incremental GC (Train GC)

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

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


<그림 9. Incremental GC 개념도>


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

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

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


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


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

Example ) java ?verbosegc MyApplication

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


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


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

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

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

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

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

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

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


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


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

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

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


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


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

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

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


※ HP JVM의 GC Log 수집

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

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

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

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


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


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

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


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




6. GC 관련 Parameter


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

1) 전체 Heap Size 조정 옵션

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

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

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

2) Perm size 조정 옵션

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

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

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

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

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

○ -server 옵션

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

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

○ -client 옵션

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


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


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

○ GC 방식에 대한 옵션

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


7.JVM GC 튜닝


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

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

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

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

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

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

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

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

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

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

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


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


○ GC Time 수행 시간 분석

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

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

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

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

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

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

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

<그림 9. GC 소요시간>


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


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


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

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

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

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

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


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


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

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

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

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

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

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

~] jstat --help

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

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



옵션은 다음과 같다.

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

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

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

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

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

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

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

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

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

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

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

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

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

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


사용 방법

jstat -gcutil -h5 16543 1000 10

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

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

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

레퍼런스 : 복사 http://blog.naver.com/ares2011/50023242845

사실 다른 사람이 쓴 것 퍼오는 이유 중 하나는 즐겨찾기를 했는데 나중에 해당 페이지가 삭제되서 못찾을 때의 난감함 때문이다.--;
이 아래부터 퍼온 것.



vmstat를 이용하여 cpu와 i/o 활동을 모니터링을 할 수가 있다.
첫번째 줄은 무시한다.(첫줄은 부팅 이후의 각 통계치에 대한 평균값을 보여준다.)

vmstat에서 중요한 것은 procs 영역의 b 필드이다. b의 수치가 높은 경우 i/o 작업을 위해 cpu가 계속 대기 상태로 있는 경우다.
이런 경우에는 디스크i/o 문제를 확인함.

스왑아웃(so)이 지속적으로 발생한다면 메모리가 부족한 것이다. 하지만 일정간격으로 스왑아웃이 발생되는 것은 정상적이다.
스왑아웃(so)필드는 항상 0에 가까워야 한다.

 # vmstat 1
 procs     memory                       page                disk              faults           cpu
 r b w   swap     free      re  mf    pi po fr de sr s6 s1 s1 s1   in    sy     cs  us  sy  id
 0 0 0 20719952 5846888 1235 9503 0  0   0  0   0  0   0   0   0  462 11337  1085 14  12  75
 0 0 0 20719864 5846792 1228 9315 0  0   0  0   0  0   0   0   0  250 10729  1029 13  13  74
 0 0 0 20720224 5847424 1261 9514 0  0   0  0   0  0   0   0   0  262 10909  1023 15  11  74

 

각 항목 설명
procs :  세가지 상태에 있는 프로세스들의 수

Solaris에서는 멀티 쓰레드 기능을 제공하며, 커널의 스케쥴링도 쓰레드 단위로 한다. 멀티 쓰레딩 방식으로 프로그래밍되어 있지 않은 프로세스는 하나의 쓰레드가 프로세스에 있다. 각 프로세스에 몇 개의 쓰레드가 있는지는 psinfo 명령어의 결과에서 NLWP 항목에 있는 값으로 확인할 수 있다.

# /opt/JSparm/bin/psinfo -f
Date.time 0507.23:09:34 proc 70 zombie 0 runnable 0 lwp 144 cpu 6.11%
avgload  0.07  0.02  0.02 hp+stk 52.75M
  PID  USERNAME HP+STK S NLWP   WCPU%   CPU%  ELAPSED        TIME CMD
  499  root     0.117M O    1   0.29%  0.29%      0d0       00.06 psinfo
  170  root     0.188M S    8   0.25%  0.25%  0d11147       00.63 syslogd
  463  guest    9.383M S    1   0.15%  0.15%   0d8594    01:03.88 netscape
  287  root     0.148M S   12   0.02%  0.02%  0d10724       02.81 mibiisa
  495  guest    0.031M S    1   0.01%  0.01%   0d5498       00.16 vi

쓰레드의 상태는 크기 세가지로 나눌 수 있다.

  • CPU를 사용하고 있는 쓰레드
  • CPU를 사용하기 위해 Run Queue에서 대기하고 있는 쓰레드
  • IO나 이벤트가 완료되기를 기다리며 sleep하는 쓰레드
  •  
    r : In run queue - CPU를 사용하기 위해 Run Queue에서 대기하고 있는 쓰레드의 수 (2 이상이면 CPU busy)

    b : Blocked for resources(I/O, paging, etc.) - IO나 이벤트가 완료되기를 기다리며 sleep하는 쓰레드의 수

        CPU 성능과 관계가 없는 필드이다. Disk의 I/O가 느리기 때문에 b 필드의 수치가 발생한다.

       w : threads swapped out - swap out된 실행가능한 쓰레드의 수 (0 이상이면 메모리가 부족하여 메모리 증설을 고려 한다.)

        시스템의 메모리가 부족할 때 paging이 일어나는데, 페이징으로도 부족한 메모리를 보충하지 못하면 swapping이 일어난다. 이때 메모리로 부터 빠져나기 swap 디바이스에 보관된 쓰레드의 갯수를 w에 보여준다. 한번 swapout된 쓰레드는 그 쓰레드가 다시 실행될 때 물리적 메모리로 들어온다 

        paging이란... 즉 물리적인 메모리의 크기는 한정되어 있기 때문에 실제 메모리의 크기보다 더 큰 메모리가 있는 것처럼 속여서 메모리를 사용하는데 이러한 방법을 가상 메모리 기법이라고 한다. 물론 메모리상에 항상 올라가 있는 프로세스도 있지만 (즉, 커널과 같은 시스템 프로세스) 대부분의 사용자 프로세스들은 실행시에만 메모리에 올라가기 때문에 자주 사용되지 않는 프로세스들은 굳이 메모리상에 있을 필요가 없다. 그래서 이런 놈들은 스왑이라는 영역에 잠시 내려 놓았다가(스왑아웃) 필요할때 다시 메모리로 불러들인다.
        이렇게 메모리에서 스왑으로 왔다 갔다하는 것을 스왑핑(swaping)이라고 한다. 근데 왜 paging을 설명하면 swaping을 얘기했냐?  왜냐면 이 두놈은 같은 개념이다. page가 뭐냐면 앞에서 얘기한 가상 메모리 기법을 사용하기 위해서 메모리를 일정 크기로 나누어 놓거든. 이렇게 나눈 것을 page라고 부르지. 예를 들어 13K의 메모리를 차지하고 있는 프로세스를 스왑아웃하려 할때 실제 스왑아웃된 영역의 크기는 얼마를 차지하느냐? 페이지 단위로 스와핑이 일어나기 때문에 만약 시스템의 페이지 사이즈가 4k면 16k의 영역을 차지한다는 거지...  근데 이게 맞나 모르겠다.

     memory : virtual and real memory의 사용에 관한 정보(?)
    swap : amount of swap space currently available (Kbytes)

    사용 가능한 가상 메모리(physical memory + swap device)의 크기를 나타낸다.

     free : size of the free list(말안해도 뭔지 알겠지?) - size of the free list (Kbytes)

    사용 가능한 physical memory 의 크기를 나타낸다.

     disk - f0, s0,s1, s4

     최대 4개의 디스크에 대하여 초당 read/write 횟수를 나타낸다.

     

    page : page에 관한 정보(썰렁)
    re : page reclaims

    프리 메모리가 부족할 경우, paging이 발생하여 최근에 사용되지 않은 페이지를 찾아서 프리시켜 부족한 메모리를 보충하게 되는데,
    이렇게 프리되는 페이지의 내용은 훼손되지 않고 프리 메모리 영역에서 관리된다. re값은 이렇게 프리된 페이지가 프로세스의 요청에 따라 다시 사용되는 페이지의 수를 나타낸다.

     

    at : 주소 변환에 실패한 페이지?

    pi : page in 되는 페이지 수
    디스크로 paged out한 페이지를 다시 메모리에서 읽을 때, 읽은 페이지를 KB로 환산한 값이다.

    po : page out 되는 페이지 수
    프리 시킬 페이지의 내용이 파일 시스템의 캐쉬일 때 해당 디스크 파티션에 저장을 하는데, 이때 디스크에 저장된 페이지를 KB로 환산
    한 값이다.

    fr : pages freed per second

    sr 필드 값(스캔한 페이지의 개수)을 KB 단위로 환산한 값이다.

    de : Anticipated short term memory shortfail (어렵다, 어려워...)

    sr : Pages scanned by clock algorithm. per second

    free physical memory가 전체 physical memory 양의 1/64보다 크게 만들기 위해서 시스템에 있는 페이지를 스캔하여 최근에 사용되지
    않는 페이지를 찾아 프리 시킨다. 이때 스캔한 페이지의 개수

     

    faults 시간 간격 동안 페이지 폴트의 수
    in : Device interrupts per second - 초당 인터럽드 횟수(clock interrupt 제외)

    인터럽트는 주변 장치가 데이타의 입출력이 발생할 때, CPU에게 알려주는 방법으로 사용된다. 예를 들어, 사용자가 키보드의 키를 누르면 키보드를 관장하는 하드웨어가 인터럽트를 발생하여 키보드로 부터 입력이 있다는 것을 CPU에게 알리게 된다. 그러면, CPU는 이미 정의되어 있는 키보드 처리 루틴(인터럽트 핸들러)을 호출하여 입력을 처리한다. 이처럼 주변장치들은 대부분 인터럽트를 발생하는 기능을 가지고 있다. 시스템에는 클럭이라는 장치가 있는데 이것은 1초에 100번씩 일정하게 인터럽트를 발생한다. 그러나 in에는 일정하게 발생하는 클럭 인터럽트는 제외한다.
    sy : System calls per second - 초당 시스템 콜 회수

    시스템 콜은 커널이 제공하는 기본적인 서비스 루틴이다. 프로세스의 실행부터 종료될 때까지 시스템 콜의 연속적인 호출이라고 할 수 있다. Solaris에는 200가지가 넘는 시스템 콜이 있으며, 가장 흔히 사용하는 시스템 콜에는 read(), write()가 있다.
    cs : CPU context switch rate - 초당 컨텍스트 스위칭 횟수

    Solaris에서 스케줄링은 커널 쓰레드 단위로 한다. 하나의 쓰레드가 실행되다가 쓰레드에게 주어진 시간을 다 사용하거나, 자발적인 sleep하거나, 자원이 준비되기를 기다리거나, 우선순위가 높은 쓰레드가 CPU를 사용하기 위해 대기하고 있으면, 다른 쓰레드로 전환이 발생한다. 이것을 컨텍스트 스위칭이라고 한다.

     

    여기서 잠깐 페이지 폴트란? 일단 실제 메모리 보다 큰 가상의 메모리 주소를 사용한다고 앞에서 얘기했다. 이 가상메모리 주소는 실제 사

    용될시에는 실제 물리적 메모리 주소와 Mapping이 되야한다. 바로 이때 Mapping이 안되서 CPU가 trap요청하는데 이것이 바로 페이지 폴트다. 이렇게 페이지 폴트가 일어나면 아까 얘기한 것처럼 오래된 놈 하나를 스왑영역으로 page out시켜버려 가상메모리 주소가 물리적
    메모리 주소에 Mapping할 수 있도록 공간을 만들어 준다. 그리고 나서야 트랩된 명령어의 실행을 다시 시작한다.

     

    cpu

    Solaris 운영체제는 1초에 100번씩 CPU가 어떤 일을 하는지 조사한다. 이때 CPU가 user mode에서 작업을 실행하면 user_tick에 1을 증가 시키고, system mode에서 작업을 실행하면 system_tick에 1을 증가시킨다.
    CPU가 실행할 작업이 없어서 쉬고 있는 경우, I/O(block device: hard disk)를 기다리는 작업이 있으면 wait_tick에 1을 증가시키고, 그렇지 않으면 idle_tick에 1을 증가시킨다.
    vmstat나 sar 명령어는 이러한 값을 시스템으로 부터 얻어서 주어진 시간간격(interval) 의 차이 값을 구하여 각각을 백분율(%)로 나타낸 것이 CPU 사용량이다.

              sar     vmstat              CPU
    ---------------------------------------------------
             %usr  =  us            run on user mode
             %sys  =  sy            run on system mode
    %wait + %idle  =  id            idle
    


    us : 사용자 프로세스가 사용하는 총 CPU시간의 비율
         (이건 시스템 프로세스보다 우선순위가 낮은 사용자 프로세스가 사용하는 CPU시간이고...)
    sy : 시스템 부하로 사용하는 총 CPU시간의 비율
        (시스템이 사용하는 CPU시간을 말하는 거겠지.)
    id : 놀고 있는 CPU시간의 비율

    '서버 이야기 > Linux 이야기' 카테고리의 다른 글

    Linux screen 사용하기  (0) 2010.11.30
    리눅스에서 FTP로 사용하기  (0) 2010.02.18
    Crontab 설정 하기  (0) 2010.02.02
    리눅스 명령어 모음  (0) 2009.08.05
    apache 재시작 문제  (0) 2009.07.31
    Posted by 서오석
    ,

    1. Crontab 실행 및 종료
       실행 : /etc/rc.d/init.d/crond start
       종료 : /etc/rc.d/init.d/crond stop

    2. 설정
    크론 설정 : /etc/crontab ( ~]$crontab 해도 됨.)

    option
      -e : 설정된 파일을 새롭게 편집
      -d : 등록된 내용을 삭제
      -l  : 현재 등록된 내용 보기

    3. 시간 등록
    ex) 01 03 * * * /disk/script/tomcatctl start

     필드 의미  내용
     필드 1 (01)  분  0-59
     필드 2 (03)  시  0-23
     필드 3 (*)  일  0-31
     필드 4 (*)  월  1-12
     필드 5 (*)  요일   0-7 (0 또는 7=일, 1=월, 2=화, ...)
     필드 6 (/disk/script/tomcatctl start)  명령어 실행할 명령어 

    연속으로 수행하는 법 : 콤마로 구분

    ex 1 00,01,08,13,17,20 * * * /disk/start.sh

    00시, 01시, 08시 .. 1분에 start.sh를 수행하게 됨.

    '서버 이야기 > Linux 이야기' 카테고리의 다른 글

    리눅스에서 FTP로 사용하기  (0) 2010.02.18
    vmstat 항목 설정 보기  (0) 2010.02.02
    리눅스 명령어 모음  (0) 2009.08.05
    apache 재시작 문제  (0) 2009.07.31
    리눅스 파일 내용 검색  (1) 2009.07.28
    Posted by 서오석
    ,

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

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

    java.lang.Comparable
    java.util.Comparator

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

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

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

    이럼 끝

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

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

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

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


    예제 소스



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

    Treeset 사용

    Posted by 서오석
    ,

    가끔 서버에 파일을 업로드하거나 다운로드 할 때 해당 파일의 용량이 얼마나 되는지 알아야 할 때가 있다. 이런 때 아파치 로그로 I/O가 된 Byte를 표기해야 하는데 아래와 같은 설정을 하면된다.
    ( 새로 Apache를 설치하는게 아니라 동작하고 있는 녀석에 모듈을 추가하는 것이다. )

    우선 내가 작업한 환경은 apache-2.0.63 이다.

    우선 http://httpd.apache.org/download.cgi 여기서 아파치를 다운 받는다.
    다운로드 (http://mirror.apache-kr.org/httpd/httpd-2.0.63.tar.gz)

    서버에서 작업 중이라면

    /]wget http://mirror.apache-kr.org/httpd/httpd-2.0.63.tar.gz 

    이렇게 명령어를 하면 파일이 다운받아진다.

    그리고 다운받은 녀석의 압축을 푼다.

    /]tar xvf httpd-2.0.63tar.gz

    하면 압축이 줄줄 풀린다. 

    그리고 logio.c가 있는지 확인하기 위해 디렉토리 이동을 한다.
    /]cd /httpd-2.0.63/modules/loggers

    그 안에 mod_logio.c가 있는지 확인이 되었으면 다시 /]로 돌아가서 configure 파일을 만든다.

    /]./configure --enable-mods-shared=logio --prefix=/apache

    이러면 checking 하면서 설정할 파일들이 제대로 있는지 확인하고나서 configure 파일을 만들게 된다.

    그리고 예전에 make 파일을 지우기위해 다음과 같은 명령어를 쓴다.

    /]make clean

    그럼 예전에 make 파일 만든게 몽땅 삭제된다.

    그리고 나서 logio만 make하기 위해 modules/loggers 밑으로 이동한다.

    /]cd modules/loggers

    그다음 make 명령어로 make 파일을 만든다.
    modules/loggers] make
    /httpd-2.0.63/srclib/apr/libtool --silent --mode=compile gcc  -g -O2 -pthread    -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -DAP_HAVE_DESIGNATED_INITIALIZER   -I/httpd-2.0.63/srclib/apr/include -I/httpd-2.0.63/srclib/apr-util/include -I. -I/httpd-2.0.63/os/unix -I/httpd-2.0.63/server/mpm/prefork -I/httpd-2.0.63/modules/http -I/httpd-2.0.63/modules/filters -I/httpd-2.0.63/modules/proxy -I/httpd-2.0.63/include -I/httpd-2.0.63/modules/generators -I/httpd-2.0.63/modules/dav/main -prefer-non-pic -static -c mod_log_config.c && touch mod_log_config.lo
    /httpd-2.0.63/srclib/apr/libtool --silent --mode=link gcc  -g -O2 -pthread    -DLINUX=2 -D_REENTRANT -D_GNU_SOURCE -DAP_HAVE_DESIGNATED_INITIALIZER   -I/httpd-2.0.63/srclib/apr/include -I/httpd-2.0.63/srclib/apr-util/include -I. -I/httpd-2.0.63/os/unix -I/httpd-2.0.63/server/mpm/prefork -I/httpd-2.0.63/modules/http -I/httpd-2.0.63/modules/filters -I/httpd-2.0.63/modules/proxy -I/httpd-2.0.63/include -I/httpd-2.0.63/modules/generators -I/httpd-2.0.63/modules/dav/main -module -export-dynamic    -o mod_log_config.la mod_log_config.lo
    make[1]: Leaving directory `/httpd-2.0.63/modules/loggers'

    이러면 apache의 modules아래 mod_logio.so 라는 파일이 만들어졌을 것이다.

    그럼 이제 apache/conf 에 가서 로그 설정만 해주면 된다.

    apache/conf]vi httpd.conf

    맨 아래에 LoadModule logio_module modules/mod_logio.so 요걸 추가해준다.

    이거 추가하면 아파치가 이 모듈을 로딩한다는 것이다. 그리고 로그 포멧 중 맨 마지막 combined 라고 된 녀석에 뒤에

    %I %O를 추가해주면 된다.

    LogFormat "%h %D %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combined

    그리고 저장하구 나와서

    apache/bin]./httpd -t

    해서 문법이 맞는지 확인 한다.
    Syntax OK 이렇게 나오면 잘 한 것이다.

    이제 아파치 설정을 적용한다.

    apache/bin] ./apachectl graceful

    끝~ 이제 access 로그 뒷 쪽에 보면 해당 I/O 값들이 붙어서 날아 올 것 이다.





    Posted by 서오석
    ,

    오라클 10g의 계층구조

    2009. 8. 30. 20:03

    보호되어 있는 글입니다.
    내용을 보시려면 비밀번호를 입력하세요.

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

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

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



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


    public class JdbcConnect {

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

    Posted by 서오석
    ,