6

我目前正在测试我的概念证明原型处理 XML 模式,并围绕一个非常消耗内存的树自动机外部库(我有源​​)构建,我想绘制“真正的峰值”(堆) 随着模式大小的增加,不同运行的内存消耗(使用的指标符合我的目的并且不影响问题),或者至少是它的合理近似值。

为了给出一个数量级,对于实际峰值为 100MB 的运行(我测试了它多次运行完全相同的输入/参数配置,强制使用 -Xmx 和 -Xms 的 jvm 内存减小值,我得到Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded < 100MB, 结果稳定且可重复)它占用了大约 1.1GB,这就是为什么获取实数对我来说非常重要,因为它们相差很大!

在过去的 10 天里,我一直在网上和 stackoverflow 上阅读问题,我真正知道的是:

  1. System.gc() “建议” GC 运行,不会以任何方式强制它,因此不可能依赖它来检测内存使用峰值

  2. 通常建议的是计算对象占用(我为此看到了SizeOf项目,我尝试过并且工作正常,即使它不符合我的需要),这对我来说是不可行的,因为由于创建了一个许多集合(集合、列表和映射)迭代器以不同的方法调用,调用次数非常多(据我记忆,每次运行 10 分钟的次数为数百万次),因此检测所有涉及的对象将非常困难并执行总和(我在几天内用内存消耗图调试了很多次运行,但无法识别出只有一个瓶颈)

  3. 没有办法轻松获取方法的内存占用(表示为对象内存分配的峰值)

事实是我自己亲身经历过 System.gc() 调用是不可靠的(例如,相同配置的不同运行,由于 GC 是否真正被调用而在 System.gc() 之后读取的不同内存),但是当我按下 JVisualVM 或 Jconsole 中的“GC 按钮”,它永远不会运行 GC 或拒绝这样做。

所以我的问题是:调用他们对该按钮的实现(我还没有尝试过,但是到目前为止我已经阅读过,使用带有附加 api的 jconsole.jar 似乎是可行的)将不同于直接调用 System.gc()从我的代码,从而解决我的问题?如果不是,您如何解释该按钮的“确定性行为”?

到目前为止,我对实际内存峰值进行了一些手动测试,给出了 10 个不断增加的模式大小(对于这种测量,模式是从单个“复杂性参数”自动生成的),如果我不能,我绘制了预期的曲线获得更好的解决方案我想将我的代码作为外部 jar 运行,其中 -Xmx/-Xms 略小于我对预期内存峰值的预测,在外部进程 ErrorStream 中捕获 OutMemoryException 并使用增加的内存重新启动,直到完全运行已完成。(如果朴素的记忆预测不够稳健,我将应用适当的机器学习技术)。我知道这不是一个优雅的解决方案,但在我的场景(学术界)中,我可以花一些额外的时间进行这些测量。

系统信息(机器是 Fedora 17、64 位):

java 版本 "1.7.0_04" Java(TM) SE Runtime Environment (build 1.7.0_04-b20) Java HotSpot(TM) 64-Bit Server VM (build 23.0-b21, 混合模式)

提前致谢, 亚历山德罗

4

4 回答 4

4

据我所知,Jconsole 或任何其他工具仅使用 System.gc()。没有其他选择。众所周知,java告诉大家不要依赖System.gc(),但这并不意味着它根本不起作用。

因此,在进行查询时,您似乎担心按下该按钮如何直接调用 GC 并且仍然 java 说 System.gc 仅“建议”调用 GC。我说,那个按钮也调用 System.gc() 并且它只是“建议”java 尝试 GC,并且它以某种方式发生,java 决定在那时自己执行 GC(它不能保证,但 java 以某种方式做到了。)

所以为了证明这个事实,我刚刚创建了一个简单的程序,它只创建了大量的对象。它用“System.gc()”注释了行。现在尝试先用注释的 System.gc() 运行相同的程序,然后取消注释 System.gc()。确保提供 VM 参数为 -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails。

package ravi.tutorial.java.gc;

/**
 * Just to test GC. RUn with below VM arguments.
 * 
 * -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails
 * 
 * 
 * @author ravi.k
 * 
 */
public class TestGC {

    public static A a;

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 100; i++) {
            populateObjects();
            System.out.println("population done for batch: " + i);
        }

    }

    public static void populateObjects() {
        for (int i = 0; i < 100000; i++) {
            a = new A("A");
        }
        //System.gc();
    }

}

class A {
    String s;

    public A(String s) {
        this.s = s;
    }
}

这里部分输出来自我的机器。

commened System.gc():这里gc是jre随意调用的。

population done for batch: 0
population done for batch: 1
population done for batch: 2
population done for batch: 3
population done for batch: 4
population done for batch: 5
population done for batch: 6
population done for batch: 7
population done for batch: 8
population done for batch: 9
0.332: [GC 0.332: [ParNew: 17024K->410K(19136K), 0.0024479 secs] 17024K->410K(83008K), 0.0025219 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
population done for batch: 10
population done for batch: 11
population done for batch: 12
population done for batch: 13
population done for batch: 14
population done for batch: 15
population done for batch: 16
population done for batch: 17
population done for batch: 18
population done for batch: 19
0.344: [GC 0.344: [ParNew: 17434K->592K(19136K), 0.0011238 secs] 17434K->592K(83008K), 0.0011645 secs] [Times: user=0.00 sys=0.01, real=0.00 secs] 
population done for batch: 20
population done for batch: 21
population done for batch: 22
population done for batch: 23
population done for batch: 24
population done for batch: 25
population done for batch: 26
population done for batch: 27
population done for batch: 28
population done for batch: 29
population done for batch: 30
0.353: [GC 0.353: [ParNew: 17616K->543K(19136K), 0.0011398 secs] 17616K->543K(83008K), 0.0011770 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
population done for batch: 31
population done for batch: 32
population done for batch: 33

未注释的 System.gc():这里为每个批次调用 GC。现在 System.gc() 只是建议 GC,但 java 选择在那个时候自己运行 GC。这与其他工具中那个神奇的 GC 按钮的情况完全相同:)

0.337: [Full GC (System) 0.337: [CMS: 0K->400K(63872K), 0.0219250 secs] 3296K->400K(83008K), [CMS Perm : 4423K->4422K(21248K)], 0.0220152 secs] [Times: user=0.04 sys=0.00, real=0.02 secs] 
population done for batch: 0
0.364: [Full GC (System) 0.364: [CMS: 400K->394K(63872K), 0.0161792 secs] 2492K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0162336 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
population done for batch: 1
0.382: [Full GC (System) 0.382: [CMS: 394K->394K(63872K), 0.0160193 secs] 2096K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0160834 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
population done for batch: 2
0.399: [Full GC (System) 0.399: [CMS: 394K->394K(63872K), 0.0160866 secs] 2096K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0161489 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
population done for batch: 3
0.417: [Full GC (System) 0.417: [CMS: 394K->394K(63872K), 0.0156326 secs] 2096K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0156924 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
population done for batch: 4
0.434: [Full GC (System) 0.434: [CMS: 394K->394K(63872K), 0.0157274 secs] 2096K->394K(83008K), [CMS Perm : 4425K->4425K(21248K)], 0.0157897 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
population done for batch: 5

要添加更多,它就像线程。无法保证线程何时运行,但每当我们编写任何示例线程程序时,线程本身都会运行该时间。所以我们不应该责怪java它是如何在线程启动时运行的:)。Java 只是说不要依赖这些东西,但它们确实有效。此外,尽管它们在某些情况下工作并不意味着它们每次都会工作。即使是那些 jconsole 工具也可能无法执行 GC,只是我们从未见过。

于 2012-11-09T13:17:14.910 回答
3

我对这种微不足道的方法有很多积极的经验:

System.gc();
Thread.sleep(500);
System.gc();

由于对象终结问题,一次 GC 运行通常是不够的,其中对象可能在终结中复活。因此,在第二次 GC 运行时会释放额外的内存。

请注意,这以及任何其他看似“更智能”的方法都是启发式的,并且非常依赖于 JVM 的确切版本,包括其 GC 配置。但在许多情况下,您不会对一般性如此感兴趣:如果它现在可以工作并允许您进行测量,那么它就是要走的路。

于 2012-11-09T13:42:13.543 回答
0

1) System.gc() “建议” GC 运行,不会以任何方式强制它,因此不可能依赖它来检测内存使用峰值

规范就是这么说的,但是如果您使用 OpenJDK 或 HotSpot,除非您将其关闭,否则它将始终执行 Full GC。

通常建议的是计算对象占用

我建议使用商业内存分析器。我会让 JVM 以最大 8 GB 启动,然后看看它尝试使用多少。之后,我会根据您对它是否想要更多或似乎不使用它的判断来增加或减少它。

没有办法轻松获取方法的内存占用(表示为对象内存分配的峰值)

方法使用的唯一内存是在堆栈上。您可以跟踪在方法中创建了多少对象(计数、类、大小),但这些对象不属于该方法并且可以在任何地方使用,即使在方法返回之后也是如此。

如果不是,您如何解释该按钮的“确定性行为”?

我会把它归结为主观分析。;)

理想情况下,您应该以 2-3 倍的最小内存运行 JVM,以使其有效运行。试图节省成本低于 1 美元的 100 MB 并不总是有用的。;)

于 2012-11-09T10:12:03.273 回答
-1

你可以像这样强制GC....

private static void force_gc()
{
    Object obj = new Object();
    WeakReference<Object> ref = new WeakReference<Object>(obj);
    obj = null;
    while (ref.get() != null)
    {
        Log.d(LOGTAG, "Forcing gc() ...");
        System.gc();
    }
}

除此之外......我很想知道这个问题的去向。

于 2012-11-09T10:50:56.720 回答