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

我有一个问题,我的Java程序突然退出,没有任何exception抛出或程序正常完成。

我正在编写一个程序来解决Project Euler的第14个问题 。 这就是我得到的:

private static final int INITIAL_CACHE_SIZE = 30000; private static Map cache = new HashMap(INITIAL_CACHE_SIZE); public void main(String... args) { long number = 0; int maxSize = 0; for (long i = 1; i  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时停止(进程退出)。每次都是相同的数字。 没有exception,错误,Java VM崩溃或抛出任何东西。

更改缓存大小似乎也没有任何影响,因此缓存引入如何导致此错误?

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

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

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

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

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

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

-XX:+ HeapDumpOnOutOfMemoryError

有了它,它将打印出这样的效果:

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

比如使用-Xmx200m来增加堆大小,你就不会有问题 – 至少对于TARGET = 1000000。

这听起来像JVM本身崩溃(这是你的程序在没有任何exception的情况下死亡时的第一个想法)。 此问题的第一步是升级到您平台的最新版本。 假设您的用户级别具有该目录的访问权限,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语句中,对象超出范围并且可以被垃圾收集,释放足够的内存以允许打印堆栈跟踪。 不保证这是有效的,但它给了它更好的镜头。

两个size(long i)一个重要区别在于您正在创建的对象数量。

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

这可以解释内存使用量的增加,但不是没有OutOfMemoryError 。 增加堆确实允许它为我完成。

来自这个太阳报 :

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

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

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

我在cache.put(i,size)上遇到OutOfMemory错误;

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

递归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”,但遗憾的是内存不足。