使垃圾收集器更快地放弃[重复]

Posted

技术标签:

【中文标题】使垃圾收集器更快地放弃[重复]【英文标题】:Making the garbage collector give up faster [duplicate] 【发布时间】:2017-08-26 10:40:11 【问题描述】:

我正在考虑用 Java 编写一个程序,该程序将重复尝试计算,注意它会耗尽内存,更改计算,重试直到成功。 (内存不足是不可避免的;粗略地说,我的想法就像基因编程,你不能总是提前知道生成的程序是否会耗尽内存。)所以看看主循环是否会出现内存不足内存错误。

以下简单的测试程序:

public static void main(String[] args) 
    HashMap<String, Integer> hashMap = new HashMap<>();
    for (int i = 0; i < 1000000000; i++) 
        hashMap.put(Integer.toBinaryString(i), i);
    
    System.out.println(hashMap.size());

内存不足并按预期以未捕获的异常退出,但这大约需要 20 分钟,其中大部分时间似乎都花在了垃圾收集器努力寻找足够的内存以继续运行。

我如何告诉垃圾收集器我预计内存不足并且应该提前放弃?

【问题讨论】:

为什么?您希望它在没有内存的情况下耗尽内存吗? @EJP 你可以这样说。我的说法是,当它 90% 的内存不足并且几乎肯定会完全耗尽时,我希望它尽早放弃,而不是花费 20 分钟的 CPU 时间来打磨剩余的 10%。 【参考方案1】:

我不知道通知垃圾收集器提前通知内存不足的特定方法

但下面的技巧可以立即通知您内存不足错误

为 HashMap 定义一个 initialCapacity,运行时会出现out of memory error

HashMap<String, Integer> hashMap = new HashMap<>(1000000000); // define initialCapacity
            for (int i = 0; i < 1000000000; i++) 
                  hashMap.put(Integer.toBinaryString(i), i);
            
            System.out.println(hashMap.size());

【讨论】:

Sahi hai Raju ji :) 在这个测试用例中,是的,但在实践中,事先并不知道要添加多少元素。 所需内存是HashMap大小的75倍。所以,当 HashMap 被构造时,不能保证它不会面对OutOfMemoryError @rwallace 如果事先不知道,你将如何告诉垃圾收集器?这是不可能的 @rwallace 你应该猜一个大概的容量。否则,当 HashMap 调整大小时,您将产生大量垃圾。【参考方案2】:

您可以计算所需的内存量并将其与Runtime.getRuntime().freeMemory() 进行比较,以了解您的算法是否有足够的可用内存。要计算所需的内存,您必须考虑:

HashMap 的容量,正如 Raju 所说,必须传递给 HashMap 构造函数以防止产生垃圾 样本中二进制字符串的平均大小约为 29 个字符或 58 个字节 每个项目的Integer 的大小 任何Object 的引用大小,包括StringsIntegers

例如,以下代码需要大约 75 GB 内存:

    HashMap<String, Integer> hashMap = new HashMap<>(1000000000);
    for (int i = 0; i < 1000000000; i++) 
        hashMap.put(Integer.toBinaryString(i), i);
    
    System.out.println(hashMap.size());

此外,如果您动态更改 Map 的大小并且未确定其大致容量,则不应使用 HashMap,因为它的大小调整会产生垃圾,并且您可能会在循环期间多次调整大小。相反,您应该使用另一个不会产生垃圾的Map,例如TreeMap

【讨论】:

当然,将固定数量的元素添加到哈希表中很容易预先计算所需的内存,但这只是一个微不足道的测试程序。对于真实的代码,提前计算内存是不可判定的。 @rwallace 我编辑了我的答案,建议使用TreeMap 而不是HashMap 啊,你是对的,切换到TreeMap 确实使测试程序运行速度快了一个数量级,大概是因为这个原因。有趣! 很高兴听到这个消息:)【参考方案3】:

只是不要这样做。不要让 JVM 抛出 OOME,甚至不要让它接近它,因为它总是浪费时间。

不要等待 OOME,而是定期或在循环中询问 Runtime.getRuntime().freeMemory(),并在您太接近零之前退出(这需要一些实验)。


不知道调用freeMemory() 的开销是多少,但如果您需要在一个紧密的循环中使用它,您可以随时执行类似的操作

if (i % 1000 == 0) 
    ... do the test

(或使用 i &lt;&lt; 22 == 0 作为模 1024 的更快测试)。

我可能会选择计时器。您还可以使用GarbageCollectorMXBean 来确定在 GC 中花费了多少时间(这并不完全是用户友好的)。


我不会尝试计算预期的内存使用量,因为当您的代码更改时,这很难并且很容易中断。调整地图和类似的技巧肯定很有用,但不要太努力,因为收益有限。考虑改进算法和/或使用更紧凑的数据结构 - 这也很困难,但可能会收获更多。


我想告诉垃圾收集器,当内存达到 90% 满时——这是无法事先预测的,但当它发生时很容易被 GC 测量——它应该放弃

当然,只是不要打扰 GC,自己测量内存并退出。

【讨论】:

以上是关于使垃圾收集器更快地放弃[重复]的主要内容,如果未能解决你的问题,请参考以下文章

Java内存管理——垃圾收集

Garbage First(G1) 垃圾收集器

Java垃圾收集机制

Java 虚拟机原理 ——垃圾收集

C#中垃圾收集的根是啥[重复]

为啥 PHP 的垃圾收集器会降低性能,没有它如何管理内存?