garbage collection을 찾아서

Table of Contents

1 소개

자바 Garbage Collection 소스코드를 무턱대고 파보자.

https://git.openjdk.java.net/jdk 의 맨첫번째 커밋을 기준으로 본다 (hash 686d76f7721)

2 nsk.share.gc.gp.GarbageUtils.java (메모리 테스트를 위한 소스코드)

Utility methods for garbage producers. 이라고 써있다.

2.1 eatMemory(final long timeout)

/**
  * Eat memory using execution controller that waits for timeout.
  * @return number of OOME occured
  */
public static int eatMemory(final long timeout) {
  return eatMemory(new ExecutionController() {
    final long initialTime = System.currentTimeMillis();

    @Override
    public void start(long stdIterations) {}

    @Override
    public boolean iteration() {return false;}

    @Override
    public boolean continueExecution() {
      return System.currentTimeMillis() - initialTime < timeout;
    }

    @Override
    public long getIteration() {return 0;}

    @Override
      public void finish() {}
  });
}

여기서 자연스럽게 ExecutionController 를 받는 eatMemory 를 알아봐야 한다.

2.2 eatMemory(ExecutionController stresser)

또 다른 함수를 부르고 있다. 기본적으로 쓰는 상수값이 들어가나보다.

/**
  * Eat memory using given execution controller and garbage producer.
  *
  * @param stresser execution controller
  * @param gp garbage producer
  * @return number of OOME occured
  */
public static int eatMemory(ExecutionController stresser) {
   return eatMemory(stresser, byteArrayProducer, 50, 100, 2, OOM_TYPE.ANY);
}

2.3 eatMemory(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor, OOMTYPE type)

static int numberOfOOMEs = 0;

/**
 * Minimal wrapper of the main implementation. Catches any OOM
 * that might be thrown when rematerializing Objects when deoptimizing.
 *
 * It is Important that the impl is not inlined.
 */
private static MethodType mt = MethodType.methodType(
  int.class,
  ExecutionController.class,
  GarbageProducer.class,
  long.class,
  long.class,
  long.class,
  OOM_TYPE.class);

public static int eatMemory(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor, OOM_TYPE type) {
  try {
    // Using a methodhandle invoke of eatMemoryImpl to prevent inlining of it
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    // 자세히보면 static method eatMemoryImpl을 호출한다는 것을 알 수 있다.
    MethodHandle eat = lookup.findStatic(GarbageUtils.class, "eatMemoryImpl", mt);
    return (int) eat.invoke(stresser, gp, initialFactor, minMemoryChunk, factor, type);
  } catch (OutOfMemoryError e) {
    return numberOfOOMEs++;
  } catch (Throwable t) {
    throw new RuntimeException(t);
  }
}

얘도 다른 함수를 호출한다.

2.4 eatMemoryImpl(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor, OOMTYPE type)

eat메모리에 대한 설명을 보자. OOM을 피핳기 위해 시작하기 전에 preallocate(미리할당)한다고 한다. 아래 catch를 보면 테스트용임을 알 수 있다.

/**
 * Eat memory using given garbage producer.
 *
 * Note that this method can throw Failure if any exception
 * is thrown while eating memory. To avoid OOM while allocating
 * exception we preallocate it before the lunch starts. It means
 * that exception stack trace does not correspond to the place
 * where exception is thrown, but points at start of the method.
 *
 * @param stresser stresser to use
 * @param gp garbage producer
 * @param initialFactor determines which portion of initial memory initial chunk will be
 * @param minMemoryChunk determines when to stop
 * @param factor factor to divide the array size by. A value of 0 means that method returns after first  OOME
 * @param type of OutOfMemory Exception: Java heap space or Metadata space
 * @return number of OOME occured
 */
public static int eatMemoryImpl(ExecutionController stresser, GarbageProducer gp, long initialFactor, long minMemoryChunk, long factor, OOM_TYPE type) {
  numberOfOOMEs = 0;
  try {
    byte[] someMemory = new byte[200000]; //200 Kb
    try {
      Runtime runtime = Runtime.getRuntime();
      long maxMemory = runtime.maxMemory();
      long maxMemoryChunk = maxMemory / initialFactor;
      long chunk = maxMemoryChunk;
      chunk = chunk > ALLOCATION_LIMIT ? ALLOCATION_LIMIT : chunk;
      int allocations = 0;
      List<Object> storage = new ArrayList<Object>();

      while (chunk > minMemoryChunk && stresser.continueExecution()) {
	try {
	  storage.add(gp.create(chunk));
	  if (Thread.currentThread().isInterrupted()) {
	    return numberOfOOMEs;
	  }
	  // if we are able to eat chunk*factor let
	  // try to increase size of chunk
	  if (chunk * factor < maxMemoryChunk
	      && factor != 0 && allocations++ == factor + 1) {
	    chunk = chunk * factor;
	    allocations = 0;
	  }
	} catch (OutOfMemoryError e) {
	  someMemory = null;
	  if (type != OOM_TYPE.ANY) {
	    if (type.accept(e.toString())) {
	      numberOfOOMEs++;
	    } else {
	      // Trying to catch situation when Java generates OOM different type that test trying to catch
	      throw new TestBug("Test throw OOM of unexpected type." + e.toString());
	    }
	  } else {
	    numberOfOOMEs++;
	  }
	  allocations = 0;
	  if (factor == 0) {
	    return numberOfOOMEs;
	  } else {
	    chunk = chunk / factor;
	  }
	}
      }
    } catch (OutOfMemoryError e) {
	someMemory = null;
	if (type != OOM_TYPE.ANY) {
	  if (type.accept(e.toString())) {
	    numberOfOOMEs++;
	  } else {
	    // Trying to catch situation when Java generates OOM different type that test trying to catch
	    throw new TestBug("Test throw OOM of unexpected type." + e.toString());
	  }
	} else {
	    numberOfOOMEs++;
	}
	// all memory is eaten now even before we start, just return
    }
  } catch (OutOfMemoryError e) {
	  numberOfOOMEs++;
  }
  return numberOfOOMEs;
}

3 sun.hotspot.WhiteBox

VM의 여러가지 정보를 보여주나보다. 몇가지 복사해서 붙여넣어보자

public native int  getHeapOopSize();
public native int  getVMPageSize();
public native long getVMAllocationGranularity();
public native long getVMLargePageSize();
public native long getHeapSpaceAlignment();
public native long getHeapAlignment();

private native boolean isObjectInOldGen0(Object o);
public         boolean isObjectInOldGen(Object o) {
  Objects.requireNonNull(o);
  return isObjectInOldGen0(o);
}

private native long getObjectSize0(Object o);
public         long getObjectSize(Object o) {
  Objects.requireNonNull(o);
  return getObjectSize0(o);
}

// Runtime
// Make sure class name is in the correct format
public int countAliveClasses(String name) {
  return countAliveClasses0(name.replace('.', '/'));
}
private native int countAliveClasses0(String name);

public boolean isClassAlive(String name) {
  return countAliveClasses(name) != 0;
}

뭔가 좋은 힌트를 얻은 것 같다.

3.1 sun.hotspot.gc.GC.java

이곳에 GC를 선택하는 코드들이 있다. #+BEGINSRC java /*

4 Enum values must match CollectedHeap::Name

*/ Serial(1), Parallel(2), G1(3), Epsilon(4), Z(5), Shenandoah(6); #+ENDSRC

이런 코드도 있다.

/**
 * @return the selected GC.
 */
public static GC selected() {
  for (GC gc : values()) {
    if (gc.isSelected()) {
      return gc;
    }
  }
  throw new IllegalStateException("No selected GC found");
}

4.1 com.sun.tools.attach.VirtualMachin.java

correto에 있는 설명을 보자.

A VirtualMachine represents a Java virtual machine to which this Java virtual machine has attached. The Java virtual machine to which it is attached is sometimes called the target virtual machine, or target VM.

An application (typically a tool such as a managemet console or profiler) uses a VirtualMachine to load an agent into the target VM. For example, a profiler tool written in the Java Language might attach to a running application and load its profiler agent to profile the running application.

아 보아하니 profiler같이 VM에 붙기 위해 attach 패키지가 존재하고 붙었을 때의 VM을 지칭하기 위한 객체가 attach의 VirutalMachine이다.

여기까지만 봐도 될 듯.

4.2 com.sun.tools.javac.jvm

여기는 javac 컴파일을 위한 것이겠지?

4.3 jdk/src/hotspot/share/gc/serial

4.3.1 markSweep.cpp

WIP

Date: 2021-03-15 Mon 00:00

Author: Younghwan Nam

Created: 2022-11-15 Tue 08:10

Emacs 27.2 (Org mode 9.4.4)

Validate