Java VM突然没有明显的原因退出

时间:2009-08-27 21:08:25

标签: java jvm

我的Java程序突然退出时出现问题,没有任何异常抛出或程序正常完成。

我正在编写一个程序来解决Project Euler14th problem。这就是我得到的:

private static final int INITIAL_CACHE_SIZE = 30000;
private static Map<Long, Integer> cache = new HashMap<Long, Integer>(INITIAL_CACHE_SIZE);

public void main(String... args) {
    long number = 0;
    int maxSize = 0;

    for (long i = 1; i <= TARGET; i++) {
        int size = size(i);
        if (size > maxSize) {
            maxSize = size;
            number = i;
        }
    }
}
private static int size(long i) {
    if (i == 1L) {
        return 1;
    }
    final int size = size(process(i)) + 1;
    return size;
}

private static long process(long n) {
    return n % 2 == 0 ? n/2 : 3*n + 1;
}

运行良好,使用目标1 000 000时,在约5秒内正确完成。

我想通过添加缓存进行优化,因此我将size方法更改为:

private static int size(long i) {
    if (i == 1L) {
        return 1;
    }
    if (cache.containsKey(i)) {
        return cache.get(i);
    }
    final int size = size(process(i)) + 1;
    cache.put(i, size);
    return size;
}

现在当我运行它时,它只是在我到达555144时停止(进程退出)。每次都是相同的数字。没有异常,错误,Java VM崩溃或抛出任何东西。

更改缓存大小似乎也没有任何影响,所以缓存如何 介绍导致此错误?

如果我强制执行缓存大小不仅仅是初始化,而是像这样永久化:

    if (i < CACHE_SIZE) {
        cache.put(i, size);
    }

错误不再发生。 编辑:当我将缓存大小设置为2M时,错误会再次显示。

任何人都可以复制这个,甚至可以提供一个关于它为什么会发生的建议吗?

7 个答案:

答案 0 :(得分:8)

这只是一个没有打印的OutOfMemoryError。如果我设置了较高的堆大小,程序运行正常,否则它会以未记录的OutOfMemoryError退出(但在调试器中很容易看到)。

您可以通过传递此JVM arg并重新运行程序来验证这一点并获得堆转储(以及发生OutOfMemoryError的打印输出):

  

<强> -XX:+ HeapDumpOnOutOfMemoryError

然后它会打印出一些效果:

  

java.lang.OutOfMemoryError:Java堆空间
     将堆转储到java_pid4192.hprof ...
     创建堆转储文件[在4.464秒内91901809字节]

用-Xmx200m比较你的堆大小,你就不会有问题 - 至少对于TARGET = 1000000。

答案 1 :(得分:3)

听起来好像JVM本身崩溃了(这是你的程序在没有任何异常的情况下死亡时的第一个想法)。此问题的第一步是升级到您平台的最新版本。假设您的用户级别具有该目录的访问权限,JVM应该将堆转储到启动JVM的目录中的.log文件。

话虽如此,一些OutOfMemory错误没有在主线程中报告,所以除非你做一个try / catch(Throwable t)并看看你是否得到一个,很难确定你实际上并不是内存耗尽。它只使用100MB的事实只是意味着JVM没有配置为使用更多。可以通过将JVM的启动选项更改为-Xmx1024m以获得内存大小来更改,以查看问题是否存在。

执行try catch的代码应该是这样的:

public static void main(String[] args) {
     try {
         MyObject o = new MyObject();
         o.process();
     } catch (Throwable t) {
         t.printStackTrace();
     }
 }

并且在进程方法中执行所有操作并且不将缓存存储在静态中,这样如果错误发生在catch语句中,对象超出范围并且可以被垃圾收集,释放足够的内存以允许打印堆栈跟踪。不能保证这样做有效,但它可以让它更好。

答案 2 :(得分:1)

size(long i)的两个实现之间的一个显着差异在于您正在创建的对象数量。

在第一个实现中,没有创建Objects。在第二步中,您正在进行大量的自动装箱,为每次访问缓存创建一个新的Long,并在每次修改时添加新的Long和新的Integer

这可以解释内存使用量的增加,但不能解释OutOfMemoryError的缺失。增加堆可以让它为我完成。

来自this Sun aritcle

  

性能......可能很差,因为它在每次获取或设置操作时都会打包或取消装箱。它足够快,偶尔使用,但在性能关键的内循环中使用它会很愚蠢。

答案 3 :(得分:0)

如果你的java进程突然崩溃,可能会有一些资源被淘汰出局。像记忆一样。您可以尝试设置更高的最大堆

答案 4 :(得分:0)

你看到崩溃后会产生堆转储吗?此文件应该位于JVM的当前目录中,这是我要查找更多信息的地方。

答案 5 :(得分:0)

我在cache.put(i,size);

上遇到OutOfMemory错误

要使用调试模式在eclipse中运行程序错误,它将出现在调试窗口中。它不会在控制台中生成堆栈跟踪。

答案 6 :(得分:0)

递归size()方法可能不是进行缓存的好地方。我调用了cache.put(i,size);在main()的for循环中,它运行得更快。否则,我也会收到OOM错误(不再有堆空间)。

编辑:这是源 - 缓存检索的大小(),但存储在main()中完成。

public static void main(String[] args) {
    long num = 0;
    int  maxSize = 0;

    long start = new Date().getTime();
    for (long i = 1; i <= TARGET; i++) {
        int size = size(i);
        if (size >= maxSize) {
            maxSize = size;
            num = i;
        }
        cache.put(i, size);
    }

    long computeTime = new Date().getTime() - start;
    System.out.println(String.format("maxSize: %4d on initial starting number %6d", maxSize, num));
    System.out.println("compute time in milliseconds: " + computeTime);
}

private static int size(long i) {
    if (i == 1l) {
        return 1;
    }

    if (cache.containsKey(i)) {
        return cache.get(i);
    }

    return size(process(i)) + 1;
}

请注意,通过从size()中删除cache.put()调用,它不会缓存每个计算的大小,但它也避免重新缓存先前计算的大小。这不会影响hashmap操作,但是像akf指出的那样,它可以避免自动装箱/取消装箱操作,这是你的堆杀手来自的地方。我还在size()中尝试过“if(!containsKey(i)){cache.put()etc”,但遗憾的是内存不足。