在Java中检测可用内存的低水平。

3
我希望能够检测空闲内存是否不足(比如只剩15%的空闲内存),并且采取一些措施。
我曾尝试使用Runtime内存指标,但好像表现不一致。

这是我的测试应用:

import java.util.*;

public class Memory
{
    static final int BYTES_PER_MB = 1024*1024;
    
    public static void main(String[] args) {
        
        Queue<byte[]> q = new LinkedList<>();
        int allocated = 0;
        while (true) {
            q.add(new byte[BYTES_PER_MB * 100]);
            allocated += 100;
            System.out.printf("Allocated 100m (total allocated=%d)%n", allocated);
            printMem();
        }
    }
        
    public static void printMem() {
        long max = Runtime.getRuntime().maxMemory() / BYTES_PER_MB;
        long total = Runtime.getRuntime().totalMemory() / BYTES_PER_MB;
        long free = Runtime.getRuntime().freeMemory() / BYTES_PER_MB;
        long totalFree = free+(max-total); // allocated free + memory that can be allocated
        System.out.printf("MEM: max=%dm, total=%dm, free=%d, (total free=%d)%n", max, total, free, totalFree);
    }
}

这是我所得到的结果:

JVM                                      | Command                         | Total free before OOM
--------------------------------------------------------------------------------------------------
Oracle Java 1.8.0_191-b12 64-Bit Server  | java -Xmx1024m -Xms10m Memory   | 309
Oracle Java 1.8.0_191-b12 64-Bit Server  | java -Xmx1024m Memory           | 205
Oracle Java 11.0.2+9-LTS  64-Bit Server  | java -Xmx1024m -Xms10m Memory   | 12
Oracle Java 11.0.2+9-LTS  64-Bit Server  | java -Xmx1024m Memory"          | 12

针对Java 11的结果是有意义的,但我不理解Java 8的结果。

  1. 为什么尽管我有足够的内存,我仍然无法分配空间?(我认为这不是碎片化问题,因为堆主要由100m的块组成,并且如果我按10m的块进行分配也会失败)
  2. 如果我指定一个较低的-Xmx,为什么我可以分配更少的内存?难道这只影响初始堆大小吗?
1个回答

4
  1. Java 8和Java 11之间默认的GC行为不同。调整GC参数是提高内存效率的一种方式。在本地测试中,将“-XX:+ UseAdaptiveGCBoundary”设置为Java 8会导致OOM接近堆内存限制。
  2. -Xmx控制允许分配给堆的最大内存量。初始堆大小可以通过-Xms进行控制。如果这两个值相同,则堆大小将固定为所设置的值。

为了更好地理解JVM版本之间的内存分配情况,查看一些可视化图表可能会有所帮助。以下图表显示了与上述类似程序在Java 8和Java 15中分配内存时的内存分配情况(源代码包含在下面):

Java 1.8的内存分配情况

Java 15的内存分配情况

使用与1.8相同的GC运行Java 15的内存分配情况

Legend Description for Charts:
1) Total Free - Free memory including already allocated free memory and memory which may still be requested from the system up to Max memory. (totFre in test)
2) JVM Allocated (Free) - The memory which is available for usage within the memory already allocated for the JVM (Total Memory above). (freMem in test)
3) JVM Allocated - The memory currently allocated for the JVM. This may vary as objects are created or more space is needed up to Max Memory. (totMem in test)
4) Max Memory - The maximum memory the JVM will allow itself to use. Can be configured via -Xmx flag. (maxMem in test)

为什么Java 8和15存在差异?

当Java 15配置使用与Java 8相同的垃圾收集器(Parallel GC)时,堆的增长方式类似于Java 8。其中一个区别是,在Java 15中,所有内存都会在内存不足时被占用,而不是直接抛出内存溢出错误。这表明,GC机制和所使用的参数影响了更多堆空间所需要的增长量以及可以使用的总有效堆容量。这在使用Serial GC在Java 8中运行相同测试的情况下得到了进一步证明,在这种情况下,所有内存将被正确使用。在示例测试中,将Parallel GC配置为使用'-XX:+UseAdaptiveGCBoundary'在故障之前提高了最大内存利用率。

..

测试源代码:

    public static void main(String[] args)
    {
        long totalProgramAllocated = 0;
        Queue<byte[]> dataQueue = new LinkedList<>();
        while (true)
        {
            System.out.println("Total Allocated: " + (totalProgramAllocated >> 20));
            byte[] nextAllocatedChunk = createArray(25);
            dataQueue.add(nextAllocatedChunk);
            totalProgramAllocated += nextAllocatedChunk.length;
            System.out.println(new MemoryUsage());
        }
    }

    private static final Random random = new Random();
    private static byte[] createArray(int megabytes){ byte[] data = new byte[(1 << 20) * megabytes]; random.nextBytes(data); return data; }
    
    public static final class MemoryUsage
    {
        public final long maxMem = Runtime.getRuntime().maxMemory();
        public final long totMem = Runtime.getRuntime().totalMemory();
        public final long freMem = Runtime.getRuntime().freeMemory();
        public final long totFre = maxMem - (totMem - freMem);
        @Override public String toString(){ return String.join(",", Long.toString(maxMem), Long.toString(totMem), Long.toString(freMem), Long.toString(totFre)); }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接