JVisualVM / JConsole中的System.gc()vs GC按钮

时间:2012-11-09 09:59:01

标签: java memory garbage-collection heap-memory jconsole

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

给出一个数量级,对于一个真正的峰值为100MB的运行(我测试它运行几次完全相同的输入/参数配置,强制jvm内存-Xmx和-Xms减少值,I获取线程中的异常“main”java.lang.OutOfMemoryError:超出GC开销限制 <100MB,具有稳定且可重复的结果)它占用大约1.1GB,这就是为什么它对我来说非常重要实数,因为它们差别很大!

我花了最近10天在网上和stackoverflow中阅读问题,我实际知道的是:

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

  2. 通常建议的是计算对象占用(我看到了SizeOf项目,我尝试并且工作正常,即使它不符合我的需要),这对我来说是不可行的,因为由于在不同的方法中创建了大量的集合(集合,列表和映射)迭代器,所以发生了大量的内存分配,这种方法被称为非常多次(比如我记得的10分钟的运行时间为数百万),所以它检测所有涉及的对象并执行总和是非常困难的(我用内存消耗图调试了很多次运行而不能只识别一个瓶颈)

  3. 无法轻易获得方法的内存占用(表示为对象内存分配的峰值)

  4. 事实是我自己经历过System.gc()调用不可靠(例如,由于真正调用了GC,因此在System.gc()之后读取不同的内存) ,但是当我按下JVisualVM或Jconsole中的“GC按钮”时,从不无法运行GC或拒绝执行此操作。

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

    到目前为止,我已经对10个增加的模式大小进行了实际内存峰值的手动测试(对于这种测量,模式是从单个“复杂度参数”自动生成的)并且我绘制了预期曲线,如果我不会能够获得更好的解决方案我想将我的代码作为外部jar运行,-Xmx / -Xms等于略低于我对预期内存峰值的预测,捕获外部进程ErrorStream中的OutMemoryException并重新启动增加的内存直到完成了一次完整的运行。 (如果天真的记忆预测不够健壮,我将应用适当的机器学习技术)。我知道这不是一个优雅的解决方案,但在我的情况下(学术界),我可以花费一些额外的时间来进行这些测量。如果您对此强力方法有其他建议或改进,欢迎(非常)欢迎您分享。

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

      

    java版“1.7.0_04”   Java(TM)SE运行时环境(版本1.7.0_04-b20)   Java HotSpot(TM)64位服务器VM(版本23.0-b21,混合模式)

    提前致谢, 的Alessandro

4 个答案:

答案 0 :(得分:4)

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

所以来到您的查询,您似乎担心如何按下该按钮直接调用GC&amp;仍然java说System.gc只是“建议”调用GC。我说,那个按钮也叫System.gc()&amp;它只是“建议”java尝试GC,&amp;它发生在某种程度上,java决定在那个时候执行GC(它不保证,但不知何故java做它。)

为了证明这一点,我只创建了一个简单的程序,它只是创建了大量的对象。它注释了“System.gc()”。现在尝试首先使用注释System.gc()&amp;运行相同的程序。然后通过取消注释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。

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,只是我们从未见过它。

答案 1 :(得分:2)

我对这种琐碎的方法有很多积极的经验:

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

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

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

答案 2 :(得分:0)

  

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

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

  

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

我建议使用商业内存分析器。我会让JVM以最大8 GB的速度启动并查看它尝试使用多少。在此之后,我会根据您对是否愿意使用它的判断来增加或减少它。

  

无法轻易获取方法的内存占用(表示为对象内存分配的峰值)

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

  

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

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

理想情况下,运行JVM的速度应该是其运行效率所需的最小内存的2-3倍。试图节省几百美元,其成本低于1美元并不总是有用。 ;)

答案 3 :(得分:-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();
    }
}

除此之外......我很想知道这个问题在哪里。