728x90
반응형
728x90
반응형
반응형

Ehcache의 주요 특징

  1. 효율적인 캐싱: 메모리와 디스크를 사용한 하이브리드 캐싱을 지원하여, 대용량 데이터의 캐싱도 효율적으로 처리할 수 있습니다.
    1. 메모리 캐시: 자주 사용되는 데이터를 메모리에 저장하여 빠른 응답 시간을 제공합니다. 메모리 캐시는 가장 빠른 캐싱 옵션입니다.
    2. 디스크 캐시: 메모리 캐시의 데이터를 디스크에 저장하여, 메모리 용량 초과 시에도 데이터를 유지할 수 있습니다. 디스크 캐시는 메모리보다 느리지만 더 많은 데이터를 저장할 수 있습니다.
  2. 분산 캐싱: 클러스터링과 분산 캐싱을 지원하여, 여러 애플리케이션 인스턴스 간의 캐시 일관성을 유지할 수 있습니다.
    1. Ehcache는 여러 노드 간에 캐시 데이터를 공유하고 동기화할 수 있는 클러스터링 기능을 제공합니다. 이를 통해 분산 환경에서 데이터 일관성을 유지할 수 있습니다.
    2. Terracotta와 통합하여 확장 가능한 분산 캐시 클러스터를 구축할 수 있습니다.
    3. Ehcache + Terracotta의 장점:
      1. 분산 캐시 처리: 여러 서버에서 동일한 캐시 데이터를 공유하고 동기화할 수 있습니다.
      2. 확장성: 여러 서버에 걸쳐 캐시를 분산 처리하므로, 대규모 애플리케이션에서도 효율적으로 캐시를 관리할 수 있습니다.
      3. 고가용성: Terracotta는 분산 환경에서 장애 복구 기능을 제공하여 데이터의 고가용성을 보장합니다.
      4. 데이터 일관성: 동기화 또는 비동기화 모드를 통해 캐시 데이터의 일관성을 유지할 수 있습니다.
  3. 플러그인 방식: Spring Framework, Hibernate, JPA 등과 쉽게 통합되어, 개발자가 캐싱을 손쉽게 구현할 수 있습니다.
  4. 캐시 구성: 다양한 캐시 정책(예: LRU, LFU, FIFO)을 통해 캐시의 만료, 용량 제어, 데이터 일관성 관리가 가능합니다.
    1. TTL(Time To Live): 캐시 항목의 유효 시간을 설정하여, 지정된 시간이 지나면 캐시에서 자동으로 제거됩니다.
    2. TTI(Time To Idle): 캐시 항목이 사용되지 않은 시간 기준으로 만료되는 설정입니다. 일정 시간 동안 접근되지 않은 캐시 항목은 제거됩니다.
    3. LRU(Least Recently Used): 최근에 사용되지 않은 항목을 먼저 제거하여 캐시의 크기를 관리하는 정책입니다.
    4. LFU(Least Frequently Used): 사용 빈도가 적은 항목을 먼저 제거하여 캐시의 크기를 관리합니다.
  5. 기본 및 확장 기능: 기본적인 캐시 기능 외에도, 캐시 이벤트 청취자, 트랜잭션 캐시, 캐시 복제 등 다양한 기능을 제공합니다.
  6. JCache 표준 지원 & Spring과의 통합:
    1. Ehcache는 Spring 프레임워크와의 통합을 지원하며, Spring의 캐시 추상화를 통해 쉽게 설정하고 사용할 수 있습니다.
    2. 애노테이션 기반 캐시 관리(@Cacheable, @CacheEvict 등)를 지원하여 개발 생산성을 높일 수 있습니다.
  • 3.1 캐시 일관성 전략
    1. Write-through 캐시
      • 개념: 애플리케이션이 데이터베이스에 데이터를 쓰는 동시에 캐시에 데이터를 갱신하는 방식입니다. 쓰기 작업이 캐시와 데이터베이스에 동시 반영됩니다.
      • 장점: 데이터베이스와 캐시 간의 일관성이 보장됩니다.
      • 단점: 쓰기 작업의 지연 시간이 증가할 수 있습니다.
    2. Write-behind 캐시
      • 개념: 애플리케이션이 데이터를 캐시에 먼저 쓰고, 비동기적으로 데이터베이스에 반영하는 방식입니다. 캐시에 데이터를 저장한 후, 일정 시간 후에 데이터베이스에 기록됩니다.
      • 장점: 쓰기 작업의 성능이 향상됩니다.
      • 단점: 캐시와 데이터베이스 간의 일관성 문제가 발생할 수 있으며, 데이터 유실 가능성도 있습니다.
    3. Cache-aside 패턴 / 읽을때
      • 개념: 애플리케이션이 먼저 캐시에서 데이터를 조회하고, 캐시된 데이터가 없으면 데이터베이스에서 데이터를 읽은 후 캐시에 저장하는 방식입니다.
      • 장점: 읽기 작업 성능이 매우 높으며, 캐시된 데이터는 자주 읽히는 데이터로 효율적으로 관리됩니다.
      • 단점: 캐시와 데이터베이스 간 일관성을 보장하지 않으므로, 캐시 무효화 전략이 필요합니다.
    4. TTL(Time To Live) 기반 캐시
      • 개념: 캐시에 저장된 데이터에 **유효 기간(Time To Live, TTL)**을 설정하여, 데이터가 일정 시간이 지나면 자동으로 무효화됩니다.
      • 장점: 오래된 데이터가 자동으로 갱신되므로 일관성을 유지할 수 있습니다.
      • 단점: TTL을 적절하게 설정하지 않으면, 캐시 히트율이 떨어지거나 불필요한 갱신이 발생할 수 있습니다.
    5. 캐시 무효화(Cache Invalidation)
      • 개념: 데이터베이스에서 데이터가 변경되면 해당 데이터를 캐시에서 무효화하여, 다음 읽기 작업 시 새로운 데이터를 캐시에 다시 로드하는 방식입니다.
      • 장점: 데이터베이스와 캐시 간의 일관성을 보장합니다.
      • 단점: 데이터베이스와 캐시 간 동기화 지연으로 인해 일시적인 일관성 문제가 발생할 수 있습니다.

    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'javax.cache:cache-api'
    implementation 'org.ehcache:ehcache:3.6.2'

application.properties

# 캐시 구성 파일 위치 설정
spring.cache.jcache.config=classpath:ehcache.xml
spring.cache.type=ehcache

ehcache.xml(자바로도 가능)

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd"
         updateCheck="false">

    <!-- 기본 캐시 설정 -->
    <defaultCache
            maxEntriesLocalHeap="10000"
            eternal="false"
            timeToIdleSeconds="300"
            timeToLiveSeconds="600"
            overflowToDisk="false"/>

    <!-- 사용자 정의 캐시 설정 -->
    <cache name="exampleCache"
           maxEntriesLocalHeap="1000"
           eternal="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
           overflowToDisk="true"
           diskSpoolBufferSizeMB="20"
           maxEntriesLocalDisk="10000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

캐시 구성 세부 사항

  • 캐시 만료 정책
    • timeToIdleSeconds: 캐시에 저장된 데이터가 얼마나 오랫동안 사용되지 않았을 때 만료될지 설정합니다.
    • timeToLiveSeconds: 캐시에 저장된 데이터가 얼마나 오랫동안 존재할 수 있는지 설정합니다.
    • eternal: 데이터가 영원히 만료되지 않도록 설정합니다. true일 경우 만료 시간 설정이 무시됩니다.
  • 캐시 용량 관리
    • maxEntriesLocalHeap: 캐시에서 허용할 최대 엔트리 수를 설정합니다.
    • overflowToDisk: 메모리 캐시가 가득 차면 디스크로 데이터를 내보낼지 설정합니다.
    • maxEntriesLocalDisk: 디스크 캐시에 허용할 최대 엔트리 수를 설정합니다.
  • 데이터 유지 정책
    • memoryStoreEvictionPolicy: 메모리 캐시에서 데이터를 제거할 정책을 설정합니다. LRU (Least Recently Used), LFU (Least Frequently Used), FIFO (First In, First Out) 등이 있습니다.

 

위 의존성/설정을 추가하고 프로젝트에 @EnableCaching 어노테이션을 추가한다.

spring-boot를 사용하면 @EnableCaching 어노테이션만으로도 ConcurrentMapCacheManager를 기본으로 등록해주기 때문에 따로 등록할 것은 없지만 커스텀을 할 경우 아래와 같이 빈을 등록하여 override 해야 한다.

@Configuration
@EnableCaching
public class CachingConfig {

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(
          new ConcurrentMapCache("directory"), 
          new ConcurrentMapCache("addresses")));
        return cacheManager;
    }
}

참고) 캐시 매니저 종류

  • ConcurrentMapCacheManager: Java의 ConcurrentHashMap을 사용해 구현한 캐시를 사용하는 캐시 매니저
  • SimpleCacheManager: 기본적으로 제공하는 캐시가 없어 사용할 캐시를 직접 등록하여 사용하기 위한 캐시 매니저
  • EhCacheCacheManager: 자바에서 유명한 캐시 프레임워크 중 하나인 EhCache를 지원하는 캐시 매니저
  • CompositeCacheManager: 1개 이상의 캐시 매니저를 사용하도록 지원해주는 혼합 캐시 매니저
  • CaffeineCacheManager: Java 8로 Guava 캐시를 재작성한 Caffeine 캐시를 사용하는 캐시 매니저
  • JCacheCacheManager: JSR-107 기반의 캐시를 사용하는 캐시 매니저

 

ConcurrentMapCache 생성자를 통해서 캐시 이름과 null값을 캐싱할지 여부, 사용할 concurrentMap을 지정할 수 있다. 참고로 ConcurrentMapCache는 만료시간에 의한 자동 만료 기능이 없기 때문에, 수동으로 체크 혹은 @Scheduled를 활용하여 구현해야 한다.

참고로 캐시 흐름을 로그로 보고 싶다면 별도 이벤트 리스너를 구현해야한다.

@Slf4j
public class CacheEventConfig implements CacheEventListener<Object, Object> {

  @Override
  public void onEvent(CacheEvent<?, ?> event) {
    log.info(">>>[Caching event] working {} :: {}", event.getType(), event.getKey());
  }
}

캐시 조회/저장 @Cacheable

캐시 해야 하는 데이터가 return 되는 메서드 위에 설정한다.

@Cacheable("addresses")
public String getAddress(Customer customer) {...}

getAddress가 호출될 때 실제 로직이 시행되기 전 input parameter(key = customer)에 해당하는 캐시를 먼저 확인하고, 없으면 실제 로직을 실행하고 캐싱을 한다. 위 예시는 Customer클래스가 key 값이므로(별도의 key값을 설정하지 않았으므로) hashcode와 equals 메서드를 오버라이드 해 어떤 데이터일 때 같다고 판별할지 명백히 해야 한다. 아래 별도의 링크를 통해 키 판별에 대해 확인하자.

@Cacheable({"addresses", "directory"})
public String getAddress(Customer customer) {...}

이렇게 여러 개의 캐시를 줄 수도 있다. 이때 둘 중 하나라도 값이 있으면 실제 로직을 실행하지 않는다.

@Cacheable(value = "bestSeller", key = "#book.bookNo")
public Book getBestSeller(Book book, User user, Date dateTime) {...}

Key값의 지정에는 SpEL이 사용된다. 그렇기 때문에 만약 파라미터가 객체라면 위와 같이 하위 속성에 접근할 수 있다.

@Cacheable(value = "bestSeller", key = "#book.bookNo", condition = "#user.type == 'ADMIN'")
public Book getBestSeller(Book book, User user, Date dateTime) {...}

만약 파라미터 값이 특정 조건인 경우에만 캐시를 적용하기를 원한다면 condition을 이용하면 된다.

 

캐시 삭제 @CacheEvict

@CacheEvict(value = "bestSeller")
public void clearBestSeller() {...}

@CacheEvict(value="directory", key=customer.name)
public String getAddress(Customer customer) {...}

기본적으로 메서드의 키에 해당하는 캐시만 제거한다. 위 예시는 이름이 같은 캐시만 제거할 것이다.

@Caching(evict = { 
  @CacheEvict("addresses"), 
  @CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {...}

여러 이름의 캐시를 지울 때는 위와 같이 복수개 설정이 가능하다.

@CacheEvict(value="addresses", allEntries=true)
public String getAddress(Customer customer) {...}

key와 상관없이 모든 객체를 지우려면 allEntries = true로 지정하면 된다.

 

캐시 수정 @CachePut

캐시 값이 바뀌었다면 바로 수정해주어야 한다. @CachePut은 @Cacheable과 유사하게 실행 결과를 캐시에 저장하지만, 캐시의 저장 유무와 상관없이 항상 메서드의 로직을 실행한다는 점에서 다르다.

@CachePut(value="addresses")
public String getAddress(Customer customer) {...}

 

@CacheConfig

하나의 캐시에 대해 일괄 관리를 하고자 하면 아래와 같이 클래스 레벨에서 처리할 수도 있다. 이때 이름을 중복으로 적을 필요 없이 클래스 선언으로 해결된다.

@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {

    @Cacheable
    public String getAddress(Customer customer) {...}
}

 

조건절

위에서 잠깐 나왔지만 spEL을 활용하여 condition / unless의 조건절을 줄 수 있다.

@CachePut(value="addresses", condition="#customer.name=='Tom'")
public String getAddress(Customer customer) {...}

@CachePut(value="addresses", unless="#result.length()<64")
public String getAddress(Customer customer) {...}

 

캐시 key 설정에 대한 내용

https://jistol.github.io/spring/2017/02/09/springboot-cache-key/

 

(Spring Cache) @Cacheable key값 정하기

 

jistol.github.io

https://sunghs.tistory.com/132

 

[SPRING] 스프링 캐시 사용하기

spring에서 cache 관련 된 기능을 지원한다. 기존 cache 처리라고 하면, Redis, memcached 등의 추가적인 memoryDB를 이용하거나, application 레벨에서 사용 가능한 EhCache 등이 많이 쓰이는데, 이 중 applicati..

sunghs.tistory.com

 

728x90
반응형
반응형

프록시 패턴

  • 인터페이스를 구현을 통해 진짜 구현체를 대신하여 부가기능을 제공하거나 리턴 값을 변경할 수 있도록 대리자(surrogate)나 자리표시자(placeholder)의 역할을 하는 객체를 제공하는 패턴

각 클래스는 자신이 해야 할 일(SRP: Single Responsibility Principle)만 해야하는데 부가적인 기증을 제공할 때 이런 패턴을 주로 사용함

하지만 부가적인 기능을 추가할 때마다 위임하는 코드가 중복해서 발생할 수 있음

코딩 예시: https://bamdule.tistory.com/154

 

[디자인 패턴] 프록시 패턴 (Proxy Pattern)

1. 프록시 패턴이란? 실제 객체를 바로 이용하는 것 대신 가상 객체에 실제 객체를 선언하여 실제 객체의 기능과 추가적인 기능을 사용함으로 써 기능의 흐름을 제어하는 디자인 패턴입니다. 2.

bamdule.tistory.com

 


매번 클래스를 만드는게 아니라 동적으로 생성하는 방식

-> java reflextion을 활용한 다이내믹 프록시 사용

  • (컴파일 타임이 아닌) 런타임에 인터페이스를 구현하는 클래스/프록시 인스턴스를 만들어 사용하는 프로그래밍 기법

프록시 인스턴스 만들기

  • Object Proxy.newProxyInstance(ClassLoader, Interfaces, InvocationHandler)
    • object로 리턴되기 때문에 type casting 필요
//BookService는 반드시 인터페이스여야 함
BookService bookService = (BookService) Proxy.newProxyInstance(BookService.class.getClassLoader(), new Class[]{BookService.class},
        new InvocationHandler() {
            BookService bookService = new DefaultBookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("rent")) { //rent 라는 method에만 적용하고 싶다면
                    System.out.println("aaaa");
                    Object invoke = method.invoke(bookService, args);
                    System.out.println("bbbb");
                    return invoke;
                }
             //나머지 method는 그대로 리턴
                return method.invoke(bookService, args);
            }
        });

근데 매번 이렇게 구현하는 게 더 복잡, 유연한 구조가 아님.

그리고 클래스 기반의 (자바가 제공하는) proxy를 만들 수 없음.

-> 그래서 Spring AOP(프록시 기반의 AOP)가 등장

 


클래스의 프록시가 필요하다면?

-> subclass를 만들어서 프록시를 만드는 라이브러리를 사용

 

CGlib lib 사용

  • https://github.com/cglib/cglib/wiki
  • 스프링, 하이버네이트가 사용하는 라이브러리
  • 버전 호환성이 좋지 않아서 서로 다른 라이브러리 내부에 내장된 형태로 제공되기도 한다.
MethodInterceptor handler = new MethodInterceptor() {
    BookService bookService = new BookService(); //class
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if(method.getName().equals("rent")){ //method 이름이 rent 일 때만 작업
            log.info("aaa");
            Objet invoke = method.invoke(bookService, args);
            log.info("bb");
            return invoke;
        }
        //나머지 method는 그대로 리턴
        return method.invoke(bookService, args);
    }
};

BookService bookService = (BookService) Enhancer.create(BookService.class, handler);

 

ByteBuddy lib 사용

  • https://bytebuddy.net/#
  • 스프링에서 버전 관리해줌
  • 바이트 코드 조작뿐 아니라 런타임(다이내믹) 프록시를 만들 때도 사용할 수 있다.
Class<? extends BookService> proxyClass = new ByteBuddy().subclass(BookService.class)
        .method(named("rent")) //method 이름이 rent면 적용
        .intercept(InvocationHanderAdaptor.of(new InvocationHander(){
            BookService bookService = new BookService();
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                log.info("aaa");
                Object invoke = method.invoke(bookService, args);
                log.info("Bbb");
                return invoke;
            }
        }))
        .make().load(BookService.class.getClassLoader()).getLoaded();
BookService bookService = proxyClass.getConstructor(null).newInstance();

 

서브 클래스를 만드는 방법의 단점

  • 상속(extends)을 사용하지 못하는 경우 프록시를 만들 수 없음
    • private 생성자(하위 클래스에서 항상 부모의 생성자를 호출한다)
    • final 클래스
  • 인터페이스가 있을 때는 인터페이스의 프록시를 만들어 사용할 것.

 

다이내믹 프록시 기법이 사용되는 곳

  • 스프링 데이터 JPA
  • 스프링 AOP
  • Mockito
  • 하이버네이트 lazy initialization(@OneToMany 등 entity 들고 있을 때 프록시 객체로 처음에 리턴)

 

728x90
반응형
반응형

이전 글: 2022.03.16 - [개발/reactive] - [reactive] 4-1. java Future/FutureTask/Callable/Runnable

 

[reactive] 4-1. java Future/FutureTask/Callable/Runnable

callable vs runnable interface Runnable interface Callable interface java version package java 1.0 ~ java.lang java 1.5 ~ java.util.concurrent return 계산 결과를 받을 수 없음 계산 결과 받을 수 있음(..

bangpurin.tistory.com

이전 글과 오늘 글은 아래 유튜브를 보고 작성하였다.

 

1. using Spring Async

@Slf4j
@EnableAsync  //있어야 async 작동
@SpringBootApplication
public class Spring {

    @Component
    public static class MyService{
        //future인데 등록해서 받아볼 수 있게하는, 스프링이 만든 ListenableFuture
        //요청할 때 마다 스레드를 계속 만들어 비효율적인 simpleAsyncTaskExecutor -> 빈으로 풀 등록해서 쓰자
        //비동기가 여러개고 스레드 풀을 분리해서 지정하고 싶으면 Async("name") 지정 가능
        @Async
        public ListenableFuture<String> hello() throws InterruptedException {
            log.info("hello()");
            Thread.sleep(2_000);
            return new AsyncResult<>("hello");
        }
    }

    //task executor를 빈으로 해두면 알아서 가져가서 쓰게 되어있음
    @Bean
    ThreadPoolTaskExecutor threadPoolExecutor(){
        ThreadPoolTaskExecutor te = new ThreadPoolTaskExecutor();
        te.setCorePoolSize(10);//기본으로 만드는거; 첫번째 스레드 요청이 올 때; jmx를 통해 런타임에도 값 수정이 가능
        te.setQueueCapacity(200);//기본 풀이 다 차면 큐에 먼저 차
        te.setMaxPoolSize(100);//큐가 꽉 찼을 때 에러나는데 그걸 막아주려고 하는 설정
        te.setThreadNamePrefix("async-");
        te.initialize();
        return te;
    }

    public static void main(String[] args) {
        //try with resource로 실행하면 작업 후 알아서 종료
        try(ConfigurableApplicationContext c = SpringApplication.run(Spring.class, args)){

        }
    }

    @Autowired MyService myService;

    //모든 빈 등록 마치면 바로 실행 됨
    @Bean
    ApplicationRunner run() {
        return args -> {
            log.info("run()");
            ListenableFuture<String> f = myService.hello();
            f.addCallback(s -> System.out.println(s), e -> System.out.println(e.getMessage()));
            log.info("Exit");
        };
    }
}

여기서 주의할 점.

ThreadPoolTaskExecutor에 max pool size에 설정하는 값은 큐가 다 차고 나서 늘어가는 풀 사이즈이다.

https://www.baeldung.com/java-threadpooltaskexecutor-core-vs-max-poolsize

 

참고로 HttpServletRequest/Response -> 자바의 InputStream 을 구현한 것으로, blocking방식이다.


2. using Spring Web - default

우선 서버 설정 값

#서블릿 스레드
server.tomcat.threads.max=1
#작업스레드풀?
spring.task.execution.pool.core-size=100
@Slf4j
public class LoadTest {
    static AtomicInteger cnt = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(100);

        RestTemplate rt = new RestTemplate();
        String url = "http://localhost:8080/callable";

        StopWatch sw = new StopWatch();
        sw.start();

        for(int i = 0; i <100; i++){
            es.execute((() -> {
                int idx = cnt.addAndGet(1);
                log.info("thread {}", idx);
                StopWatch s1 = new StopWatch();
                s1.start();
                rt.getForObject(url, String.class);
                s1.stop();
                log.info("elapsed:{} -> {}", idx, s1.getTotalTimeSeconds());
            }));
        }
     
        es.shutdown();
        es.awaitTermination(100, TimeUnit.SECONDS);

        sw.stop();
        log.info("total time {}", sw.getTotalTimeSeconds());
    }
}
@RestController
public static class MyController{
    @GetMapping("/callable")
    public String callable() throws InterruptedException {
        log.info("async");
        Thread.sleep(2_000);
        return "hello";
    }
}

위 요청은 아래 그림과 같이 표현된다.

load test는 100개의 스래드를 만들어 거의 동시에 요청했으나, 순차 처리로 인해 총 소요 시간은 200초가 넘는다.

...
13:04:56.428 [pool-1-thread-50] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080/callable
13:04:56.427 [pool-1-thread-65] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080/callable
13:04:56.428 [pool-1-thread-3] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080/callable
13:04:56.427 [pool-1-thread-90] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8080/callable
...
13:08:04.993 [pool-1-thread-61] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
13:08:04.993 [pool-1-thread-61] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
13:08:04.994 [pool-1-thread-61] INFO toby.live.lecture4.LoadTest - elapsed:61 -> 188.612548654
13:08:06.999 [pool-1-thread-82] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
13:08:06.999 [pool-1-thread-82] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
13:08:06.999 [pool-1-thread-82] INFO toby.live.lecture4.LoadTest - elapsed:82 -> 190.618529907
13:08:09.002 [pool-1-thread-71] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
13:08:09.002 [pool-1-thread-71] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
13:08:09.003 [pool-1-thread-71] INFO toby.live.lecture4.LoadTest - elapsed:71 -> 192.622624014
13:08:11.009 [pool-1-thread-32] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
13:08:11.009 [pool-1-thread-32] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
13:08:11.009 [pool-1-thread-32] INFO toby.live.lecture4.LoadTest - elapsed:32 -> 194.629844982
13:08:13.012 [pool-1-thread-35] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
13:08:13.012 [pool-1-thread-35] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
13:08:13.013 [pool-1-thread-35] INFO toby.live.lecture4.LoadTest - elapsed:35 -> 196.631976988
13:08:15.015 [pool-1-thread-59] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
13:08:15.015 [pool-1-thread-59] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
13:08:15.016 [pool-1-thread-59] INFO toby.live.lecture4.LoadTest - elapsed:59 -> 198.634385976
13:08:17.018 [pool-1-thread-65] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
13:08:17.018 [pool-1-thread-65] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
13:08:17.019 [pool-1-thread-65] INFO toby.live.lecture4.LoadTest - elapsed:65 -> 200.638424197

/callable 쪽은 요청을 거의 동시에 100개의 요청을 받았지만 일반 동기 방식이므로 하나씩 처리한다.

...
2022-03-17 13:08:06.998  INFO 22518 --- [nio-8080-exec-1] toby.live.lecture4.SpringWeb             : async
2022-03-17 13:08:09.002  INFO 22518 --- [nio-8080-exec-1] toby.live.lecture4.SpringWeb             : async
2022-03-17 13:08:11.008  INFO 22518 --- [nio-8080-exec-1] toby.live.lecture4.SpringWeb             : async
2022-03-17 13:08:13.012  INFO 22518 --- [nio-8080-exec-1] toby.live.lecture4.SpringWeb             : async
2022-03-17 13:08:15.014  INFO 22518 --- [nio-8080-exec-1] toby.live.lecture4.SpringWeb             : async

 

3. callable

/callable 쪽만 소스를 수정한다.

@RestController
public static class MyController{
    @GetMapping("/callable")
    public Callable<String> callable() throws InterruptedException {
        log.info("callable");
        //Callable Interface 익명 클래스
        return () -> {
            log.info("async");
            Thread.sleep(2_000);
            return "hello";
        };
    }

callable은 요청을 받으면 별도의 스레드를 생성해 일을 병렬로 처리한다.

100개의 스래드가 거의 동시에 요청을 보내 100개의 작업 스래드가 받아서 동시에 처리하기 때문에 총 소요시간도 2초 남짓이다.

...
13:33:24.694 [pool-1-thread-63] INFO toby.live.lecture4.LoadTest - elapsed:63 -> 2.289299445
13:33:24.694 [pool-1-thread-30] INFO toby.live.lecture4.LoadTest - elapsed:30 -> 2.291355199
13:33:24.695 [pool-1-thread-67] INFO toby.live.lecture4.LoadTest - elapsed:67 -> 2.292713823
13:33:24.695 [pool-1-thread-81] INFO toby.live.lecture4.LoadTest - elapsed:80 -> 2.289499954
13:33:24.695 [pool-1-thread-3] INFO toby.live.lecture4.LoadTest - elapsed:3 -> 2.290040178
13:33:24.695 [pool-1-thread-57] INFO toby.live.lecture4.LoadTest - elapsed:57 -> 2.292708376
13:33:24.696 [pool-1-thread-44] INFO toby.live.lecture4.LoadTest - elapsed:44 -> 2.29307173
13:33:24.696 [main] INFO toby.live.lecture4.LoadTest - total time 2.307467369

/callable이 1개의 서블릿 스레드로 받았지만 별도의 작업 스래드로 바로 토스하여 일을 하는 모습..

...
2022-03-17 13:33:22.624  INFO 23081 --- [nio-8080-exec-1] toby.live.lecture4.SpringWeb             : callable
2022-03-17 13:33:22.624  INFO 23081 --- [        task-98] toby.live.lecture4.SpringWeb             : async
2022-03-17 13:33:22.625  INFO 23081 --- [nio-8080-exec-1] toby.live.lecture4.SpringWeb             : callable
2022-03-17 13:33:22.625  INFO 23081 --- [        task-99] toby.live.lecture4.SpringWeb             : async
2022-03-17 13:33:22.625  INFO 23081 --- [nio-8080-exec-1] toby.live.lecture4.SpringWeb             : callable
2022-03-17 13:33:22.625  INFO 23081 --- [       task-100] toby.live.lecture4.SpringWeb             : async

 


4. DeferredResult

DeferredResult란 “지연된 결과”를 의미하며 외부의 이벤트 혹은 클라이언트 요청에 의해서 지연되어 있는 HTTP 요청에 대한 응답을 나중에 써줄 수 있는 비동기 작업이다. 

기존에 /callable api가 있던 곳에 아래와 같이 신규 api를 추가해준다.

Queue<DeferredResult<String>> results = new ConcurrentLinkedQueue<>();

@GetMapping("/dr")
public DeferredResult<String> dr(){
    log.info("dr");
    //DR은 setResult가 오기전 까지 응답을 끊지 않고 대기, 그래서 테스트 시 타임아웃 시간을 길게
    //결과가 오면 바로 return
    DeferredResult<String> dr = new DeferredResult<>(600_000L);
    results.add(dr);
    return dr;
}

@GetMapping("/dr/count")
public String drcount(){
    return String.valueOf(results.size());
}

@GetMapping("/dr/event")
public String drevent(String msg){
    for(DeferredResult<String> dr: results){
        dr.setResult("hello " + msg);
        results.remove(dr);
    }
    return "OK";
}

load test에 /dr을 쏘게끔 설정하고 실행하면 100개의 요청을 우르르 보내지만 아무 응답을 받지 않고 대기하는 모습을 보이는데

이때 /dr/count를 쏴보면 100개의 요청이 큐에 들어있는 것을 확인할 수 있다.

그 상태에서 /dr/event?msg=today 를 요청하면 그제야 100개의 응답이 한 번에 온다. setResult라는 이벤트를 주면 리턴하는 방식이다.

...
13:48:17.039 [pool-1-thread-98] INFO toby.live.lecture4.LoadTest - elapsed:98 -> 213.022709247
13:48:17.039 [pool-1-thread-30] INFO toby.live.lecture4.LoadTest - elapsed:30 -> 213.027111781
13:48:17.039 [pool-1-thread-16] INFO toby.live.lecture4.LoadTest - elapsed:16 -> 213.026715767
13:48:17.040 [pool-1-thread-1] INFO toby.live.lecture4.LoadTest - elapsed:1 -> 213.025866684

 

5. ResponseBodyEmitter

ResponseBodyEmitter는 하나의 요청에 대한 응답을 여러번에 나눠서 보낼 때 사용된다.

//여러번에 나눠서 데이터를 보낸다
@GetMapping("/emitter")
public ResponseBodyEmitter emitter(){
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    Executors.newSingleThreadExecutor().submit(() -> {
        try {
            for(int i =0; i<=50; i++){
                emitter.send("<p> stream " +i+ "</p>");
                Thread.sleep(100);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
    return emitter;
}

/emitter를 브라우저로 실행하면 stream 문구가 100ms 마다 계속 뜨는 것을 볼 수 있다. 위에 상태 표시에도 로딩으로 되어 있어 계속 응답을 받아오고 있음을 알 수 있다.


참고

https://jongmin92.github.io/2019/03/31/Java/java-async-1/

 

자바와 스프링의 비동기 기술

해당 포스팅은 토비님의 토비의 봄 TV 8회 스프링 리액티브 프로그래밍 (4) 자바와 스프링의 비동기 기술 라이브 코딩을 보며 따라했던 실습 내용을 바탕으로 정리한 글입니다. 실습 코드들은 Inte

jongmin92.github.io

 

728x90
반응형
반응형

callable vs runnable interface

  Runnable interface Callable interface
java version
package
java 1.0 ~
java.lang
java 1.5 ~
java.util.concurrent
return 계산 결과를 받을 수 없음 계산 결과 받을 수 있음(a generic value V)
throw checked exception을 throw 불가 checked exception을 throw 가능
override method run() call()

https://www.geeksforgeeks.org/difference-between-callable-and-runnable-in-java/

 

Difference Between Callable and Runnable in Java - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

자바 비동기 맛보기

0. ExecutorService 특징

  • es.execute vs es.submit 
  • 시간이 오래 걸리는 작업을 할 때는 submit 추천. future object를 리턴하기 때문에 나중에 get으로 꺼낼 수 있음(get은 블로킹!)
  • 그냥 스레드의 병렬 처리만 필요하면 execute
  execute() submit()
accept Runnable Runnable and Callable
declared in Executor interface ExecutorService interface
return void a Future object
  • es.shutdown 은 graceful shutdown이라서 할 거 다 할 때까지 기다림
  • es.shutdownNow 가 강제 종료
  • es.awaitTermination 은 타임아웃을 기다리는 용도로 실제 shutdown 시키지는 않음
  • 따라서 shutdown 먼저 걸로 awaitTermination을 써야 함

 

1. Future class

public static void main(String[] args) throws InterruptedException, ExecutionException {
    //스래드를 새로 만들고 반납하는 것이 비용이 많이 들어가니, 풀이 나옴
    //맥시멈 제한없고 처음에 스레드 없음 요청할 때 새로 만들고 다음 요청 시 기존 만든 스레드 실행
    ExecutorService es = Executors.newCachedThreadPool();
    //es.execute -> void

    //Future 비동기 핸들러지 결과물은 아님
    Future<String> f = es.submit(() -> {
        Thread.sleep(5_000);
        log.info("helloo");
        return "hello";
    });
    //f.isDone() -> non blocking method;
    System.out.println(f.isDone());
    log.info("before");
    //f.get -> blocking method
    System.out.println(f.get()); //비동기가 완성될 때 까지 블로킹(20초 기다림)
    log.info("exit");//맨 마지막
    System.out.println(f.isDone());
}

 

2. FutureTask class

Future 자체를 object로 만들어줌

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService es = Executors.newCachedThreadPool();

    FutureTask<String> f = new FutureTask<String>(() -> {
       Thread.sleep(3_000);
       log.info("async");
       return "hello";
    }){//익명클래스
        @Override
        protected void done() { //다 하면 이거해; f.get 할 필요없음
            try {
                System.out.println(get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    };

    es.execute(f);
    System.out.println(f.isDone());
    //System.out.println(f.get());
    es.shutdown(); //다 끝나면 종료
}

 

3. FutureTask 확장

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService es = Executors.newCachedThreadPool();

    CallbackFutureTask f = new CallbackFutureTask(() -> {
        Thread.sleep(3_000);
        if(1 == 1){
            throw new RuntimeException(">async error");
        }
        log.info("async");
        return "hello";
    }, s -> System.out.println("success:" + s)
    , e -> System.out.println(">error: " + e.getMessage())
    );

    es.execute(f);
    es.shutdown(); //다 끝나면 종료
}

interface SuccessCallback{
    void onSuccess(String result);
}

interface ExceptionCallback{
    void onError(Throwable t);
}

public static class CallbackFutureTask extends FutureTask<String>{
    SuccessCallback sc;
    ExceptionCallback ec;

    public CallbackFutureTask(Callable<String> callable, SuccessCallback sc, ExceptionCallback ec) {
        super(callable);
        //if(sc == null) throw null; //null point exception
        this.sc = Objects.requireNonNull(sc); //if null -> NPE
        this.ec = Objects.requireNonNull(ec);
    }

    @Override
    protected void done() {
        try {
            sc.onSuccess(get());
        } catch (InterruptedException e) { //종료 시그널
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) { // 찐에러
            ec.onError(e.getCause());
        }
    }
}
728x90
반응형
반응형

환경: springboot2.6.2 / java 11

로그 레벨을 DEBUG로 프로젝트를 실행하니 아래와 같은 에러가 빵빵 지나가는 것이 아닌가..

grpc를 사용하는 프로젝트라, grpc server가 내려가 있어서 발행하는 것이 아닌가 싶어서 기동 후 다시 봤는데도 마찬가지였다.

java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled
	at io.grpc.netty.shaded.io.netty.util.internal.ReflectionUtil.trySetAccessible(ReflectionUtil.java:31)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0$4.run(PlatformDependent0.java:239)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:233)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:294)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:93)
	at io.grpc.netty.shaded.io.netty.util.AsciiString.<init>(AsciiString.java:223)
	at io.grpc.netty.shaded.io.netty.util.AsciiString.<init>(AsciiString.java:210)
	at io.grpc.netty.shaded.io.netty.util.AsciiString.cached(AsciiString.java:1401)
	at io.grpc.netty.shaded.io.netty.util.AsciiString.<clinit>(AsciiString.java:48)
	at io.grpc.netty.shaded.io.grpc.netty.Utils.<clinit>(Utils.java:78)
	at io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.<clinit>(NettyChannelBuilder.java:82)
    
    ...
    
    
    
java.lang.IllegalAccessException: class io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0$6 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @61874b9d
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
	at java.base/java.lang.reflect.Method.invoke(Method.java:558)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0$6.run(PlatformDependent0.java:353)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:344)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:294)
	at io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:93)
	at io.grpc.netty.shaded.io.netty.util.AsciiString.<init>(AsciiString.java:223)
	at io.grpc.netty.shaded.io.netty.util.AsciiString.<init>(AsciiString.java:210)
	at io.grpc.netty.shaded.io.netty.util.AsciiString.cached(AsciiString.java:1401)
	at io.grpc.netty.shaded.io.netty.util.AsciiString.<clinit>(AsciiString.java:48)
	at io.grpc.netty.shaded.io.grpc.netty.Utils.<clinit>(Utils.java:78)
	at io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder.<clinit>(NettyChannelBuilder.java:82)

 

원인: 자바 11부터 Object의 접근권한이 더이상 증가하지 않는다는 에러. 에러가 아예 안 나려면(정상 액션을 원한다면) 자바 8을 사용하라는데, 발전하는 IT 시대에 역행은 오버라고 생각한다.. 자바 11에 AccessibleObject라는 새로운 클래스가 생겼으니, 아마 관련 라이브러리들이 곧 수정하지 않을까 하는 생각..

https://stackoverflow.com/questions/60241857/java-lang-unsupportedoperationexception-reflective-setaccessibletrue-disabled

 

java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled

When I run my Ktor application with gradle run then I've got the following exception: 19:21:11.795 [main] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default l...

stackoverflow.com

 

해결:

관련 에러에 대해 검색해보면 이것저것 많이 나오는데, 특히 뜰 때 옵션에 아래 등과 같은 옵션을 주라고 한다.

-Dio.netty.tryReflectionSetAccessible=true

설정해보았지만 여전히 에러같이 생긴 친구들이 지나가서 굉장히 신경 쓰인다..

사실 저 로그는 에러라기보다는 디버그에 가깝다(고 한다..). 그래서 그냥 해당 로그 레벨을 ERROR로 바꿔버렸다..

<logger name="io.grpc.netty" level="ERROR"/>

 

사실 에러(?)를 무시하는 격이라 좀 찝찝하긴 하지만, 아직까지는 더 나은 방안은 없어 보인다. 기다려보자.

728x90
반응형
반응형

JVM (Java Virtual Machine)

  • 자바 가상 머신으로 자바 바이트 코드(.class 파일)를 OS에 특화된 코드로 변환(인터프리터와 JIT 컴파일러)하여 실행
  • 바이트 코드를 실행하는 표준(JVM 자체는 표준)이자 구현체(특정 밴더가 구현한 JVM)
  • 특정 플랫폼에 종속적

JRE (Java Runtime Environment): JVM + 라이브러리

  • 자바 애플리케이션을 실행할 수 있도록 구성된 배포판
  • JVM과 핵심 라이브러리 및 자바 런타임 환경에서 사용하는 프로퍼티 세팅이나 리소스 파일을 가지고 있음
  • 개발 관련 도구(JDK)는 포함하지 않음

JDK (Java Development Kit): JRE + 개발 툴

  • JRE + 개발에 필요할 툴
  • 소스 코드를 작성할 때 사용하는 자바 언어는 플랫폼에 독립적
  • 오라클은 자바 11부터는 JDK만 제공하며 JRE를 따로 제공하지 않음
  • Write Once Run Anywhere

Java

  • 프로그래밍 언어
  • JDK에 들어있는 자바 컴파일러(javac)를 사용하여 바이트코드(.class 파일)로 컴파일
  • 자바 유료화? 오라클에서 만든 Oracle JDK 11 버전부터/ 상용으로 사용할 때 유료이며 나머지는 무료

JVM 구조

jvm

Class Loader

  • .class에 있는 바이트코드를 읽고 메모리에 저장
  • loading: 클래스 읽어오는 과정
  • linking: 레퍼런스를 연결하는 과정
  • initialization: static 값들 초기화 및 변수에 할당

메모리

  • 메소드 영역에는 클래스 수준의 정보(클래스 이름, 부모 클래스 이름, 메소드, 변수) 저장
  • 힙 영역에는 객체를 저장
  • 스택 영역에는 스레드 마다 런타임 스택을 만들고, 그 안에 메소드 호출을 스택 프레임이라 부르는 블록으로 쌓음. 스레드 종료하면 런타임 스택도 삭제됨
  • PC(Program Counter) 레지스터: 스레드 마다 스레드 내 현재 실행할 instruction의 위치를 가리키는 포인터가 생성됨
  • 네이티브 메소드 스택: Native Method를 호출하는 코드를 수행하기 위한 스택

Execution engine

  • interpreter: 바이트 코드를 한 줄씩 실행
  • JIT compiler: 인터프리터 효율을 높이기 위해, 인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러는 반복되는 코드를 모두 네이티브 코드로 바꿈. 그다음부터 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용할 수 있도록 지원
  • GC(Garbage Collector): 더이상 참조되지 않는 객체를 모아서 주기적으로 정리하는 프로그램

JNI(Java Native Interface)

  • 자바 애플리케이션에서 C, C++, 어셈블리로 작성된 함수를 사용할 수 있게 함
  • native 키워드를 사용한 메소드 호출

Native Method Library

  • C, C++로 작성된 라이브러리

 

garbage?

  • 주소를 잃어버려서 사용할 수 없는 메모리, 정리되지 않은 메모리, dangling object

garbage collection(GC)?

  • garbage의 메모리 해제; JVM이 한가할 때(idle) 혹은 메모리가 부족해져 OS에게 추가로 메모리를 할당해달라고 요청할 때 실행

 

JVM의 Heap영역의 대전제

  • 대부분의 객체는 대부분 일회성이며 금방 접근 불가능한 상태(Unreachable)가 된다.
  • 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
  • 생존 기간에 따라 heap을 두 가지 영역으로 쪼갬(Young, Old 영역) - 초기에는 Perm 영역이 존재하였지만 Java8부터 제거됨

 

minor gc?

  • 새롭게 생성된 객체가 할당(Allocation)되는 Young 영역에 대한 가비지 컬렉션(Garbage Collection)
  • 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young영역에 생성되었다가 사라짐
    • The Young Generation is further divided into three spaces: Eden space and two Survivor spaces (S0 and S1).
  • eden 영역이 꽉 찬 경우 발생, 빠름
    • eden 영역에서 사용되지 않아진 객체의 메모리 해제
    • eden 영역에서 살아남은 객체는 s1/s2로 이동(s1이 꽉차면 s2, vice versa)
       

major gc/full gc?

  • minor gc속에서 살아남은 객체가 복사되는 Old 영역에 대한 가비지 컬렉션(Garbage Collection)
  • 복사되는 과정에서 대부분 Young 영역보다 크게 할당되며, 크기가 큰 만큼 가비지는 적게 발생함
  • old 영역이 꽉 찬 경우 발생, 느림

 

구체적으로 어떻게?

  • Stop the world(STW): GC를 실행하기 위해 JVM이 어플리케이션의 실행을 멈추는 작업
    • GC를 실행하는 스레드를 제외한 모든 스레드의 작업이 중단되고, GC가 완료되면 작업 재개됨
    • GC옵션이나 알고리즘으로 STW시간을 단축할 수 있음
  • Mark and Sweep: 
    • Mark: 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업
    • Sweep: Mark 단계에서 사용되지 않음으로 식별된 메모리를 해제하는 작업
    • Stop The World를 통해 모든 작업을 중단시키면, GC는 스택의 모든 변수 또는 Reachable 객체를 스캔하면서 각각이 어떤 객체를 참고하고 있는지를 탐색하며 mark and sweep 작업을 진행함

algorithm

java11 default gc

-XX:+UseG1GC

힙을 격자/작은 지역으로 쪼개서 특정 지역별로 gc가 일어남.

그래서 예측가능하고 정지시간이 짧음,

성능이 좋고 메모리 효율이 좋음,

gc처리가 병렬처리 가능

튜닝 가능

 

java8 default gc

-XX:+UseParallelGC

G1GC가 자바7부터 나왔기 때문에 사용 가능 다만 기본값은 아니라는 점

 

주로 사용되는 GC

  1. G1 garbage collector(G1GC)
    1. java 7+ 사용가능
  2. Z garbage collector(ZGC)
    1. java 11+ 사용가능
  3. Parallel garbage collector
    1. java 5+ 사용가능
728x90
반응형

'개발 > java' 카테고리의 다른 글

[jmh] benchmark test  (0) 2022.08.04
[powermock] mock static method  (0) 2022.07.21
[keyword] transient in serialization  (0) 2022.02.16
[junit5] no test found / checked exception is invalid  (0) 2022.02.07
[junit] test runner  (0) 2022.01.03

+ Recent posts