อาร์เรย์ Java byte ขนาด 1 MB หรือมากกว่านั้นใช้ RAM เป็นสองเท่า


14

เล่นโค้ดด้านล่างบน Windows 10 / OpenJDK 11.0.4_x64 ผลิตเป็นผลผลิตและused: 197 expected usage: 200ซึ่งหมายความว่าอาร์เรย์ 200 ไบต์ของหนึ่งล้านองค์ประกอบใช้เวลาประมาณ RAM 200MB ทุกอย่างดี

เมื่อฉันเปลี่ยนการจัดสรรอาร์เรย์ไบต์ในรหัสจาก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

ในกรณีที่สองนอกเหนือจากอาร์เรย์ไบต์ฉันเห็นจำนวน int อาร์เรย์เดียวกันที่ใช้ RAM ในปริมาณเท่ากันกับอาร์เรย์ไบต์:

int อาร์เรย์ใช้เวลาเพิ่มขึ้น 200mb

โดยวิธีอาร์เรย์เหล่านี้ไม่ได้แสดงว่าพวกเขามีการอ้างอิง แต่ฉันไม่สามารถเก็บรวบรวมขยะพวกเขา ... (อาร์เรย์ไบต์แสดงเพียงแค่ปรับที่พวกเขามีการอ้างอิง)

ความคิดใด ๆ ที่เกิดขึ้นที่นี่


ลองเปลี่ยนข้อมูลจาก ArrayList <byte []> เป็น byte [blocks] [] และใน for for loop ของคุณ: data [i] = new byte [1000000] เพื่อกำจัดการพึ่งพาภายในของ ArrayList
jalynn2

มันมีบางอย่างที่เกี่ยวข้องกับ JVM ภายในโดยใช้การint[]เลียนแบบขนาดใหญ่byte[]สำหรับพื้นที่เชิงพื้นที่ที่ดีกว่าหรือไม่?
Jacob G.

@JacobG แน่นอนมันมีลักษณะบางอย่างภายใน แต่มีไม่ดูเหมือนจะเป็นตัวบ่งชี้ใด ๆ ในคู่มือ
Kayaman

เพียงสองข้อสังเกต: 1. หากคุณลบ 16 จาก 1024 * 1024 ดูเหมือนว่าจะทำงานได้ตามที่คาดไว้ 2. พฤติกรรมของ jdk8 ดูเหมือนจะแตกต่างจากนั้นสิ่งที่สามารถสังเกตได้ที่นี่
ครั้งที่สอง

@ วินาทีใช่ข้อ จำกัด ที่เห็นได้ชัดคือว่าอาเรย์ใช้แรม 1MB หรือไม่ ฉันสมมติว่าถ้าคุณลบล้างเพียง 1 หน่วยความจำนั้นจะเพิ่มประสิทธิภาพในการรันไทม์และ / หรือค่าใช้จ่ายในการจัดการสำหรับอาร์เรย์นับเป็น 1MB ... ตลกที่ JDK8 นั้นทำงานต่างกัน!
เฟรดริก

คำตอบ:


9

สิ่งนี้อธิบายได้ว่าเป็นพฤติกรรมนอกกล่องเก็บขยะ G1ซึ่งโดยปกติแล้วจะเริ่มต้นที่ "ภูมิภาค" 1MB และกลายเป็นค่าเริ่มต้น JVM ใน Java 9 การเปิดใช้งานด้วย GCs อื่นเปิดใช้งานให้ตัวเลขที่แตกต่างกัน

วัตถุใด ๆ ที่มีขนาดพื้นที่เกินกว่าครึ่งถือว่าเป็น "humongous" ... สำหรับวัตถุที่มีขนาดใหญ่กว่าขนาดพื้นที่ฮีปหลายเท่าเล็กน้อยพื้นที่ที่ไม่ได้ใช้นี้อาจทำให้ฮีปนั้นแตกเป็นชิ้น ๆ

ฉันวิ่ง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


ฉันมีปัญหาเดียวกันกับอนุกรม GC และอาร์เรย์ยาวที่ใช้เวลา 8MB (และใช้ได้กับขนาด 1024-1024-2) และการเปลี่ยน G1HeapRegionSize ไม่ได้ทำอะไรเลยในกรณีของฉัน
GotoFinal

ฉันไม่แน่ใจในเรื่องนี้ คุณช่วยอธิบายการเรียกใช้จาวาและเอาท์พุทของโค้ดด้านบนด้วยความยาว []
drekbour

@GotoFinal ฉันไม่ได้สังเกตปัญหาใด ๆ ที่ไม่ได้อธิบายข้างต้น ฉันทดสอบโค้ดlong[1024*1024]ซึ่งให้การใช้งานที่คาดหวังไว้ที่ 1600M ด้วย G1 ซึ่งแตกต่างกันไปตาม-XX:G1HeapRegionSize[1M ที่ใช้: 1887, 2M ที่ใช้: 2097, 4M ที่ใช้: 3358, 8M ที่ใช้: 3358, 16M ที่ใช้: 3363, 32M ที่ใช้: 1682] กับการ-XX:+UseConcMarkSweepGCใช้: 1687 กับการ-XX:+UseZGCใช้: 2105 กับการ-XX:+UseSerialGCใช้: 1698
drekbour

gist.github.com/c0a4d0c7cfb335ea9401848a6470e816รหัสเช่นเดียวกับที่โดยไม่ต้องเปลี่ยนตัวเลือก GC ใด ๆ ก็จะพิมพ์used: 417 expected usage: 400แต่ถ้าผมจะลบว่า-2มันจะเปลี่ยนไปused: 470เพื่อให้รอบ 50MB จะหายไปและ 50 * 2 longs แน่นอนมากน้อยกว่า 50 MB
GotoFinal

1
สิ่งเดียวกัน ความแตกต่างคือ ~ 50MB และคุณมีบล็อค "humongous" 50 อัน นี่คือรายละเอียดของ GC: 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->400มันพิสูจน์ได้ว่าสองค่าสุดท้ายนั้นบังคับให้ G1 จัดสรรพื้นที่ 1MB อีก 1 อันเพื่อเก็บ 16 ไบต์ใน
drekbour
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.