如何提前了解Out Of Memory或Stack Overflow错误

时间:2009-04-27 16:15:02

标签: java exception

在Java中,有没有办法知道很快会发生StackOverflow错误或OutOfMemory异常?

OutOfMemory异常可能更容易捕获,如果一个能够以编程方式获取内存使用情况统计信息,并且如果提前知道在OutOfMemory之前需要使用多少内存抛出异常。但这些价值是否可知?

对于StackOverflow错误,有没有办法获得递归深度,以及如何知道递归深度的值会导致错误发生?

通过提前了解这些错误是否会发生,我觉得我可以更优雅地恢复应用程序,而不是看着它崩溃。

12 个答案:

答案 0 :(得分:8)

预测内存不足错误

我很惊讶我在其他帖子中没有看到这一点,但您可以在Java 5/6中使用ManagementFactory来获取大量的内存使用信息。

查看platform mbean server page以获取有关检测Java中的低内存条件的更多信息。我相信你可以设置通知程序,以便在内存使用率达到某个阈值时调用代码。

答案 1 :(得分:5)

您可以使用Runtime.freeMemory()Runtime.maxMemory()预测内存不足的情况。大多数时候,优雅地恢复很难,但我把它留给你。

答案 2 :(得分:3)

大多数StackOverflow错误来自错误的递归。不幸的是,确定递归是否会停止的问题通常是不可判定的(这是CS中的一个核心概念)。但是,有些情况下,您可能会收到警告,例如,如果您在没有参数的情况下递归调用函数,某些IDE会通知您。

答案 3 :(得分:2)

如果您的应用程序设计和实现正确,您永远不会看到StackOverflow异常!

通常,如果您收到StackOverflow个异常,则表示您的递归代码中存在错误。

答案 4 :(得分:1)

通常内存使用很难预测。

无限回归的堆栈溢出通常会出现在写入/调试周期中。

更难捕获的是诸如大型藏品,缓存等等的内存问题。正如已经指出的那样,你可以检查运行时空闲和最大内存,但要小心,因为它们的含义不明显 - 这里的“免费”意味着“现在免费”,例如意思是如果你运行一个完整的gc,你可能会得到更多。不幸的是,如果没有运行System.gc(),就没有办法获得“总可能免费,包括垃圾收集”,这对于生产应用程序来说不是一件好事(你可能有足够大的数据集到首先导致问题)因为整个JVM将在几秒钟内(或更多,在大型应用程序中)停止尖叫。请注意,即使System.gc()也不能保证“现在”运行,但我的经验是,无论什么时候我都玩过它。

您可以通过使用-verbose:gc,-XX:+ PrintGCTimeStamps和-XX:+ PrintGCDetails(更多详细信息here)启动java来从正在运行的jvm中打印gc活动,并且通常如果收集器启动为了更频繁地运行,这可能表明你的内存不足。

答案 5 :(得分:1)

您可以做的一件有用的事情是将SoftReference用于缓存。当你的内存耗尽时,这将给你一个逐渐的性能下滑。即使在WeakReference中也不要使用WeakHashMap,因为当你的申请在我身上时,它会让我烦恼。

答案 6 :(得分:1)

如果您的记忆达到某个阈值,MemoryMXBean会发出通知。

答案 7 :(得分:0)

您可以通过创建Throwable对象并查询其getStackTrace()方法来发现很多有关递归深度的信息。但这样做很昂贵。

如果你真的有一个潜在的抛出StackOverflowError或OutOfMemoryError的方法,为什么不只是插入try-catch块并捕获这些错误?它们可以像被检查的异常一样被捕获和处理。

答案 8 :(得分:0)

我不知道在运行时如何解决这个问题,或者一旦你预测它会发生,你可以做些什么来避免它。最好尽量避免它首先发生。

1)您可以使用Findbugs,这可能表示由于无意中从自身调用相同的方法而发生的一些StackOverFlow错误。

2)您可以使用SoftReference存储可能导致内存不足的数据,并在访问它时进行空检查,以便在垃圾回收时可以重新加载。

如果这些事情中的任何一个实际上都是您的问题,那么解决方案可能不会检测到它的发生,而是以不同方式构建您的解决方案,以避免它们出现,如果可能的话。

答案 9 :(得分:0)

对于StackOverflowError:

要知道当前的深度,通常是:

  1. 使用有状态函数(在函数外部存储深度)
  2. 使用累加器(将深度作为参数传递给函数)
  3. 知道它将发生的深度是困难的。有几个因素:

    1. 分配给JVM的堆栈空间(可以使用-Xss选项更改它)
    2. 已使用的堆栈空间量
    3. 当前功能使用的金额。
    4. 为什么不尝试使用这样的东西?

      public static void main(String[] args) {
          try {
              recurs();
          } catch (Throwable t) {
              // not a good idea in production code....
          }
      }
      static int depth = 0;
      static void recurs() {
          System.out.println(depth++);
          recurs();
      }
      

      多次运行。还尝试添加虚拟变量。可以看出,即使相同的代码也可能在不同的深度处停止,并且添加更多变量会导致它更早结束。所以,是的,几乎是不可预测的。

      我认为除了重写算法之外,唯一的选择是使用-Xss选项增加堆栈空间。

      对于OutOfMemoryError,有-Xmx选项

答案 10 :(得分:0)

我不知道如何防止这些错误情况但是如果你向线程添加一个未处理的异常处理程序,至少你可以记录并在另一个线程中进行某种恢复。

答案 11 :(得分:0)

使用MemoryPoolMXBean

的实现示例
import static java.lang.management.ManagementFactory.getMemoryMXBean;
import static java.lang.management.ManagementFactory.getMemoryPoolMXBeans;
import static java.lang.management.MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED;
import static java.lang.management.MemoryType.HEAP;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MemoryWatcher {

    public interface Listener {
        void memoryUsageLow(long usedMemory, long maxMemory);
    }

    private static final MemoryPoolMXBean tenuredGenPool = findTenuredGenPool();
    private static final Logger logger = LoggerFactory.getLogger(MemoryWatcher.class);

    private static MemoryPoolMXBean findTenuredGenPool() {
        for (MemoryPoolMXBean pool : getMemoryPoolMXBeans())
            if (pool.getType() == HEAP && pool.isUsageThresholdSupported())
                return pool;
        return null;
    }

    public static MemoryPoolMXBean getTenuredGenPool() {
        return tenuredGenPool;
    }

    private final Collection<Listener> listeners = new CopyOnWriteArrayList<>();

    public MemoryWatcher(double usageThresholdPercent) {
        if (tenuredGenPool == null) {
            logger.warn("Tenured pool is not used");
            return;
        }
        if (tenuredGenPool.getUsageThreshold() != 0)
            logger.warn("Overriding tenured usage threshold {} with {}", tenuredGenPool.getUsage().getMax() / (double) tenuredGenPool.getUsageThreshold(), usageThresholdPercent);
        tenuredGenPool.setUsageThreshold((long) (tenuredGenPool.getUsage().getMax() * usageThresholdPercent));

        NotificationEmitter emitter = (NotificationEmitter) getMemoryMXBean();
        emitter.addNotificationListener((Notification n, Object hb) -> {
            MemoryUsage usage = tenuredGenPool.getUsage();
            if (n.getType().equals(MEMORY_THRESHOLD_EXCEEDED) && usage.getMax() == usage.getCommitted())
                listeners.forEach(listener -> listener.memoryUsageLow(usage.getUsed(), usage.getMax()));
        }, null, null);
    }

    public boolean addListener(Listener listener) {
        return listeners.add(listener);
    }

    public boolean removeListener(Listener listener) {
        return listeners.remove(listener);
    }
}