[도서]자바 성능 튜닝 이야기 정리

Posted by 백재현 on December 12, 2017

#[도서] 자바 성능 튜닝 이야기 정리

도서 : 개발자가 반드시 알아야 할 자바 성능 튜닝 이야기

저자 : 이상민

책을 읽고 실습해보면서 모르는 용어 / 중요하다고 생각한 내용들을 정리해놓았다. 그뿐이다…

  1. 디자인 패턴은 꼭 써야 한다
    • MVC 패턴 : 뷰는 이벤트를 발생시키고 이벤트의 결과를 보여주는 역할, 모델은 뷰에서 입력된 내용을 저장, 관리, 수정하는 역할, 컨트롤러는 뷰와 모델의 연결자 역할이라고 생각하면 된다.
    • design pattern : 시스템을 만들기 위해서 전체 중 일부 의미 있는 클래스들을 묶은 각각의 집합을 디자인 패턴이라고 생각하면 된다.
    • 반복되는 의미 있는 집합을 정의하고 이름을 지정해서, 누가 이야기 하더라도 동일한 의미의 패턴이 되도록 만들어 놓은 것이다. - 꼭 알아야 할 기본적이고 실용적인 패턴
    • Business Delegate : 비지니스 서비스 접근을 캡슐화하는 패턴이다.
    • Service Locator : 서비스와 컴포넌트 검색을 쉽게 하는 패턴
    • Session Facade : 비지니스 티어 컴포넌트를 캡슐화하고, 원격 클라이언트에서 접근할 수 있는 서비스를 제공하는 패턴이다.
    • Transfer Object : 일명 Value Object 패턴이라고 많이 알려져 있다. 데이터를 전송하기 위한 객체에 대한 패턴이다.
    • Data Access Object : 일명 DAO라고 많이 알려져 있다. DB에 접근을 전담하는 클래스를 추상화하고 캡슐화 한다. - Serializable 인터페이스를 구현하면 객체를 직렬화할 수가 있다. 다시 말해 서버 사이의 데이터 전송이 가능해진다.
  2. 내가 만든 프로그램의 속도를 알고 싶다.
    • 프로파일링 툴은 개발자용 툴이고, APM툴은 운영 환경용 툴이다.
    • 프로파일링 툴
    • 소스레벨의 분석을 위한 툴이다. (개발시 느린 메서드, 느린 클래스를 찾는 것을 주 목적으로 한다.)
    • 애플리 케이션의 세부 응답 시간까지 분석할 수 있다.
    • 메모리 사용량을 객체나 클래스, 소스의 라인 단위까지 분석할 수 있다.
    • 자바 기반의 클라이언트 프로그램 분석을 할 수 있다. - APM 툴
    • 애플리케이션의 장애 상황에 대한 모니터링 및 문제점 진단이 주 목적이다.
    • 서버의 사용자수나 리소스에 대한 모니터링을 할 수 있다.
    • 실시간 모니터링을 위한 툴이다.
    • 자바 기반의 클라이언트 프로그램 분석이 불가능하다. - Clock time : cpu 사용시간과 대기시간을 더한 실제 소요시간 - JVM에서 사용할 수 있는 설정은 크게 두가지로 나뉜다.
    • 속성(property) : JVM에서 지정된 값들
    • 환경(Environment) : 장비(서버)에 지정되어 있는 값들 (흔히 env라고 한다.) - System.class의 절대 사용하면 안되는 메서드
    • gc() : 자바에서 사용하는 메모리를 명시적으로 해제하도록 GC를 수행하는 메서드
    • exit(int status) : 현재 수행중인 자바VM을 멈춘다.
    • runFinalization() : 가비지 콜렉터가 알아서 호출하는 finalize()를 수동으로 호출 해야 참조 해제 작업을 수행한다. (GC가 제대로 안된다.)
  3. 왜 자꾸 String을 쓰지 말라는 거야.
    • 여러 이터레이션(iteration)에 걸쳐서 개발한다는 말은 하나의 프로젝트를 여러 차수에 걸쳐서 개발하는 방식이다.
    • StringBuffer 클래스는 스레드에 안전하게 설계되어 있으므로, 여러 개의 스레드에서 하나의 StringBuffer 객체를 처리해도 전혀 문제가 되지 않는다. 하지만 StringBuilder는 단일스레드에서의 안전성만을 보장한다.
    • 속도 테스트 결과
    • String은 짧은 문자열을 더할 경우 사용한다.
    • String + String 할경우 쓸데 없는 객체가 생성되서 GC가 자주 일어난다.
    • StringBuffer는 스레드에 안전한 프로그램이 필요할 때 사용한다.
    • StringBuilder는 스레드에 안전한지의 여부와 전혀 관계가 없는 프로그램을 개발할 때 사용한다. - 참고 : (http://12bme.tistory.com/42?category=682904)
  4. 어디에 담아야 하는지…
    • Collection 인터페이스 구성
    • Collection : 가장 상위 인터페이스
    • set : 중복을 허용하지 않는 집합을 처리하기 위한 인터페이스
    • SortedSet : 오름차순을 갖는 Set인터페이스
    • List : 순서가 있는 집합을 처리하기 위한 인터페이스이기 떄문에 인덱스가 있어 위치를 지정하여 값을 찾을 수 있다. 중복을 허용하며, List 인터페이스를 상속받는 클래스 중에 가장 많이 사용하는 것으로 ArrayList가 있다.
    • Queue : 여러 개의 객체를 처리하기 전에 담아서 처리할 때 사용하기 위한 인터페이스이다. 기본적으로 FIFO(First In First Out)을 따른다.
    • Map : 키와 값의 쌍으로 구성된 객체의 집합을 처리하기 위한 인터페이스이다. 이 객체는 중복되는 키를 허용하지 않는다.
    • SortedMap : 키를 오름차순으로 정렬한다. - Set 인터페이스
    • HashSet : 데이터를 해쉬 테이블에 담는 클래스로 순서 없이 저장된다.
    • TreeSet : red-black(이진트리구조)이라는 트리에 데이터를 담는다. 값에 따라서 순서가 정해진다. 데이터를 담으면서 동시에 정렬을 하기 떄문에 HashSet보다 성능상 느리다.
    • LinkedHashSet : 해쉬테이블에 데이터를 담는데, 저장된 순서에 따라서 순서가 결정된다.
    • 데이터를 순서에 따라 탐색하는 작업이 필요할 떄는 TreeSet을 사용하는 것이 좋다. 하지만 그럴 필요가 없을때는 HashSet이나 LinkedHashSet을 사용하는 것을 권장한다. - List 인터페이스
    • 배열은 최초 선언시 담을 수 있는 데이터 개수가 한정 되어 있다. 하지만, List 인터페이스를 구현한 클래스들은 담을 수 있는 크기가 자동으로 증가된다.
    • Vector : 객체 생성시에 크기를 지정할 필요가 없는 배열 클래스이다.
    • ArrayList : Vector와 비슷하지만, 동기화 처리가 되어 있지 않다.
    • LinkedList : ArrayList와 동일하지만 Queue인터페이스를 구현했기 떄문에 FIFO 큐 잡업을 수행한다.
    • 성능상 차이 : ArrayList는 여러스레드에서 접근할 경우 문제가 발생할 수 있지만, Vector는 여러 스레드에서 접근할 경우를 방지하기 위해서 get()메서드 안에 Synchronized가 선언되어 있다. 따라서 성능 저하가 발생할 수 밖에 없다. - Map 인터페이스
    • Map은 key와 value의 쌍으로 저장되는 구조체이다.
    • 단일 객체만 저장되는 다른 Collection API들과는 다르게 따로 분리되어 있다.
    • HashTable : 데이터를 해쉬 테이블에 담는 클래스이다. 내부에서 관리하는 해쉬 테이블 객체가 동기화되어 있으므로, 동기화가 필요한 부분에서는 이 클래스를 사용하기 바란다.
    • HashMap : 데이터를 해쉬 테이블에 담는 클래스이다. HashTable 클래스와 다른점은 Null값을 허용한다는 것과 동기화가 되어 있지 않다는 것이다.
    • TreeMap : red-black 트리에 데이터를 담는다 TreeSet과 다른점은 키에 의해서 순서가 정해진다는 것이다.
    • LinkedHashMap : HashMap과 거의 동일하며 이중 연결 리스트라는 방식을 사용하여 데이터를 담는다는 점만 다르다.
      • 이중연결리스트 : 자료구조론에서 앞뒤 노드에 대한 링크 정보를 갖고 잇는 것을 말한다. 만약 앞의 링크값이 Null이거나 비어있으면 가장 첫 노드를 의미하며, 뒤의 링크값이 null이거나 비어있으면 가장 마지막 노드를 의미한다. - Queue 인터페이스
    • PriorityQueue : 큐에 추가된 순서와 상관없이 먼저 생성된 객체가 먼저 나오도록 되어있다.
    • LinkedBlockingQueue : 저장할 데이터의 크기를 선택적으로 정할 수도 있는 FIFO 기반의 링크노드를 사용하는 블로킹 큐다.
    • ArrayBlockingQueue : 저장되는 데이터의 크기가 정해져 있는 FIFO 기반의 블로킹 큐다.
    • PriorityBlockingQueue : 저장되는 데이터의 크기가 정해져 있지 않고, 객체의 생성순서에 따라서 순서가 저장되는 블로킹 큐다.
    • DelayQueue : 큐가 대기하는 시간을 지정하여 처리하도록 되어 있는 큐다.
    • SynchronousQueue : put()메서드를 호출하면, 다른 스레드에서 take() 메서드가 호출될 때까지 대기하도록 되어 있는 큐다. 이큐에 저장되는 데이터가 없다. API에서 제공하는 대부분의 메서드는 0이나 null을 리턴한다.
    • 참고 : 블로킹큐(blocking Queue)란 크기가 지정되어 있는 큐에 더 이상 공간이 없을 때, 공간이 생길 때까지 대기하도록 만들어진 큐를 의미한다. - Collection 인터페이스의 일반적인 사용
    • Set - > HashSet
    • List -> ArrayList
    • Map -> HashMap
    • Queue -> LinkedList
  5. 지금 까지 사용하던 for 루프를 더 빠르게 할 수 있다고?
    • 자바의 JIT(Just In Time) 컴파일러 : JIT 컴파일(just-in-time compilation) 또는 동적 번역(dynamic translation)은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다. 이 기법은 프로그램의 실행 속도를 빠르게 하기 위해 사용된다.
    • For loop의 조건문에 list.size()를 사용하면 반복할 때 마다 해당 메서드가 호출 된다. 변수를 선언하고 list.size()를 초기화 한후 해당 변수를 조건문에 넣는것이 바람직하다.
    • For-Each를 사용하면 별도로 형변환한거나 get()메서드 또는 elementAt()메서드를 호출 할 필요없이 순서에 따라서 해당 객체를 사용할 수 있다. 단, 이방식은 데이터의 첫버째 값 부터 마지막까지 처리해야할 경우에만 유용하다. 만약 순서를 거꾸로 돌리거나, 특정 값부터 데이터를 탐색하는 경우에는 적절하지 않다.
  6. static 제대로 한번 써 보자
    • HTTP상태코드
    • 200번대 : 정상적인 경우의 리턴코드
    • 300번대 : 리다이렉션이 필요한 경우의 리턴코드
    • 400번대 : 클라이언트 오류가 있을 경우 리턴코드
    • 500번대 : 서버에 오류가 있을 경우 리턴코드 - static으로 선언한 class Variable은 클래스 변수라고 한다. - 100개의 VariableTypes 클래스의 인스턴스를 생성하더라도 모든 객체가 class Variable에 대해서는 동일한 주소의 값을 참조한다. - static블록은 클래스가 최초 로딩될 때 수행되므로 생성자 실행과 상관없이 수행된다. - static은 GC의 대상도 되지 않는다. - 자주 사용하고 절대 변하지 않는 변수는 final static으로 선언하자 - static에 계속해서 데이터가 쌓이면 OutOfMemoryError가 발생한다. -> 시스템을 재기동해야한다. - 더 이상 사용가능한 메모리가 없어지는 현상을 메모리 릭(Memory Leak)이라고 한다.
  7. 클래스 정보, 어떻게 알아낼 수 있나?
    • reflection패키지를 사용하면 JVM에 로딩되어 있는 클래스와 메서드 정보를 읽어 올 수 있다.
    • Class 클래스는 클래스에 대한 정보를 얻을 때 사용하기 좋고, 생성자는 따로 없다.
    • Method 클래스를 이용하여 메서드에 대한 정보를 얻을 수 있다. 하지만, Method클래스에는 생성자가 없으므로 Method클래스의 정보를 얻기 위해서는 Class 클래스의 getMethod()메서드를 사용하거나 getDeclareMethod()메서드를 써야한다.
    • Field 클래스는 클래스에 있는 변수들의 정보를 제공 한다. 생성자가 없으므로 Class클래스의 getField()메서드나 getDeclaredField() 메서드를 써야한다.
    • 객체를 비교할때는 리플렉션 정보(.getClass().getName())보다 instanceof를 사용하는게 성능상 좋다.
    • 클래스의 메타 데이터 정보는 JVM의 Perm영역에 저장된다. 만약 Class 클래스를 이용하여 엄청나게 많은 클래스를 동적으로 생성하는 일이 벌어지면 Perm 영역이 더이상 사용하 수 없게 되어 OutOfMemoryError가 발생할 수도 있다.
  8. synchronized는 제대로 알고 써야 한다.
    • 클래스를 하나 수행시키거나 WAS를 기동하면, 서버에 자바 프로세스가 하나 생성된다.
    • 하나의 프로세스에는 여러개의 스레드가 생성된다. 단일 스레드가 생성되어 종료될 수도 있고, 여러 개의 스레드가 생성되어 수행될 수도 있다.
    • 프로세스와 스레드의 관계는 1:다 관계다.
    • sleep()메서드는 명시된 시간만큼 해당 스레드를 대기시킨다.
    • wait()메서드도 명시된 시간만큼 대기시킨다. sleep()과 다른점은 매개변수인데, 만약 매개변수를 지정하지 않으면 notify()메서드 혹은 notifyAll()메서드가 호출될 때까지 대기한다.
    • join()메서드는 명시된 시간만큼 해당 스레드가 죽기를 기다린다. 만약 아무런 매개변수를 지정하지 않으면 죽을 떄까지 계속 대기한다.
    • interrupt()메서드는 해당 스레드가 Block(대기상태)되거나 특정 상태에서만 동작하므로 항상 특정 스레드의 메서드를 멈출수 있는 것은 아니다.
    • synchronized는 메서드와 블록으로 사용할 수 있다. 절대로 생성자의 식별자로는 사용할 수 없다.
    • synchronized라는 식별자만 쓰면 동기화 할 수 있다.
    • synchronized로 동기화가 필요한 경우
    • 하나의 객체를 여러 스레드에서 동시에 사용 할 경우
    • static으로 선언한 객체를 여러 스레드에서 동시에 사용할 경우
  9. IO에서 발생하는 병목현상
    • New IO = NIO
    • 자바에서 입력과 출력은 스트림(Stream)을 통해서 이루어 진다.
    • 바이트 단위로 읽거나, 문자열 단위로 읽을 때 중요한 것은 한 번 Open한 스트림은 반드시 닫아 주어야 한다. 스트림을 닫지 않으면 나중에 리소스가 부족핼 질 수도 있다.
    • JDK6 까지는 자바에서 파일이 변경되었는지를 확인하기 위해서 File클래스에 있는 lastModified()라는 메서드를 사용했다. JDK7부터는 WatcherService를 사용한다.
  10. 로그는 반드시 필요한 내용만 적자.
    • (튜닝 전 응답 속도 - 튜닝 후 응답 속도) * 100 / 튜닝 후 응답 속도 = 개선률(%)
    • System.out.println()으로 출력하는 로그는 개발할 때만 사용 된다. 운영할 때는 전혀 사용되지 않고, 볼수도 없는 디버그용 로그를 운영서버에 놔두면 안된다.
    • System.out.format()메서드는 C에서 프린트하던 방식으로 처리할 수 있어 소스가 더 간결해진다.
    • 문자열을 사용할 경우에는 %s, int나 long과 같은 정수형을 나타낼 경우에는 %d, float이나 double을 나타낼 경우에는 %f를 사용하면 된다.
    • 자동 줄바꿈이 없으므로 가장뒤에 "\n"을 사용하여 줄바꿈을 해주어야 한다. - 디버그용으로 사용한다면, format 메서드를 사용하기를 권장한다. 더 편리하고 소스의 가독성도 높아지기 떄문이다. 다만 운영시에는 디버그용 로그를 제거할 경우를 가정하고 권하는 것임을 명심하자. - slf4j는 format 문자열에 중괄호를 넣고, 그 순서대로 출력하고자 하는 데이터들을 콤마로 구분하여 전달해준다. 이렇게 전달해 주면 로그를 출력하지 않을 경우 필요 없는 문자열을 더하기 연산이 발생하지 않는다. - LogBack : 예외의 스텍 정보를 출력할 때 해당 클래스가 어떤 라이브러리를 참고하고 있는지도 포함하여 제공하기 떄문에 쉽게 관련된 클래스를 확인할 수 있다. - 자바의 예외 스택 정보는 로그를 최대 100개까지 프린트하기 때문에 서버의 성능에도 많은 부하를 준다.
    • 스택 정보를 가져오는 부분에서는 거의 90%이상이 cpu를 사용하는 시간이고, 나머지 프린트하는 부분에서는 대기시간이 소요된다.
  11. jsp와 서블릿, Spring에서 발생할 수 있는 여러 문제점
    • jsp의 라이프 사이클
    1. jsp Url호출
    2. 페이지 번역
    3. jsp 페이지 컴파일
    4. 클래스 로드
    5. 인스턴스 생성
    6. jspInit 메서드 호출
    7. jspService 메서드 호출 - jsp 페이지가 이미 컴파일되어 있고, 클래스가 로드되어 있고, jsp파일이 변경되지 않았다면, 가장 많은 시간이 소요되는 2~4프로세스는 생략된다. - 서블릿의 라이프 사이클
      • Servlet 객체가 자동으로 생성되고 초기화 되거나,
      • 사용자가 해당 Servlet을 처음으로 호출했을 때 생성되고 초기화 된다. - 서블릿은 JVM에 여러 객체로 생성되지 않는다
      • WAS가 시작하고, ‘사용가능’ 상태가 된 이상 대부분의 서블릿은 JVM에 살아있고, 여러스레드에서 해당 서블릿의 service()메서드를 호출하여 공유한다.
      • 서블릿 클래스에서의 메서드 내에 선언한 지역 변수가 아닌 멤버 변수(인스턴스 변수)를 선언하여 service()메서드를 사용하면 값은 여러 스레드에서 접근하면서 계속 값이 바뀔 것이다.
      • service()메서드를 구현할 때는 멤버 변수나 static한 클래스 변수를 선언하여 지속적으로 변경하는 작업은 피해야 한다. - jsp include
      • 정적인 방식이 빠르다. <%@ include file ="관련 URL" %>
      • 동적인 방식 <jsp:include page = "relative URL"/>
      • 정적인 방식을 사용하면 추가된 jsp와 메인 jsp에 동일한 이름의 변수가 있으면 심각한 오류를 발생할 수 있다. - 자바빈즈 : UI에서 서버 측 데이터를 담아서 처리하기 위한 컴포넌트이다.
      • 한두 개의 자바 빈즈를 사용하는 것은 상관없지만, 10~20개의 자바 빈즈를 사용하면 성능에 영향을 주게 된다. 그러므로 TO(VO)를 만들어 사용하도록 하자 - TAG 라이브러리
      • 태그 라이브러리를 사용하기 위해서는 web.xml 파일을 열어 tld의 URI와 파일 위치를 정의해야 한다.
      • 선언 : <%@ taglib rui = "/taglibURI" prefix="myPreFix" /> - 스프링 프레임워크
      • 스프링의 가장 큰 특징은 복잡한 애플리케이션도 POJO로 개발할 수 있다는 점이다.
      • 스프링을 사용하면 HttpServlet을 확장하지 않아도 웹 요청을 처리할 수 있는 클래스를 만들 수 있다.
      • 스프링의 핵심기술은 Dependency Injection, Aspect Oriented Programming, Portable Service Abstraction으로 함축 할 수 있다.
        • Dependency Injection은 의존성 주입이라고 한다. 객체 간의 의존성 관계를 관리하는 기술이다.
        • AOP는 관점 지향 프로그래밍이라고 한다. 반복 되는 코드를 실제 비지니스로직과 분리할 수 있도록 도와주는 것이 바로 AOP다.
        • PSA는 쉬운 추상화 서비스라고 한다. 비슷한 기술을 모두 아우를 수 있는 추상화계층을 제공하고, 사용하는 기술이 바뀌더라도 비지니스 로직의 변화가 없도록 도와준다.
  12. DB를 사용하면서 발생 가능한 문제점들
    • DataSource가 DB Connection Pool을 포함한다고 생각해 두면 된다. 여기서 유의할 점은 DB Connection Pool은 자바표준으로 지정되어 있는 것이 없다는 것이다.
    • Statement와 PreparedStatement를 사용할떄는 다음과 같은 프로세서를 거친다.
    1. 쿼리 문장분석
    2. 컴파일
    3. 실행
      • Statement를 사용하면 매번 쿼리를 수행할 때마다 1~3단계를 거치게 되고, PreparedStatement는 처음 한 번만 세 단계를 거친 후 캐시에 담아서 재사용을 한다. - Connection, Statement 관련 인터페이스, ResultSet인터페이스에서 close() 메서드를 호출하는 이유는, 자동으로 호출 되기 전에 관련된 DB와 JDBC리소스를 해제하기 위함이다.
      • 0.00001초라도 빨리 닫으면, 그만큼 해당 DB 서버의 부담이 적어지게 된다. - Connection은 대부분 Connection Pool을 사용하여 관리된다. 시스템이 기동되면 지정된 개수만큼 연결하고, 필요할 때 증가시키도록 되어 있다. 증가되는 최대 값 또한 지정하도록 되어 있다. 사용자가 증가해 더 이상 사용할 수 있는 연결이 없으면, 여유가 생길 떄까지 대기한다. 그러다가 어느 정도 시간이 지나면 오류가 발생한다. 그러므로 close() 메서드를 호출하여 연결을 닫아야한다. GC가 될 때까지 기다리면 Connection Pool이 부족해지는 것은 시간문제다. - null로 치환하면 GC의 대상이 되긴한다. 하지만 언제 GC가 될지 모르기 때문에 좋은 방법은 아니다. - ResultSet 객체는 데이터는 갖고 있지 않고, 커서만을 관리하는 객체다. - JDK7부터 autoCloseable 인터페이스를 구현한 클래스라면 try블록이 시작될 때 소괄호 안에서 close() 메서드를 호출하는 객체를 생성해주면 간단하게 처리할 수 있다. - JDBC를 사용하면서 유의할만한 몇 가지 팁
    4. setAutoCommit()메서드는 필요할 때만 사용하자.
    5. 배치성 작업은 excuteBatch()메서드를 사용하자.
    6. setFetchSize()메서드를 사용하여 데이터를 더 빠르게 가져오자.
    7. 한 건만 필요할 때는 한 건만 가져오자.
  13. XML과 JSON도 잘 쓰자.
    • 마크업 언어 : 태그 기반의 텍스트로 된 언어를 의미한다. 태그 안에 필요한 데이터를 추가함으로써 데이터를 전달하거나 보여주는 것이 주 목적이다.
    • SAX는 각 XML의 노드를 읽는 대로 처리하기 떄문에 메모리에 부담이 DOM에 비해서 많지 않다.
    • DOM은 모든 XML의 내용을 읽은 이후에 처리한다. 읽은 XML을 통하여 노드를 추가, 수정, 삭제하기 쉬운 구조로 되어 있다.
    • XML 파일의 사이즈가 클 때 DOM 파서를 사용한다면, OutOfMemoryError가 빈번히 일어날 확률이 높다. - 힙덤프 : 현재 JVM의 힘 메모리에서 점유하고 있는 객체에 대한 정보를 파일로 생성해 놓은 것이다.
    • OutOfMemoryError 발생시 자동으로 힙 덤프를 저장 하도록 하려면 -XX:+HeapDumpOnOutOfMemoryError 옵션을 추가하면 된다. - JSON데이터의 두 가지 구조
    • name/value 형태의 쌍으로 collection 타입
    • 값의 순서가 있는 목록 타입 - Serialize는 데이터를 전송할 수 있는 상태로 처리하는 것을 말하고, Deserialize는 전송 받은 데이터를 사용 가능한 상태로 처리하는 것을 말한다. - Json 데이터는 Serialize와 Deserialize를 처리하는 성능이 XML에 비해 좋지 않다.
  14. 서버를 어떻게 세팅해야 할까?
    • 웹 서버는 반드시 WAS앞단에 두고 정적인 부분을 처리하게 해야한다.
    • Tomcat에서는 AJP Connector라는 웹 서버와 WAS 사이의 컨넥터에 설정한 Backlog라는 값의 영향을 받아 WAS가 응답하지 않을 때 100개의 요청까지 큐에 담아둔다. 이 100개를 넘는 요청들은 503이라는 HTTP헤더 코드 값을 리턴 받게 된다.
    • 웹서버와 웹 브라우저가 연결이 되었을 때 KeepAlive 기능이 켜져 있지 않으면, 매번 HTTP 연결을 맺었다 끊었다 하는 작업을 반복한다.
    • KeepAlive : Keep Alive란 연결된 socket에 IN/OUT의 access가 마지막으로 종료된 시점부터 정의된 시간까지 access가 없더라도 대기하는 구조입니다. 즉 정의된 시간내에 access가 이루어진다면 계속 연결된 상태를 유지할 수 있다는 것 이죠.
    • KeepAlive 설정을 할 때 반드시 같이 해야하는 설정이 있다. 마지막 연결이 끝난 이후에 다음 연결이 될 때까지 얼마나 기다릴지를 지정하는 KeepAlive-timeOut이다. - WAS 서버에 4GB의 여유 메모리가 있다고 하더라도 하나의 인스턴스에 4GB의 메모리를 지정하여 사용하는 것은 굉장히 좋지 않은 방법이다. 왜냐하면 Full GC가 발생할 때마다 많은 시간이 소요될 확률이 커지기 때문이다. 가급적이면 512Mb에서 2Gb사이에서 메모리를 지정하는 것이 좋다.
  15. 안드로이드 개발하면서 이것만은 피하자.
    • 안드로이드는 오라클이나 IBM에서 만든 JVM을 사용하지 않고, Dalvik VM이라는 것을 사용한다.
    • 자바와 문법은 같지만 컴파일러와 가상머신은 다른다 .
    • 윈도우, 맥, 리눅스 장비는 모두 SWAP이라는 메모리 영역을 사용할 수 있다. 하지만 안드로이드는 사용하지 못한다.
      • SWAP : 물리적인 RAM이 부족할 경우 디스크를 메모리처럼 사용하는 것 - 구글에서 말하는 안드로이드 성능 개선 1. 필요 없는 객체 생성을 피하자. 2. static을 적절히 사용하자. 3. 상수에는 static final을 사용하자. 4. 내부에서는 getter와 setter 사용을 피하자. 5. 개선된 for 루프를 사용하자. 6. private한 Inner클래스의 private접근을 피하자. 7. 소수점 연산을 피하자. 8. 라이브러리를 알고 사용하자. 9. Native 메서드는 유의해서 사용하자. - 안드로이드에서 가장 간단히 앱의 성능을 측정하는 방법은 DDMS를 활용하는 것이다 . - 안드로이드에서는 이미지 처리만 잘해도 성능이 좋아진다.
    • 이미지는 크기가 얼마나 되는지 확인해 보자.
    • image View의 setImageResource() 메서드 사용을 자제하자.
  16. JVM은 도대체 어떻게 구동될까?
    • 자바의 HtoSpot은 ‘Java HotSpot Perfomance Engine’이다.
    • 자바를 만든 SUN에서 자바의 성능을 개선하기 위해서 Just In Time(JIT)컴파일러를 만들고 이름을 HotSpot으로 지었다.
    • JIT 컴파일러는 프로그램의 성능에 영향을 주는 지점에 대해서 지속적으로 분석한다.
    • HotSpot은 세 가지 주요 컴포넌트로 되어있다 .
      • VM 런타임
      • JIT 컴파일러
      • 메모리 관리자
    • JIT를 사용한다는 것은 ‘언제나 자바 메서드가 호출되면 바이트 코드를 컴파일하고 실행 가능한 네이티브 코드로 변환한다’는 의미다. 하지만, 매번 JIT로 점파일을 하면 성능 저하가 심하므로, 최적화 단계를 거친다.
    • HotSpot VM에서 컴파일할 대상을 정할 때 각 메서드에 있는 두 개의 카운터를 이용한다.
      • 수행 카운터 : 메서드를 시작할 때마다 증가
      • 백에지 카운터 : 높은 바이트 코드 인덱스에서 낮은 인덱스로 컨트롤 흐름이 변경될 때마다 증가. 수행 카운터보다 컴파일 우선순위가 높다.
    • 카운터들이 인터프리터에 의해서 증가될 때마다, 그 값들이 한계치에 도달했는지 확인하고, 도달했을 경우 인터프리터는 컴파일을 요청한다.
    • HotSpot VM은 OSR이라는 특별한 컴파일도 수행한다.
      • OSR은 인터프리터에서 수행한 코드 중 오랫동안 루프가 지속되는 경우에 사용된다.
      • 코드의 컴파일이 완료된 상태에서 최적화되지 않은 코드가 수행되는 것을 발견한 경우에 인터프리터에 계속 머무르지 않고 컴파일된 코드로 변경한다. - JIT를 사용하면 시작할 때의 성능은 느리겠지만, 지속적으로 수행할 때는 더 빠른 처리가 가능하다.
    • 모든 메서드를 컴파일하고 최적화하는 작업은 JVM 시작시간을 느리게 만들기 때문에 시작할 때는 모든 메서드를 최적화하지 않는다.
    • IBM JVM의 JIT 컴파일 방식 5개
      1. 인라이닝 : 호출된 메서드가 단순할 경우 그 내용이 호출한 메서드의 코드에 포함해 버린다.
      2. 지역 최적화 : 작은 단위의 코드를 분석하고 개선하는 작업을 수행
      3. 조건 구문 최적화 : 메서드 내의 조건 구문을 최적화하고, 효율성을 위해서 코드의 수행 경로를 변경한다.
      4. 글로벌 최적화 : 메서드 전체를 최적화하는 방식이다. 매우 비싼 방식이지만 성능개선이 많이 된다.
      5. 네이티브 코드 최적화 : 플랫폼 아키텍처에 의존적이다. 아키텍처에 따라서 최적화 방식이 달라진다. - 컴파일이라는 작업은 상위 레벨의 언어로 만들어진 코드를 기계에 의존적인 코드로 변환하는 것을 말한다. - 클래스 로딩 절차
        1. 주어진 클래스의 이름으로 클래스 패스에 있는 바이너리로 된 자바 클래스를 찾는다.
        2. 자바 클래스를 정의한다.
        3. 해당 클래스를 나타내는 java.lang 패키지의 Class 클래스의 객체를 생성한다.
        4. 링크 작업이 수행된다. 이 단계에서 static 필드를 생성 및 초기화하고, 메서드 테이블을 할당한다.
        5. 클래스의 초기화가 진행되며, 클래스의 static 블록과 static 필드가 가장 먼저 초기화 된다. 당연한 이야기지만, 해당클래스가 초기화 되기 전에 부모 클래스의 초기화가 먼저 이루어진다.
    • 요약 : loading -> linking -> linitializing - 대표적인 클래스 로딩 에러
    • NoclassDefFoundError : 클래스 파일을 찾지 못한 경우
    • ClassFormatError : 클래스 파일의 포맷이 잘못된 경우
    • UnsupportedClassVersionError : 상위 버전의 JDK에서 컴파일한 클래스를 하위 버전의 JDK에서 실행하려고 하는 경우
    • ClassCirculariyError : 부모 클래스를 로딩하는 데 문제가 있는 경우(자바는 클래스를 로딩하기 전에 부모 클래스들을 미리 로딩해야 한다.)
    • IncompatibleClassChangeError : 부모 클래스인데 implements를 하는 경우나 부모가 인터페이스인데 extends하는 경우
    • VerifyError : 클래스 파일의 semantic, 상수 풀, 타입 등의 문제가 있을 경우 - JVM은 자바 언어의 제약을 어겼을 때 예외(Exception)라는 시그널로 처리한다.
  17. 도데체 GC는 언제 발생할까?
    • 자바의 메모리 영역은 Heap메모리와 Non-heap메모리로 나뉜다.
    • Heap 메모리 : 클래스 인스턴스, 배열이 이 메모리에 쌓인다. 이 메모리는 공유 메모리라고도 불리우며 여러 스레드에서 공유하는 데이터들이 저장되는 메모리다.
    • Non-heap 메모리 : 자바의 내부처리를 위해서 필요한 영역이다. 여기서 주된 영역이 바로 메서드 영역이다.
      • 메서드 영역 : 메서드 영역은 모든 JVM 스레드에서 공유한다. (런타임 상수 풀, 메서드 데이터, 메서드, 생성자 코드)
      • JVM 스택 : 스레드가 시작할 때 JVM스택이 생성된다. 메서드가 호출되는 정보인 프레임이 저장된다. 그리고 지역변수와 임시결과, 메서드 수행과 리턴에 관련된 정보도 포함된다.
      • 네이티브 메서드 스택 : 자바코드가 아닌다른 언어로된 코드들이 실행하게 될 때의 스택 정보를 관리한다.
      • PC레지스터 : 자바 스레드들은 각자의 pc(program Counter) 레지스터를 갖는다. 자바의 코드들이 수행될 때 JVM의 인스트럭션 주소를 pc레지스터에 보관한다.
    • 스택의 크기가 최대치를 넘어서면 StackOverFlowError가 발생하고 스레드를 생성할때 메모리가 부족한경우 OutOfMemoryError가 발생한다. - GC 작업을 하는 가비지 콜렉터는 다음의 역할을 한다.
    • 메모리 할당
    • 사용중인 메모리 인식
    • 사용하지 않는 메모리 인식 - 행(Hang) : 서버가 요청을 처리 못하고 있는 상태를 의미한다.

| Old 영역 <th colspan=3>Young 영역</span> | |:-:|:-:|:-:|:-:| |메모리 영역 | Eden | Survivor 1 | Survivor 2 |

  • 자바의 메모리 영역
    • 메모리에 객체가 생성되면, Young영역의 가장 왼쪽인 Eden영역에 객체가 지정된다.
    • Eden 영역에 데이터가 꽉 차면, 이 영역에 있던 객체가 Survivor영역으로 옮겨지거나 삭제 된다.
    • Young 영역에서 Old영역으로 넘어가는 객체중 Survivor 영역을 거치지 않고 ㅏㅂ로 Old영역으로 이동하는 객체가 있을 수 있다. 객체의 크기가 Survivor영역보다 큰 객체는 바로 Old영역으로 이동한다.
  • GC의 종류
    • 마이너 GC : Young 영역에서 발생하는 GC
    • 메이저 GC : Old 영역이나 Perm영역에서 발생하는 GC
  • GC의 5가지 방식
    1. 시리얼 콜렉터 : Young영역과 Old영역이 시리얼하게 처리되며 하나의 Cpu를 사용한다. 이 처리를 수행할 때를 Stop-the-world라고 하며 콜렉션이 수행될 때 애플리케이션이 정지한다.
    2. 병렬 콜렉터 : 다른 CPU가 대기 상태로 남아 있는 것을 최소화한다. Young영역에서의 콜렉션을 병렬로 처리한다. 많은 CPU를 사용하기 때문에 GC의 부하를 줄이고 애플리케이션의 처리량을 증가시킨다.
    3. 병렬 콤팩팅 콜렉터 : 병렬콜렉터와 다른 점은 Old영역 GC에서 새로운 알고리즘을 사용한다.
    4. CMS 콜렉터 : 로우 레이턴시 콜렉터로도 알려져 있으며, 힙 메모리 영역의 크기가 클때 적합하다.
    5. G1 콜렉터 : Garage First는 지금까지의 Garbage Collector와는 다른 영역으로 구성되어있다. 바둑판 같은 모양의 구역이 각각 Eden, Survivor, Old영역의 역할을 변경해 가면서 하고 Humongous라는 영역도 포한된다. G1은 CMS GC의 단점을 보완하기 위해서 만들어졌으며 성능도 매우 빠르다.
  • 강제 GC
    • System.gc()메서드나 Runtime.getRuntime().gc()메서드를 쓰면된다.
    • 개발자가 코드에 사용하면 안되고, 특히 웹기반의 시스템에서는 절대로 사용하지 말것을 권한다.
  1. GC가 어떻게 수행되고 있는지 보고 싶다.
    • jps는 해당 머신에서 운영 중인 JVM의 목록을 보여준다.
    • jstat는 GC가 수행되는 정보를 확인하기 위한 명령어다.
    • jstatd는 원격으로 JVM상황을 모니터링 할 수 있다.
    • verbosegc 옵션을 이용하면 GC로그를 남길 수 있다.
    • 메모리 릭 여부는 Full GC가 발생한 이후의 메모리 사용량으로 판단해야 한다.
  2. GC 튜닝을 항상 할 필요는 없다.
    • JVM의 메모리 크기도 지정하지 않았고, TimeOut이 지속적으로 발생하는 경우에 GC 튜닝을 하는 것이 좋다.
    • 운영하고 만드는 시스템이 GC를 적게 하도록 하려면 객체생성을 줄이는 작업을 먼저 해야 한다.
    • GC 튜닝의 절차
    1. GC 상황 모니터링
    2. 모니터링 결과 분석 후 GC 튜닝 여부 결정
    3. GC 방식/메모리크기 지정
    4. 결과분석
    5. 결과가 만족스러울 경우 전체 서버에 반영 및 종료 - GC가 수행되는 시간을 확인했을 때 다음의 조건에 모두 부합한다면 GC튜닝이 필요없다.
      • Minor GC의 처리 시간이 빠르고, 주기가 빈번하지 않다.
      • Full GC의 처리시간이 빠르고, 주기가 빈번하지 않다. - 메모리 크기는 JVM의 시작 크기(-Xms)와 최대 크기(-Xmx)를 말한다.
      • 메모리의 크기가 크면, GC는 길게 가끔 한다.
        • GC의 발생 횟수는 감소한다.
        • GC 수행 시간은 길어진다.
      • 메모리의 크기가 작으면, GC는 짧게 자주 한다.
        • GC의 발생 횟수는 짧아진다.
        • GC 수행 시간은 증가한다.
  3. 모니터링 API인 JMX
    • JMX : Java Management Extensions
    • WAS를 모니터링 하기 위한 기술 중 서버의 데이터를 가장 많이 확인할 수 있는 것이 JMX이다.
    • 모든 모니터링 툴이 그렇듯이, 모니터링 툴로 인한 부하는 절대 무시할 수 없다.
    • 서버의 CPU 및 메모리, 네트워크 리소스에 여유가 없는 상황에서는 주의해서 사용해야 한다.
  4. 반드시 튜닝해야 하는 대상은?
    • 파레토 법칙? 20:80법칙
    • 포틀릿? 웹 포탈 유저들이 사용할 수 있는 사용자 인터페이스 컴포넌트이다. 유저가 마음대로 위치 수정 또는 추가, 삭제 할 수 있다.
    • 성능을 분석할 대상을 정할 때, 모든 화면을 대상으로 하는 것은 상당히 무모한 행동이다.(자주 사용하는 화면/기능 위주의 튜닝 필요)
  5. 어떤 화면이 많이 쓰이는지 알고 싶다.
    • 초단위
    • 밀리초 : 1/1,000 초
    • 마이크로초 : 1/1,000,000 초
    • 나노초 : 1:1,000,000,000 초 - 추천 무료 웹 로그 분석 툴
    • AWStats
  6. 튜닝 절차는 그때그때 달라요.
    • 암달법칙? 1Core에서 20시간 소요되는 작업 중 1시간은 절대 개선할 수 없다면, 20배 이상의 성능 향상은 불가능 하다.
    • 성능 튜닝 step by step
    1. 원인 파악 : 성능 저하가 발생하는 원인을 파악하는 단계
    2. 목표 설정 : 성능 튜닝의 목표를 설정하는 단계
    3. 튜닝 실시 : 코드를 최적화하며 튜닝하는 단계
    4. 개선율 확인 : 튜닝 실시한 후 얼마나 개선되었는지를 확인하는 단계
    5. 결과 정리 및 반영 : 튜닝한 결과들을 정리하고, 실제 운영되는 시스템에 반영하는 단계 - 성능 튜닝 비법
      • 하나만 보지 말아라.
      • 큰 놈을 없애라.
      • 깊게 알아야 한다.
      • 결과 공유는 선택이 아닌 필수다.
  7. 애플리케이션에서 점검해야 할 대상들
    • 패턴과 아키텍처
    • 너무 많은 패턴을 사용하지 않았는가?
    • 데이터를 리턴할 때 TO(VO) 패턴을 사용하였는가?
    • 서비스 로케이터 패턴은 적용이 되어 있는가?
      • 서비스 로케이터 패턴? 애플리케이션에서 필요한 대상을 찾는 룩업 작업을 할때 소요되는 대기시간을 줄일 수 있다. - 기본적인 애플리케이션 코딩
    • 명명 규칙은 잘 지켰는가?
    • 필요한 부분에 예외 처리 되어 있는가?
    • 예외 화면은 지정되어 있는가?
    • 예외 정보를 혹시 e.printStackTrace()로만 처리하고 있지 않은가?
    • System.gc() 메서드가 소스에 포함되어 있지 않은가?
    • System.exit() 메서드가 소스에 포함되어 있지 않은가?
    • 문자열을 계속 더하도록 코딩하지는 않았는가?
    • StringBuffer나 StringBuilder 클래스도 제대로 사용했는가?
    • 무한 루프가 작동할 만한 코드는 없는가?
    • static을 남발하지 않았는가?
    • 필요한 부분에 synchronized블록을 사용하였는가?
    • IO가 계속 발생하도록 계발되어 있지는 않은가?
    • 필요없는 로그는 다 제거했는가?
    • 디버그용 System.out.println()은 다 제거 했는가? - 웹 관련 코딩
    • JSP의 include는 동적으로 했는가? 아니면 정적으로 했는가?
    • 자바 빈즈는 너무 많이 사용하지 않았나?
    • 태그 라이브러리는 적절하게 사용했나?
    • EJB는 적절하게 사용하였나?
      • Enterprise Java Bean? 서버측 컴포넌트모델, 비지니스 로직담당, EJB컨테이너에서 운영
    • 이미지 서버를 사용할 수 있는 환경인가?
    • 사용 중인 프레임워크는 검증되었는가? - DB관련
    • 적절한 JDBC 드라이버를 사용하는가?
    • DB Connection, Statement, ResultSet은 잘 닫았는가?
    • DB Connection Pool은 잘 사용하고 있는가?
    • 자동 커밋 모드에 대한 고려는 하였는가?
    • ResultSet.last() 메서드를 사용하였는가?
    • PreparedStatements를 사용하였는가? - 서버 설정
    • 자바VM 관련 옵션들은 제대로 설정되어 있는가?
    • 메모리는 몇 MB로 설정해 놓았는가?
    • GC 설정은 어떻게 되어 있는가?
    • 서버가 운영모드인지 개발모드 인지 확인하였는가?
    • WAS의 인스턴스가 몇개 기동되고 있는가?
    • JSP Precompile 옵션은 지정해 놓았는가?
    • DB Connection Pool 개수와 스레드 개수는 적절한가?
    • 세션 타임아웃시간은 적절한가
    • 검색서버가 있다면, 검색 서버에 대한 설정 및 성능 테스트를 하였는가? - 모니터링
    • 웹 로그(Access log)는 남기고 있는가?
    • verbosegc 옵션은 남기고 있는가?
    • 각종 로그 파일에 대한 규칙은 있는가?
    • 서버의 시스템 사용률은 로그로 남기고 있는가?
    • 모니터링 툴은 사용중인가?
    • 모니터링 툴에 대한 설정은 적절하게 되어 있는가?
    • 서버가 갑자기 코어 덤프를 발생시키지 않는가?
      • 코어덤프? 특정시점에 작업중이던 메모리 상태를 기록한 것, 보통 프로그램이 비정상적으로 종료했을 때 만들어진다.
    • 응답 시간이 너무 느리지 않은가?
  • 지나가는 말
    • 책을 사서 읽고 실습을 꼭 해보자.
      • 유용한 내용이 많았다.