【问题标题】:Detect low available memory in Java检测 Java 中的可用内存不足
【发布时间】:2020-09-23 12:21:26
【问题描述】:

我想检测可用内存何时不足(例如 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,为什么我可以分配更少?它不应该只影响初始堆大小吗?

【问题讨论】:

    标签: java memory-management out-of-memory


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

    ..

    为了更好地理解 JVM 版本之间的内存分解,查看一些可视化可能会有所帮助。下面的图表显示了内存分解,因为内存是由与上面包含在 Java 8 和 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 中使用的相同垃圾收集器(并行 GC)时,堆的增长方式与 Java 8 中的情况类似。一个不同之处在于,在 Java 15 中,所有内存都在失败之前被消耗内存不足。这表明,当需要更多堆空间时,所使用的 GC 机制和参数都会影响堆的增长量以及可以使用的总有效堆容量。通过在 Java 8 中使用 Serial GC 运行相同的测试进一步证明了这一点,在这种情况下,所有内存都得到了正确使用。在示例测试中,将 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)); }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-05
      • 1970-01-01
      • 2010-11-22
      • 2018-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多