콘텐츠로 이동

2022 11 12

2022-11-12

빈 스코프

  • 빈 스코프 종류
    • Singleton : 스프링 IoC 컨테이너당 하나의 인스턴스만 사용
    • Prototype : 매번 새로운 빈을 정의하여 사용
    • Request : HTTP 라이프사이클 마다 한 개의 빈을 사용
    • Session : HTTP 세션마다 한 개의 빈을 사용
    • Application : ServletContext 라이프사이클 동안 한 개의 빈을 사용
    • Websocket : Websocket 라이프사이클 안에서 한 개의 빈을 사용
  • Prototype 스코프를 사용하는 방법

    • 정의하는 법
      @Component
      @Scope(value = "prototype")
      public class ProtoType {
      }
      
    • 근데... 싱글톤 빈의 컴포넌트로 Prototype 스코프 빈을 쓰면 어쩌지?
      • 싱글톤 스코프의 빈이 프로토타입 빈을 주입받으면 싱글톤 프로토타입 빈은 매번 바뀌지 않고 같은 빈이 쓰임
        @Component
        public class Single {
            @Autowired
            ProtoType protoType;
        }
        
        @Component
        @Scope(value = "prototype")
        public class ProtoType {
        }
        
    • 프록시 모드로 해결하기!
      • ApplicationContext가 빈을 처음 생성시, proto 빈을 주입받지 않고,
      • proto 클래스 상속받은 프록시를 빈으로 등록해
      • 프록시에서 내부적으로 매번 새로운 proto 빈을 사용하게 끔 설계됨
        @Component
        @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
        public class ProtoType {
        }
        

스프링 사용자들의 주입 방식

@ComponentScan

  • ComponentScan 하는 방법
    1. xml 파일에 설정
      • <context:component-scan base-package="com.rcod.lifelog"/>
      • base package 기준으로 클래스들을 스캔하여 빈으로 등록 가능
      • include-filter, exclude-filter 등을 통해 특정 객체 포함/제외 설정 가능
    2. 자바 파일 안에서 설정
      • 특정 패키지를 basePackage로 잡고 ComponentScan을 통한 빈 등록
        @Configuration
        @ComponentScan(basePackages = "com.rcod.lifelog")
        public class ApplicationConfig {
        }
        
  • ComponentScan 동작 과정
    1. ConfigurationClassParser가 @Configuration 클래스를 파싱
    2. ComponentScan 설정을 파싱
      • base-package에 명시된 패키지를 기준으로 ComponentScanAnnotationParser가 스캔하기 위한 설정 파싱
    3. base-pacakge 설정을 바탕으로 모든 클래스를 로딩
    4. ClassLoader가 로딩한 클래스들을 BeanDefinition으로 정의
    5. 생성할 빈에 대한 정의를 토대로 빈 생성
  • ComponentScan 동작 원리
    • @ComponentScanBeanFactoryPostProcessor를 구현한 ConfigurationClassPostProcessor에 의해 동작
    • 컴포넌트 스캔을 해서 Bean으로 등록해줌
    • vs @Autowired
      • @Autowired는 등록된 다른 Bean을 찾아 BeanPostProcessor의 구현체를 적용해 의존성 주입을 적용함
      • @ComponentScan은 다른 Bean들을 찾아 BeanFactoryPostProcessor의 구현체를 적용해 Bean을 등록시켜줌

어플리케이션 컨텍스트

  • 참고: https://mangkyu.tistory.com/151
  • 어플리케이션 컨텍스트
    • 빈의 생성과 관계 설정 등의 제어를 담당하는 IoC 컨테이너인 빈 팩토리가 존재
    • 빈의 생성/관계 설정 외에 추가적인 기능이 필요! Spring에서는 빈 팩토리를 상속받아 확장한 어플리케이션 컨텍스트를 사용함
    • 어플리케이션 컨텍스트는...
      • 별도의 설정 정보 참고
      • IoC 적용하여 빈의 생성, 관계 설정 등의 제어 작업 총괄
      • 직접 오브젝트를 생성하고 관계를 맺어주는 코드가 없고, 그런 생성 정보와 연관관계 정보에 대한 설정을 읽어 처리함
    • 생성 정보, 연관관계 정보에 대한 설정을 참고하여 처리함 => @Configuration
  • 클라이언트의 Bean 요청 시 처리 과정
    1. ApplicationContext는 @Configuration이 붙은 클래스를 설정 정보로 등록해두고, @Bean이 붙은 메서드의 이름으로 빈 목록 생성
    2. 클라이언트가 해당 빈 요청
    3. ApplicationContext는 자신의 빈 목록에서 요청한 이름이 있는지 찾음
    4. ApplicationContext는 설정 클래스로부터 빈 생성을 요청, 생성된 빈을 돌려줌
  • 어플리케이션 컨텍스트의 장점
    • 클라이언트는 @Configuration이 붙은 구체적인 팩토리 클래스를 알 필요가 없음
      • 원하는 객체를 가져오려면 어떤 팩토리 클래스에 접근해야 하는지 알아야하는데...
      • 어플리케이션 컨텍스트를 사용하면 팩토리가 아무리 많아져도 직접 접근할 필요 X
    • 어플리케이션 컨텍스트는 종합 IoC 서비스를 제공해줌
      • 객체 생성과 관계 설정이 전부가 아님...
      • 객체가 만들어지는 방식/시점/전략 등을 다르게 가져갈 수 있고,
      • 그 외에도 후처리나 정보의 조합 인터셉트 등과 같은 다양한 기능이 존재
    • 어플리케이션 컨텍스트를 통한 다양한 빈 검색 방법을 제공할 수 있음
      • 어플리케이션 컨텍스트에서 빈 목록을 관리하여 빈의 이름이나 타입, 어노테이션 설정 등으로 빈을 찾을 수 있음
      • dependency lookup!
  • Spring에서의 Singleton
    • Spring에서 싱글톤
      • 여러번 빈을 요청하더라도 매번 동일한 객체
      • 대규모 트래픽 처리할 수 있도록 하기 위함
        • 대규모 트래픽 처리하려고 Controller, Service, Repository 등 나뉘게 됨
      • 매번 클라이언트에서 요청이 올 때 마다 빈 새로 만들면 부하 감당 빡셈
      • 빈을 싱글톤 스코프로 관리하여 1개의 요청 왔을 때 여러 쓰레드가 빈을 공유해 처리하도록 하였음
  • Spring Singleton vs Java Singleton
    • Java Singleton의 단점
      • private 생성자를 가져 상속이 불가능
      • 테스트하기 힘듦
      • 서버 환경에서는 싱글톤이 1개만 생성됨을 보장하지 못함
      • 전역 상태를 만들 수 있기에 객체지향적이지 못함
    • Spring은 직접 싱글톤 형태의 오브젝트 만들고 관리하는 기능을 제공 => 싱글톤 레지스트리
      • 싱글톤 생성하고, 관리하고, 공급하는 컨테이너
      • static 메서드나 private 생성자 등을 사용하지 않아 객체지향적 개발을 할 수 있음
      • 테스트 하기 편함!
    • Spring singleton은 내부에 상태를 갖지 않는 무상태 방식으로 만들 것!

JVM memory leak

GC와 Java Reference

  • GC와 Reachability
    • Reachability라는 개념...
      • 유효한 참조 O => reachable
      • 유효한 참조 X => unreachable
    • 객체는 참조 사슬을 이룸
      • 유효한 참조 여부를 파악하려면 최초의 참조가 있어야 함 (Root Set)
        • 스택 참조
        • 네이티브 스택 (JNI 참조)
        • Static 영역의 참조
        • root set으로 시작한 참조들은 Strong reference
  • Soft, Weak, Phantom Reference
    • java.lang.ref 에서 soft reference/weak reference/phantom reference를 클래스 형태로 제공함
      • weak reference는 참조 대상인 객체를 캡슐화한 WeakReference 객체를 생성 => GC의 특별 취급 대상
    • 코드를 통해 봅시다

      WeakReference<Sample> wr = new WeakReference<Sample>(new Sample());
      Sample ex = wr.get();
      

      • WeakReference<Sample> wr = new WeakReference<Sample>(new Sample());
        Sample ex = wr.get();
        // ...
        ex = null;
        
      • new WeakReference() : reference object
      • new Sample() : referent
  • Reference와 Reachability
    • 원래 GC 대상 여부는 reachable/unreachable
    • java.lang.ref 패키지를 통해 객체들을 더 세분화하여 분류 가능
      • strongly reachable
      • softly reachable
      • weakly reachable
      • phantomly reachable
    • GC 동작시 unreachable 객체 뿐만 아니라 weakly reachable 객체도 GC에서 회수됨
      • weakly의 경우 root set으로 부터 시작된 참조 사슬에 포함되어도 회수됨
      • 참조는 가능하지만 반드시 항상 유효할 필요없는 LRU 같은 임시 객체 저장 구조 쉽게 만들 수 있음
  • Strengths of Reachability
    • strongly reachable: root set으로 시작해서 어떤 reference object도 중간에 끼지 않은 상태로 참조 가능
      • 객체까지 도달하는 여러 참조 사슬 중 reference object가 없는 사실이 하나라도 있는 객체
    • softly reachable: strongly reachable 객체가 아닌 객체 중에서 weak reference, phantom reference 없이 soft reference만 통과하는 참조 사슬이 하나라도 있음
      • soft reference만 통과하는 참조 사슬이 하나라도 있음
    • weakly reachable: strongly reachable 객체도, softly reachable 객체도 아닌 객체 중에서 phantom reference 없이 weak reference만 통과하는 참조 사슬이 하나라도 있음
      • weak reference만 통과하는 참조 사슬이 하나라도 있음
    • phantomly reachable: root로 부터 strongly/softly/weakly reachable 객체 모두 해당되지 않음
      • finalize 되었지만, 메모리가 아직 회수되지 않았음
    • unreachable: root set으로 부터 시작되는 참조 사슬로 참조되지 않는 객체
  • Softly Reachable과 SoftReference
    • 오직 SoftReference 객체로만 참조된 객체는 힙에 남아있는 메모리의 크기해당 객체의 사용 빈도에 따라 GC 여부가 결정됨
    • Softly reachable 객체는 GC가 동작할 때마다 회수되지 않으며, 자주 사용될수록 더 오래 살아남을 것
      • Oracle Hotspot VM에서는 softly reachable 객체의 GC를 조절하기 위한 옵션을 제공
        • -XX:SoftRefLRUPolicyMSPerMB=<N> // 옵션 기본값 1000
      • Softly reachable 객체의 GC 여부는 설정한 숫자에 따라 다음 수식에 의해 결정
        • (마지막 strong reference가 GC된 때로 부터 지금까지의 시간) > (옵션 설정값 N) * (힙에 남아있는 메모리 크기)
        • 옵션 설정값: 1000
        • 메모리: 100MB
        • 수식 = 1000ms/MB * 100MB = 100,000ms = 100sec
          • 100초 이상 사용되지 않으면 GC가 회수해감!
  • Weakly Reachable과 WeakReference
    • weakly reachable 객체는 GC를 수행할 때마다 회수 대상이 됨
    • 언제 회수될지는 확실하지 않음 => GC 알고리즘 별로 다 다름
    • LRU 캐시와 같은 어플리케이션은 weakly reachable이 유리해서 대체로 WeakReference 사용
  • ReferenceQueue
    • SoftReference/WeakReference 객체가 참조하는 객체가 GC의 대상이 되면...
      • SoftReference/WeakReference의 참조는 null,
      • SoftReference/WeakReference 객체 자체는 GC에 의해 ReferenceQueue에 enqueue
    • ReferenceQueue의 poll()/remove()를 통해 reference object가 enqueue 되었는지 확인하여 softly reachable, weakly reachable 객체가 GC 되었는지 판단 => 후처리 가능
    • ReferenceQueue와 WeakReference를 통한 간단한 캐시 구현 가능
    • PhantomReference는 무조건 ReferenceQueue를 사용해야함
  • Phantomly Reachable과 PhantomReference
    • GC 대상 객체를 처리하는 작업 -- 할당된 메모리 회수하는 작업 각각 별도!
    • GC 대상 여부를 결정하는 부분에 관여하는 softly reachable, weakly reachable과 달리, phantomly reachable은 파이널라이즈와 메모리 회수 사이에서 관여
  • GC가 객체를 처리하는 순서
    1. Soft References => 메모리 크기/해당 객체의 사용 빈도에 따라 GC 여부 결정
    2. Weak References => GC 수행시 마다 회수 대상이 되지만, GC 알고리즘 별로 다 다름
    3. 파이널라이즈 => 회수할 메모리 결정. Strong Reference는 당연히 회수, 1/2번에서 고른것도 대상
    4. Phantom References
    5. 메모리 회수
  • 요약
    • Java GC는 GC 대상 객체를 찾는다
    • 대상 객체를 Finalization하고, 할당된 메모리를 회수하는 작업으로 구성된다
    • 어플리케이션은 사용자 코드에서 Reachability를 조절하여 Java GC에 일부 관여할 수 있다
    • 객체의 Reachablility를 조절하기 위해 java.lang.ref 패키지의 SoftReference, WeakReference, PhantomReference, ReferenceQueue 등 사용
  • 참조 유형

    • Strong References
      • 기본 참조 유형
      • MyClass obj = new MyClass();
    • Soft References
      • JVM의 메모리가 부족한 경우에만 힙 영역에서 제거됨
      • 아니라면 굳이 제거 안함
        MyClass ref = new MyClass();
        SoftReference<MyClass> softRef = new SoftReference<MyClass>(ref);
        
        // 이 시점에 GC의 실행 대상이 가능
        ref = null;
        
        // JVM의 메모리가 부족하지 않으면 GC 실행 대상이 되지 않을 수 있음
        // 그런 경우, null이 반환되는 것이 아닌 기존 객체가 반환됨.
        ref = softRef.get();
        
    • Weak References
      • 대상 객체 참조하는 경우 WeakReference만 존재한다면 GC 대상 => 다음 GC에서 무조건 힙 메모리에서 제거
        MyClass ref = new MyClass();
        WeakReference<MyClass> weakRef = new WeakReference<MyClass>(ref);
        
        // 이 시점에 GC의 실행 대상이 가능 => null 명시 해줘야 GC의 대상이 되는 듯!
        ref = null;
        
        // 다음 GC 실행시 무조건 힙 메모리에서 제거
        // 제거된 경우 null 반환
        ref = softRef.get();
        
    • Phantom References
      MyClass ref = new MyClass();
      ReferenceQueue<MyClass> refQueue = new ReferenceQueue<MyClass>();
      PhantomReference<MyClass> phantomRef = null;
      phantomRef = new PhantomReference<MyClass>(ref, refQueue);
      ref = null;
      
      System.gc();
      
      // phantomRef.get() == null
      

성능테스트

외부 시스템 장애에 대처하는 우리의 자세

  • 참고: https://techblog.woowahan.com/6447/
  • 의존성 제거
    • 이 연동이 꼭 필요한지 고민할 것
    • 안정성 신뢰도, 연동 목적, 리스크 감수할 만큼의 가치가 있는지를 명확히 할 것!
    • 의존 관계를 맺지 않는 것이 우리 서비스의 안정성을 높이는 데 가장 좋은 방법일수도 있음
  • 벤더 이중화
    • 널리 사용되는 서비스의 경우, 여러 업체에서 비슷한 기능 제공
    • 지도, pg, 문자 등이 대표적
    • 따라서 이중화 구현해두는 것이 좋은 방식!
  • 장애 격리
    • 이중화에 비용이 많이 들거나, 다른 벤더가 없으면 이중화 못하잖아?
    • 외부 시스템의 장애가 연동과 관련 없는 부분으로 전파되지 않도록 장애를 격리하는 것이 중요
    • 특정 기능이 일부 동작하지 않거나 기능이 저하되더라도 사용자가 최소 기능 사용할 수 있도록 하는 것이 핵심!
    • 최대한 한 기능의 장애가 다른 기능의 장애까지 이어지지 않도록 고려해야 함!
  • 미작동 감내
    • AWS, GCP 자체가 뻑나면... 모니터링 열심히하시고, 서비스 담당부서에 빠르게 확인하는 핫라인 운영하자!