我的Java程序突然退出时出现问题,没有任何异常抛出或程序正常完成。
我正在编写一个程序来解决Project Euler的14th 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时,错误会再次显示。
任何人都可以复制这个,甚至可以提供一个关于它为什么会发生的建议吗?
答案 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
的缺失。增加堆可以让它为我完成。
性能......可能很差,因为它在每次获取或设置操作时都会打包或取消装箱。它足够快,偶尔使用,但在性能关键的内循环中使用它会很愚蠢。
答案 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”,但遗憾的是内存不足。