[java] 1MB 이상의 Java 바이트 배열은 RAM의 두 배를 차지합니다.

윈도우 10에 아래의 코드를 실행 / 오픈 JDK 11.0.4_x64는 출력으로 생산 used: 197하고 expected usage: 200. 이는 백만 개의 요소로 구성된 200 바이트 배열이 대략적으로 사용됨을 의미합니다. 200MB RAM. 다 괜찮아.

I로부터 코드에서 바이트 배열의 할당을 변경하는 경우 new byte[1000000]new byte[1048576](1024 개 * 1024 요소이라고)는 출력으로서 생성 used: 417하고 expected usage: 200. 이런 젠장?

import java.io.IOException;
import java.util.ArrayList;

public class Mem {
    private static Runtime rt = Runtime.getRuntime();
    private static long free() { return rt.maxMemory() - rt.totalMemory() + rt.freeMemory(); }
    public static void main(String[] args) throws InterruptedException, IOException {
        int blocks = 200;
        long initiallyFree = free();
        System.out.println("initially free: " + initiallyFree / 1000000);
        ArrayList<byte[]> data = new ArrayList<>();
        for (int n = 0; n < blocks; n++) { data.add(new byte[1000000]); }
        System.gc();
        Thread.sleep(2000);
        long remainingFree = free();
        System.out.println("remaining free: " + remainingFree / 1000000);
        System.out.println("used: " + (initiallyFree - remainingFree) / 1000000);
        System.out.println("expected usage: " + blocks);
        System.in.read();
    }
}

visualvm으로 조금 더 깊이 살펴보면 첫 번째 경우 모든 것이 예상대로 보입니다.

바이트 배열은 200MB를 차지합니다

두 번째 경우에는 바이트 배열 외에도 바이트 배열과 동일한 양의 RAM을 차지하는 동일한 수의 int 배열이 표시됩니다.

int 배열은 추가 200MB를 차지합니다.

그런데이 int 배열은 참조 된 것을 나타내지 않지만 가비지 수집 할 수는 없습니다 … (바이트 배열은 참조 된 곳에서 잘 나타납니다.)

어떤 아이디어가 여기서 일어나고 있습니까?



답변

이것이 설명하는 것은 G1 가비지 수집기의 기본 동작 으로 일반적으로 1MB “regions”로 기본 설정되며 Java 9에서는 JVM 기본값이되었습니다. 다른 GC를 사용하여 실행하면 다양한 숫자가 나타납니다.

영역 크기의 절반보다 큰 개체는 “풍선”으로 간주됩니다. 힙 영역 크기의 배수보다 약간 큰 개체의 경우이 사용되지 않는 공간으로 인해 힙이 조각화 될 수 있습니다.

나는 달렸고 java -Xmx300M -XX:+PrintGCDetails그것은 거대한 지역에 의해 힙이 소진되었음을 보여줍니다.

[0.202s][info   ][gc,heap        ] GC(51) Old regions: 1->1
[0.202s][info   ][gc,heap        ] GC(51) Archive regions: 2->2
[0.202s][info   ][gc,heap        ] GC(51) Humongous regions: 296->296
[0.202s][info   ][gc             ] GC(51) Pause Full (G1 Humongous Allocation) 297M->297M(300M) 1.935ms
[0.202s][info   ][gc,cpu         ] GC(51) User=0.01s Sys=0.00s Real=0.00s
...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

우리는 1MiB byte[]가 “G1 영역 크기의 절반 미만”이 되기를 원 하므로 추가 -XX:G1HeapRegionSize=4M하면 기능적 응용이 가능합니다.

[0.161s][info   ][gc,heap        ] GC(19) Humongous regions: 0->0
[0.161s][info   ][gc,metaspace   ] GC(19) Metaspace: 320K->320K(1056768K)
[0.161s][info   ][gc             ] GC(19) Pause Full (System.gc()) 274M->204M(300M) 9.702ms
remaining free: 100
used: 209
expected usage: 200

G1에 대한 심층적 인 개요 : https://www.oracle.com/technical-resources/articles/java/g1gc.html

G1에 대한 자세한 내용 : https://docs.oracle.com/en/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552


답변