1 MB veya daha fazla Java bayt dizisi RAM'in iki katını kaplar


14

Aşağıdaki kodu Windows 10 / OpenJDK 11.0.4_x64 üzerinde çalıştırmak çıktı olarak üretir used: 197ve expected usage: 200. Bu, bir milyon elementten 200 baytlık dizilerin yaklaşık olarak kapladığı anlamına gelir. 200 MB RAM. Herşey yolunda.

Koddaki bayt dizisi ayırmasını new byte[1000000]olarak new byte[1048576](yani 1024 * 1024 öğelerine) değiştirdiğimde, çıktı used: 417ve olarak üretilir expected usage: 200. Ne halt?

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 ile biraz daha derinlere baktığımda, ilk durumda her şeyi beklendiği gibi görüyorum:

bayt dizileri 200mb alır

İkinci durumda, bayt dizilerine ek olarak, bayt dizileriyle aynı miktarda RAM alan aynı sayıda int dizisini görüyorum:

int dizileri ek 200 mb alır

Bu int diziler, bu arada, referans alındığını göstermezler, ancak onları çöp toplayamam ...

Burada neler olduğu hakkında bir fikrin var mı?


ArrayList <bayt []> 'dan bayt [bloklar] []' a ve for döngüsü içinde verileri değiştirmeyi deneyin: veri [i] = yeni bayt [1000000], ArrayList'in içindeki bağımlılıkları ortadan kaldırmak için
jalynn2

Daha iyi bir uzamsal yerellik için int[]büyük bir öykünmek için bir kullanarak JVM ile ilgili bir şey olabilir byte[]mi?
Jacob G.

@JacobG. kesinlikle dahili bir şey gibi görünüyor, ancak rehberde herhangi bir belirti yok gibi görünüyor .
Kayaman

Sadece iki gözlem: 1. 1024 * 1024'ten 16 çıkarırsanız, beklendiği gibi çalışır. 2. jdk8 ile davranış, burada gözlemlenenden farklı görünmektedir.
ikinci

@second Evet, sihirli sınır, dizinin 1MB RAM alıp almayacağıdır. Ben sadece 1 özetlemek varsa, o zaman bellek çalışma zamanı verimliliği için yastıklı olduğunu ve / veya dizi için yönetim yükü 1MB sayar ... JDK8 farklı davranır komik!
Georg

Yanıtlar:


9

Bu, genellikle 1MB "bölgelerini" varsayılan olarak kullanan ve Java 9'da JVM varsayılanı haline gelen G1 çöp toplayıcısının kullanıma hazır davranışıdır .

bölge büyüklüğünün yarısından daha fazla olan herhangi bir nesne "humongo" olarak kabul edilir ... Yığın bölge boyutunun katlarından biraz daha büyük olan nesneler için, bu kullanılmayan alan yığının parçalanmasına neden olabilir.

Koştum java -Xmx300M -XX:+PrintGCDetailsve yığının humongo bölgeler tarafından tükendiğini gösteriyor:

[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

Bizim 1MiB istiyoruz byte[]"yarısından az G1 bölge boyutu" öylesine ekleyerek olmak -XX:G1HeapRegionSize=4Mfonksiyonel bir uygulama verir:

[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'e genel bakış: https://www.oracle.com/technical-resources/articles/java/g1gc.html

G1'in ezici detayı: https://docs.oracle.com/tr/java/javase/13/gctuning/garbage-first-garbage-collector-tuning.html#GUID-2428DA90-B93D-48E6-B336-A849ADF1C552


Seri GC ve 8MB (ve 1024-1024-2 boyutu ile iyiydi) uzun dizi ile aynı sorunları var ve G1HeapRegionSize değiştirme benim durumumda bir şey yapmadı
GotoFinal

Bu konuda net değilim. Kullanılan java çağırma ve yukarıdaki kodun çıktısını uzun bir []
drekbour

@GotoFinal, yukarıda açıklanmayan herhangi bir sorun gözlemlemiyorum. long[1024*1024]Kullanılan 1600M G1 ile beklenen kullanımı veren kodu test ettim -XX:G1HeapRegionSize. İle -XX:+UseConcMarkSweepGCkullandı: 1687 ile -XX:+UseZGCkullandı: 2105. With -XX:+UseSerialGCkullandı: 1698
drekbour

gist.github.com/c0a4d0c7cfb335ea9401848a6470e816 sadece böyle bir kod yazmadan , herhangi bir GC seçeneğini değiştirmeden yazdıracak, used: 417 expected usage: 400ancak 50MB civarında -2değişeceğini kaldıracağım used: 470ve 50 * 2
uzunluğunda

1
Aynı şey. Fark ~ 50MB ve 50 "humongo" blokunuz var. İşte GC detayı: 1024 * 1024 -> [0.297s][info ][gc,heap ] GC(18) Humongous regions: 450->4501024 * 1024-2 -> [0.292s][info ][gc,heap ] GC(20) Humongous regions: 400->400Son iki uzunluğun G1'in sadece 16 bayt depolamak için başka bir 1MB bölge tahsis etmesini kanıtlıyor.
drekbour
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.