Java에서 쓰레기 수거기 GC가 처리량에 미치는 영향 테스트

메모리 관리 용어표를 보다가 우연히 "Pig in the Python(주: 중국어의 탐욕이 뱀이 코끼리를 삼키지 못하는 것 같다)"이라는 정의를 발견하고 이 글을 쓰게 되었다.표면적으로 보면 이 용어는 GC가 끊임없이 큰 대상을 한 세대에서 다른 세대로 끌어올리는 상황을 말한다.이렇게 하면 마치 구렁이가 사냥감을 통째로 삼켜 소화할 때 움직일 수 없게 하는 것과 같다.
다음 24시간 동안 내 머릿속에는 이 숨막히는 구렁이의 화면이 가득 차서 휘저을 수 없었다.정신과 의사가 말한 것처럼 두려움을 없애는 가장 좋은 방법은 말하는 것이다.그래서 이 문장이 생겼다.하지만 이어지는 이야기는 구렁이가 아니라 GC의 조우다.나는 하늘에 맹세한다.
GC 정지는 성능 병목을 일으키기 쉽다는 것을 모두가 알고 있다.현대 JVM은 출시할 때 모두 고급 쓰레기 수거기를 가지고 왔지만, 나의 사용 경험을 보면 어떤 응용 프로그램의 가장 좋은 설정을 찾아내는 것은 정말 어렵다.수동 조정은 여전히 한 가닥의 희망이 있을지도 모르지만, GC 알고리즘의 정확한 메커니즘을 알아야 한다.이 점에 관해서 본고는 오히려 당신에게 도움이 될 것입니다. 다음은 JVM 설정의 작은 변화가 어떻게 당신의 응용 프로그램의 토출량에 영향을 미치는지 예를 들어 설명하겠습니다.
예제
GC가 처리량에 미치는 영향을 보여주는 데 사용되는 응용 프로그램은 간단한 프로그램일 뿐이다.두 개의 스레드가 있습니다.
PigEater C는 구렁이가 뚱뚱한 돼지를 끊임없이 삼키는 과정을 모방한다.코드는java로 갑니다.util.목록에 32MB 바이트를 추가해서 이 점을 실현합니다. 매번 삼킨 후에 100ms를 자게 됩니다.
PigDigester C는 비동기적인 소화 과정을 시뮬레이션합니다.소화를 실현하는 코드는 돼지의 목록을 비어 있을 뿐이다.이것은 매우 힘든 과정이기 때문에, 인용을 제거할 때마다 이 라인은 2000ms를 잔다.
두 라인은 모두 while 순환 중에 운행하며 뱀이 배불리 먹을 때까지 끊임없이 소화를 한다.이것은 대략 5천 마리의 돼지를 먹어야 한다.

package eu.plumbr.demo;

public class PigInThePython {
  static volatile List pigs = new ArrayList();
  static volatile int pigsEaten = 0;
  static final int ENOUGH_PIGS = 5000;

  public static void main(String[] args) throws InterruptedException {
    new PigEater().start();
    new PigDigester().start();
  }

  static class PigEater extends Thread {

    @Override
    public void run() {
      while (true) {
        pigs.add(new byte[32 * 1024 * 1024]); //32MB per pig
        if (pigsEaten > ENOUGH_PIGS) return;
        takeANap(100);
      }
    }
  }

  static class PigDigester extends Thread {
    @Override
    public void run() {
      long start = System.currentTimeMillis();

      while (true) {
        takeANap(2000);
        pigsEaten+=pigs.size();
        pigs = new ArrayList();
        if (pigsEaten > ENOUGH_PIGS)  {
          System.out.format("Digested %d pigs in %d ms.%n",pigsEaten, System.currentTimeMillis()-start);
          return;
        }
      }
    }
  }

  static void takeANap(int ms) {
    try {
      Thread.sleep(ms);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

현재 우리는 이 시스템의 흡수량을'초당 소화할 수 있는 돼지의 두수'로 정의한다.100ms마다 돼지가 이 구렁이에 갇히는 것을 감안하면 우리는 이 시스템 이론상의 최대 흡수량이 10마리/초에 이를 수 있음을 볼 수 있다.
GC 구성 예
우리는 두 개의 서로 다른 설정 시스템을 사용하는 표현이 각각 어떤 것인지 보았다.어떤 구성이든 응용 프로그램은 듀얼 코어, 8GB 메모리를 가진 Mac(OS X10.9.3)에서 실행된다.
첫 번째 구성:
1.4G 더미(-Xms4g -Xmx4g)
2. CMS를 사용하여 이전 세대를 정리 (-XX:+UseConcMark SweepGC) 병렬 회수기를 사용하여 새 세대를 정리 (-XX:+UseParNewGC)
3. 더미의 12.5%(-Xmn512m)를 신생대에 분배하고 Eden 구역과 Survivor 구역의 크기를 동일하게 제한한다.
두 번째 구성은 약간 다릅니다.
1.2G 더미(-Xms2g -Xms2g)
2. 신생대와 구세대는 모두 Parellel GC 사용(-XX:+UseParallel GC)
3. 더미의 75%를 신생대에 분배한다(-Xmn 1536m)
4. 이제 내기를 해야 할 때다. 어떤 배치가 더 나을지(초당 돼지를 얼마나 먹을 수 있는지 기억하시죠)?칩을 첫 번째 배치에 넣은 녀석들은 실망할 것이다.결과는 정반대였다.
1. 첫 번째 배치(대더미, 오래된 연대, CMSGC)는 초당 8.2마리의 돼지를 삼킬 수 있다
2. 두 번째 배치(소더미, 큰 신생대, ParellelGC)는 초당 9.2마리의 돼지를 삼킬 수 있다
이제 우리는 이 결과를 객관적으로 한번 봅시다.할당된 자원은 2배 적지만 처리량은 12퍼센트 증가했다.상식과는 정반대이기 때문에 도대체 무슨 일이 일어났는지 좀 더 분석할 필요가 있다.
GC 분석 결과
원인은 사실 복잡하지 않다. 테스트를 실행할 때 GC가 무엇을 하는지 자세히 보면 답을 발견할 수 있다.이것은 네가 사용할 도구를 스스로 선택할 수 있다.jstat의 도움으로 나는 배후의 비밀을 발견했다. 명령은 대략 이렇다.

jstat -gc -t -h20 PID 1s
데이터 분석을 통해 구성 1은 총 63.723초의 GCT_FGCT(YGCT_FGCT)를 통해 1129차례의 GC주기를 거쳤음을 알 수 있습니다.

Timestamp        S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
594.0 174720.0 174720.0 163844.1  0.0   174848.0 131074.1 3670016.0  2621693.5  21248.0 2580.9   1006   63.182  116 0.236   63.419
595.0 174720.0 174720.0 163842.1  0.0   174848.0 65538.0  3670016.0  3047677.9  21248.0 2580.9   1008   63.310  117 0.236   63.546
596.1 174720.0 174720.0 98308.0 163842.1 174848.0 163844.2 3670016.0   491772.9  21248.0 2580.9   1010   63.354  118 0.240   63.595
597.0 174720.0 174720.0  0.0   163840.1 174848.0 131074.1 3670016.0   688380.1  21248.0 2580.9   1011   63.482  118 0.240   63.723
두 번째 구성은 총 168회(YGCT+FGCT) 중단되었고 11.409초밖에 걸리지 않았습니다.

Timestamp        S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT
539.3 164352.0 164352.0  0.0    0.0   1211904.0 98306.0   524288.0   164352.2  21504.0 2579.2 27    2.969  141 8.441   11.409
540.3 164352.0 164352.0  0.0    0.0   1211904.0 425986.2  524288.0   164352.2  21504.0 2579.2 27    2.969  141 8.441   11.409
541.4 164352.0 164352.0  0.0    0.0   1211904.0 720900.4  524288.0   164352.2  21504.0 2579.2 27    2.969  141 8.441   11.409
542.3 164352.0 164352.0  0.0 0.0   1211904.0 1015812.6  524288.0   164352.2  21504.0 2579.2 27 2.969  141 8.441   11.409
두 가지 상황에서 작업량이 같다는 것을 감안하면 이 돼지를 먹는 실험에서 GC가 장기적으로 살아남는 대상을 발견하지 못했을 때 쓰레기 대상을 더 빨리 제거할 수 있다.첫 번째 설정을 사용하면 GC의 운행 빈도는 6~7배 정도 되고, 전체 정지 시간은 5~6배 정도 된다.
이 이야기를 하는 데는 두 가지 목적이 있다.첫 번째도 가장 중요한 것이다. 나는 이 바람이 부는 구렁이를 서둘러 내 머릿속에서 쫓아내고 싶다.또 다른 뚜렷한 수확은 GC조우는 기교가 필요한 경험일이다. GC조우는 밑바닥의 이런 개념들을 손금 보듯 잘 알아야 한다는 것이다.비록 본고에서 사용한 이것은 매우 일반적인 응용일 뿐이지만, 선택의 서로 다른 결과도 당신의 흡수량과 용량 계획에 큰 영향을 미칠 것이다.현실 생활에서의 응용에서 이곳의 차이는 더욱 클 것이다.따라서 당신이 어떻게 선택하느냐에 따라 이러한 개념을 파악하거나 일상적인 일에만 관심을 가지면 됩니다. Plumbr로 하여금 당신이 필요로 하는 가장 적합한 GC 설정을 찾아내도록 하세요.

좋은 웹페이지 즐겨찾기