본문 바로가기
개발 공부/Java

이펙티브 자바 아이템 6 - 불필요한 객체 생성을 피하라 - 완벽 공략

by 개발인생 2022. 9. 23.
반응형

아이템 6 - 불필요한 객체 생성을 피하라 - 완벽 공략

이 글은 백기선 님의 이펙티브 자바 강의와 이펙티브 자바 3 / E 편을 참고하여 작성하였습니다.

Deprecation

개발을 하다보면 해당 api 가 바뀌어야하는 경우가 생긴다.

클라이언트가 사용할 수도 있고, 클라리언트가 사용을 하고 있는 api 지만 사용 자제를 권장하는 경우가 있다.

public class Deprecation {

   // TODO 애노테이션 프로세서
   @Deprecated
   public Deprecation() {

   }
}

사용을 자제하고 싶은 api 에 @Deprecated 를 붙이면 컴파일러에서 표시가 된다.

public class Client {
  public static void main(String[] args) {
    Deprecation deprecation = new Deprecation();
  }
}

IDE에서 컴파일 경고를 표시해준다.

자바 9버전에서는

@Deprecated(forRemoval = true) 을 통해 강력하게 경고할 수 있다.

그러나 해당 코드를 사용할 수 있긴하다.

public class Deprecation {

   /**
    * @deprecated in favor of 
    * {@link #Deprecation(String)}
    */
   @Deprecated(forRemoval = true)
   public Deprecation() {

   }

   private String name;

   public Deprecation(String name) {
      this.name = name;
   }
}

위와 같이 @deprecated 를 사용해 자바 문서에 자세히 설명할 수 있다.

@link 어노테이션을 통해 해당 api 의 대안을 링크할 수 있다.


정규 표현식

정규 표현식이 쓰일만한 곳을 알아보자.

크게 3곳이 있다.

  • String.matches(String regex)
  • String.split(String regex)
    • 대안 : Pattern.compile(regex).split(str)
  • String.replace*(String regex, String replacement)
    • 대안 : Pattern.compile(regex).matcher(str).replaceAll(repl)
public class RegularExpression {
   private static final Pattern SPLIT_PATTERN = Pattern.compile(",");

   public static void main(String[] args) {
      long start = System.nanoTime();
      for (int j = 0; j < 10000; j++) {
         String name = "keesun,whiteship";
         name.split(",");
//            SPLIT_PATTERN.split(name);
      }
      System.out.println(System.nanoTime() - start);
   }
}

replaceAll 과 replaceFirst 의 첫번째 인자가 정규 표현식이다.

split 은 한글자만 인자를 보낼때

위에서 보면 "," 를 인자로 보내는 경우에는 패턴을 미리 만들어놓지 않더라도 속도가 빠르다.

split 내부에서 한글자만 인자로 왔을때는 빠르게 처리가 되도록 로직이 작성되어 있다.

하지만 name.split(";;;"); 같은 경우는 패턴을 재사용하는게 속도가 빠르다.


가비지 컬렉션

Mark

더 이상 오브젝트가 참조를 가지고 있는지 체크를 하는 것이다.

앞으로 사라져도 되는 인스턴스인지 체크를 하는 것이다.

이걸 Mark 를 하는 과정이다.

Sweep

필요없는 오브젝트를 메모리 공간(Heap) 에서 날리는 것이다.

Compact

메모리 공간내에 중간 중간 오브젝트가 저장된 공간이 비어버리면 (파편화)

큰 메모리 공간을 사용할 수가 없다.

이걸 방지하기 위해서 부분 부분 나누어져 있는 메모리 공간들을 정리하는 개념이다.

책꽂이에 듬성 듬성 꽂아져 있는 책들을 한 곳으로 정리하여 큰 부피의 책을 넣읋 수 있게 하는 것과 같다.

Young Generation (Eden, S0, S1), Old Generation

객체들은 보통 주기가 굉장히 짧다.

오래 살아남는 객체가 그렇게 많지 않다는 뜻이다.

오래 살아남는 객체만 Old Generation 에서 인스턴스가 관리된다.

Young Generation 영역에서 최초 생성되는 인스턴스를 Eden 의 공간에 인스턴스를 만들고

Eden 이 가득차면 가비지 컬렉션 상에서 오류가 발생한다.

이때 GC가 일어나 S0 영역으로 인스턴스로 옮기면서 필요없는 오브젝트를 정리한다.

S0, S1 의 이름과 순서는 중요하지 않는다.

물을 두가지 통에 부어가면서 정리하는 작업이라 생각하면 된다.

이 과정에서 계속 살아남는 객체는 Old Generation 에 넘어가게 된다.

Minor GC, Full GC

Minor GCYoung Generation 에서 일어나는 GC 를 의미한다.

Full GCYoung GenerationOld Generation 에서 같이 일어나는 대규모 GC 를 의미한다.

Full GC 가 매우 중요하다.

Full GC 에는 여러가지 로직이 있는데,

자바 8에서는 Parallel GC 가 기본이다.

Serial, Parallel, CMS, G1, ZGC, Shenandoah

자바에서 사용하는 GC 들이다.

GC 들을 보는 관점은 세가지가 있다.

Throughput, Latency (Stop-The-World), Footprint

GC 을 볼 때 중요한 관점 3가지이다.

Throughput 은 애플리케이션이 처리할 수 있는 처리량이다.

서버의 역량을 100이라고 봤을 때 애플리케이션이 100을 다 사용하면 Throughput 좋은 것이다.

GC 가 애플리케이션이 동작하는 중간에 리소스 일부를 계속 사용할 수도 있다.

얼마나 GC 가 서버의 Throughput 을 사용하느냐를 유념하자.

Stop-The-World 는 애플리케이션 서버가 멈추는 현상을 말한다.

GC 가 일어나는 경우에는 서버가 멈추게 된다.

GC 만 일하게 되는 경우를 말한다.

GC 가 작업을 마무리하기 전에는 요청 처리를 할 수 없다.

Stop-The-World 의 시간이 길어질수록 시스템 장애에 큰 영향을 미친다.

어떻게하면 Latency 를 줄일 수 있을지가 성능 개선의 핵심이다.

요즘 나온 새로운 GC 들은 메모리를 얼마나 쓰던 GC 의 시간이 거의 늘어나지 않는다.

Serial, Parallel GC 는 메모리 관점에서 그렇게 좋은 GC 는 아니다.

FootprintGC 가 얼마만큼의 저장 공간을 필요로 하느냐 이다.

GC 때문에 필요한 메모리 공간을 의미한다.

이 세가지 중에서 Latency 가 특히 중요하다.

  • 자바 8 : Serial GC 가 기본이다.
  • 자바 11 : G1 GC 가 기본이다.

물론 설정에 따라 다른 GC 를 사용할 수 있다.

지금까지 나온 GC 중에서는 ZGC 가 가장 발전된 GC 라 생각되어 진다.

참고로 CMS 는 Deprecated 되었다.

반응형

댓글