IT_Programming/Java

자바의 성능을 결정 짓는 코딩 습관과 튜닝 이야기 정리 1 ~ 2

JJun ™ 2009. 10. 10. 10:51

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

 

1. 디자인 패턴을 사용해야 한다.

 

MVC 모델이란?

V(View) 사용자가 결과를 보거나 입력을 할 수 있는 화면

C(Controller) 중간에서 연결해주는 연결자 역활

M(Model) 실제 기능에 대한 비지니스 로직

 

모델1 : JSP에서 자바빈을 호출하고 데이터베이스에서 정보를 조회,등록,수정,삭제 업무를 한 후

           결과를 브라이저로 보내 주는 방식이다. 간단하게 개발할 수 있는 장점이 있지만,

           유지보수의 문제가 있다는 단점이있다.

  

모델2 : 모델1의 단점을 해결하기 위해서 나온 상위의 모델이다. 모델2는 MVC 모델을 정확하게 따른다.

            요청을 DB로 하는것이 아니라 서블릿으로 하고 서블릿이 연결을 하는 역활을 한다.

 

 

 

꼭 MVC 모델 2를 써야 하나?

적은 개발 인원으로 단기간에 할때는 모델1과 모델2의 큰 차이가 존재하지 않는다.

하지만, 개발자가 많거나 혹은 유지보수가 빈번히 발생하는 곳에서는 문제가 심각하다.

3000라인이 넘는 JSP 소스에서 중복 호출 되는 경우도 발생하며, 그 위치를 파악하는것도 어렵다.

왜냐하면 페이지의 가독성이 떨어지기 때문이다.

 

 

디자인 패턴이란?

: 시스템을 만들기 위해서 전체 중 일부 의미 있는 클래스들을 묶은 각각의 집합을 디자인 패턴이라고 한다.

 

아래 그림은 Core J2EE Patterns이다. 

 

 

Intercepting Filter : 요청 타입에 따른 다른 처리를 하기 위한 패턴

Front Controller : 요청 전후에 처리하기 위한 컨트롤러를 지정하는 패턴

View Helper : 프리젠테이션 로직과 상관 없는 비지니스 로직을 핼퍼로 지정하는 패턴

Composite View : 최소 단위의 하위 컴포넌트를 분리하여 화면을 구성하는 패턴

Service to Worker : Front Controller & View Helper 사이에 디스패치를 두어 조합하는 패턴

Dispatcher View : Front Controller & View Helper로 디스패쳐 컴포넌트를 형성한다.

                          뷰 처리가 종료될 때까지 다른 활동을 지연한다는 점이 Service to Worker 패턴과 다른다.

Business Delegate : 비지니스 서비스 접근을 캡슐화하는 패턴

Session Facade : 비지니스 티어 컴포넌트를 캡슐화하고, 원격 클라이언트에서 접근할 수 있는 서비스를

                          제공하는 패턴

Composite Entity : 로컬 엔티티 빈과 POJO를 이용하여 큰 단위의 엔티티 객체를 구현

Transfer Object : 일명 Value Object 패턴이라고 알려져 있음. 데이터를 전송하기 위한 객체에 대한 패턴

Transfer Object Assembler : 하나의 Transfer Object로 모든 데이터를 처리할 수 없으므로,

                                         여러 Transfer Object를 조합하거나 변형한 객체를 생성하여 사용하는 패턴

Value List Handler : 데이터 조회를 처리하고, 결과를 임시 저장하며, 결과 집합을 검색하여 필요한 항목을

                             선택하는 역활을 수행한다.

Data Access Object : 일명 DAO라고 알려져 있음, DB에 접근을 전담하는 클래스를 추상화하고 캡슐화한다.

Service Activator : 비동기적 호출을 처리하기 위한 패턴

 

  • Service to Worker 와 Dispatcher View 의 비슷하여 혼동할수 있으나, Dispatcher View는 직접 Helper 클래스를 직접 컨트롤 하지 않는다.

 

** Transfer Object

  1. package com.perf.pattern;
    import java.io.Serializable;
    public class EmployeeTo implements Serializable
    {
        private String empName;
        private String empID;
        private String empPhone;
        public EmployeeTo()
        {super();}
        public EmployeeTo( String empName, String empID, String empPhone)
        {
            super();
            this.empName = empName;
            this.empID = empID;
            this.empPhone = empPhone;
        }
        public String getEmpID()
        {
            return empID;
        }
        public void setEmpID( String empID)
        {
            this.empID = empID;
        }
        public String getEmpName()
        {
            if( empName==null) return "";
            else return empName;
        }
        public void setEmpName( String empName)
        {
            this.empName = empName;
        }
        public String getEmpPhone()
        {
            return empPhone;
        }
        public void setEmpPhone( String empPhone)
        {
            this.empPhone = empPhone;
        }
        public String toString()
        {
            StringBuilder sb = new StringBuilder();
            sb.append("empName=").append(empName).append("empID=").append(empID)
            .append("empPhone=").append(empPhone);
            return sb.toString();       
        }
    }

* toString()을 구현해야 하는 이유는  EmployeeTO객체의 toString()을 수행하면 알 수 없는 값을 리턴한다.

   그래서 구현이 필요하다.

 
참고)  Serializable를 구현한 이유는 객체를 직렬화 할수 있기때문이다.
         결국 서버사이의 데이터 전송이 가능해진다.

    이 패턴의 사용 이유는 월등한 성능항샹보다는 하나의 객체에 결과값을 담아올 수 있어서 두세번 요청하는

    일이 줄어든다.

     

     

    **Service Locator

    1. package com.perf.pattern;
      import java.util.Collections;
      import java.util.HashMap;
      import java.util.Map;
      import javax.ejb.*;
      import javax.naming.InitialContext;
      import javax.naming.NamingException;

      public class ServiceLocator
      {
          private InitialContext ic;
          private Map cache;
         
          private static ServiceLocator me;
          static{
              me = new ServiceLocator();
          }
          private ServiceLocator()
          {
              cache = Collections.synchronizedMap( new HashMap());
          }
         
          public InitialContext getInitialContext() throws Exception{
              try
              {
                  if( ic == null)
                  {
                      ic = new InitialContext();
                  }
              }
              catch( Exception ex)
              {
                  throw ex;
              }
              return ic;
          }
         
          public static ServiceLocator getInstance()
          {
              return me;
          }
          public EJBLocalHome getLocalHome( String jndiHomeName) throws Exception
          {
              EJBLocalHome home = null;
              try
              {
                  if(cache.containsKey( jndiHomeName))
                  {
                      home = (EJBLocalHome);
                      cache.get( jndiHomeName);
                  }
                  else
                  {
                      home = (EJBLocalHome);
                      getInitialContext().lookup(jndiHomeName);
                      cache.put(jndiHomeName, home);
                  }
              }
              catch( NamingException ne)
              {
                  throw new Exception( ne.getMessage());
              }
              catch( Exception ex)
              {
                  throw new Exception( ex.getMessage());
              }
              return home;
          }
      }

    Service Locator는 가장 빈번히 사용되는 EJB Home 객체나 DB의 Datasource를 찾을때 소요되는

    응답속도를 감소시키기 위해서 사용된다.

    EJB home 객체를 찾거나 Datasource객체를 찾는데 일반적으로 30ms가 소요된다.

    하지만 메모리에서 찾으면 0.01ms도 소요되지 않으므로, 적어도 100배 이상의 성능향상을 가져올수 있다.

       

      ** 추가적으로 Business Delegate, Session Facade, Data Access Object, Service Locator,

          Transfer Object는 더 학습하여 별첨한다.

       

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

       

      2. 도대체 GC는 언제 발생할까?

       

      GC란?

      Garbage Collection. 말 그대로 쓰레기를 정리하는 작업이다. 자바에서 쓰레기는 객체이다.

      하나의 객체가 메모리를 점유하고, 필요하지 않으면 메모리에서 해제되어야 한다.


      GC의 원리

      GC작업을 하는 가비지 콜렉터는 다음의 역할을 한다.

      • 메모리 할당
      • 사용중인 메모리 인식
      • 사용하지 않는 메모리 인식

       

      사용하지 않는 메모리를 인식하는 작업을 수행하지 않으면, 할당된 메모리 영역이 꽉차서

      WAS에 행(hang)이 걸리거나, 더 많은 메모리를 할당하려는 현상이 발생할 것이다.

      사용 가능한 메모리 영역이 없는데 계속 메모리 할당하려고 하면 OutOfMemoryError가 발생하면서

      WAS가 다운 된다.

       

      JVM의 메모리는 크게 클래스 영역, 자바스택, 힙, 네이티브 메소드 스택의 4가지 영역으로 나눠진다.

      메모리 영역인 힙 영역에 대해서만 생각하자. 

        1) Permanent Space : JVM 클래스와 메소드 개체를 위해 쓰인다.
        2) Old Object Space : New 영역에서 count를 세어서  어느정도 증가된(만들어진지 좀 된)

                                           개체를 위해 쓰인다.
        3) New(Young) Object Space : 새로 생성된 개체들을 위해 쓰인다

       

      일단 메모리에 객체가 생성되면, 

      1. Eden 영역에 객체가 지정

      2. Eden 영역에 데이터가 어느정도 싸이면 폐기 객체들이 Survivor 영역으로 이동된다.

      3. Survivor 영역의 우선순위는 없으며, 이 두 영역중 한 영역은 반드시 비어 있어야 하며, 그 영역에 Eden영역의 객체가 할당 된다.

      4. Survivor 1 혹은 2의 영역이 차면 GC가 되면서 Eden 영역에 있는 객체가 빈 Survivor 영역으로 이동한다.

      5. 그러다가 더 큰 객체가 생성되거나, New(Youn)영역에 공간이 없을 경우 Old 영역으로 이동한다.

       

      GC의 종류

      1. 마이너 GC : Young 영역에서 발생하는 GC

      2. 메이져 GC : Old 영역이나 Perm 영역에서 발생하는 GC

      • GC가 발생하거나 객체가 각 영역에서 다른 영역으로 이동할 때 애플리케이션의 병목이 발생하면서 성능에 영향을 주게 된다.

      GC의 방식

      1. Serial Collector(시리얼 콜렉터)

        Young 영역과 Old 영역이 시리얼하게(연속적으로) 처리되며 하나의 CPU를 사용한다.                   콜렉션이 수행될 때 애플리케이션 수행이 정지 된다. 일단 살아 있는 객체는 Eden 영역에 있다.    Eden 영역이 꽉차면 To Survivor 영역으로 살아 있는 객체가 이동한다. 이때 Survivor에 들어가기에 큰 객체는 바로 Old 영역으로 이동한다. To Survivor 영역이 꽉 찼을 경우, Eden 영역이나 From Survivor 영역에 남아 있는 객체들은 Old 영역으로 이동한다.

        이후 Old 영역이나 Perm영역에 있는 객체들은 Mark-sweep-compact 콜렉션 알고리즘을 따른다.      일반적으로 클라이언트 종류의 장비에서 많이 사용하는 방식이다.
        Mark-sweep-compact                                                                                                         1. Old 영역으로 이동된 객체들 중 살아 있는 객체를 식별한다.
        2. Old 영역의 객체들을 훑는 작업을 수행하여 쓰레기 객체를 식별한다.
        3. 필요 없는 객체들을 지우고 살아 있는 객체들을 한곳으로 모은다.

      2. Paraller Collector(병렬 콜렉터)

        이방식의 목표는 CPU가 대기 상태로 남아 있는 것을 최소화하는 것이다. Young 영역에서 콜렉션을 병렬로 처리한다. 많은 CPU를 사용하기 때문에 GC의 부하를 줄이고 애플리케이션의 처리량을 증가시킬 수 있다. Old 영역의 GC는 Mark-sweep-compact 콜렉션 알고리즘을 사용한다. 

         

      3. Paraller Compacting Collector(병렬 컴팩팅 콜렉터)

        이 방식은 JDK5.0 Update 6 부터 사용 가능함. 병렬 콜렉터와 다른 점은 Old 영역의 GC에서 새로운 알고리즘을 사용한다. 결국 Young영역에 대한 GC는 병렬 콜렉터와 동일하다.

        병렬콜렉터와 동일하게 이방식도 여러 CPU를 사용하는 서버에서 적합하다.

        Old 영역의 GC는 다음 3단계를 거친다.
        표시단계 : 살아 있는 객체를 식별하여 표시해 놓는 단계
        종합단계 : 이전에 GC를 수행하여 컴팩션된 영역에 살아 있는 객체의 위치를 조사하는 단계
        컴팩션 단계 : 컴팩션을 수행하는 단계. 수행 이후에는 컴팩션된 영역과 비어있는 영역으로 나뉜다.

      4. Concurrent Mark-Sweep(CMS) Collector(CMS 콜렉터)

        이 방식은 로우 레이턴시 콜렉터(low-latency collector)로도 알려져 있으며, 힙 메모리의 크기가 클 경우 적합하다. Young 영역은 병렬콜렉터와 동일하며 Old 영역은 아래와 같다.

           CMS는 컴패션 단계를 거치지 않기 때문에 왼쪽으로 메모리를 몰아 놓는 작업을 수행하지 않는다.

      결국 자바 프로그램에서 GC는 아무때나 하는것이 아니라, 각 영역의 할당된 크기의 메모리가

      허용치를 넘을 때 수행한다는것과 개발자가 컨트롤할 영역이 아니라는것은 확실함

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

      memorymanagement_whitepaper.zip
      0.22MB