什么是JVM预分配异常?

5

2
请注意,预分配的异常并不仅限于JVM分配的异常。在纯Java中,您也可以预分配和重复使用异常实例,标准库和第三方库确实这样做。 - the8472
@the8472,你有链接吗? - nha
1
jruby 使用预分配的异常来使非本地控制流更加容易处理。 - the8472
2个回答

14
这些是在JVM启动时预分配的异常。预分配的异常应该是隐式的:当出现意外情况时,如取消引用空指针、使用负索引访问数组等,它们由JVM抛出,而不是通过throw new ...抛出。
当方法开始(隐式)抛出这些异常太频繁时,JVM会注意到并用已经预分配的异常替换每次抛出异常的分配,而无需堆栈跟踪。
这种机制依赖于实现,因此如果我们谈论的是热点,您可以在graphKit.cpp中找到这些异常的列表。
NullPointerException 
ArithmeticException
ArrayIndexOutOfBoundsException
ArrayStoreException
ClassCastException

这个理念非常简单:抛出异常中最昂贵的部分不是实际的抛出和堆栈展开,而是在异常中创建堆栈跟踪(通过Throwable#fillInStackTrace在异常构造函数中相对缓慢地调用VM)。要找到具体的数字和相关成本,您可以阅读Hotspot性能工程师关于exceptional performance的精彩文章。

有些人将异常用于常规控制流程(请不要这样做)或者出于性能考虑(通常是不正确的,例如参见this这种比较流行的连接池框架),因此Hotspot通过抛出已经创建好的没有堆栈跟踪的异常(从而消除了抛出异常中最昂贵的部分)来使这段[可能糟糕的]代码稍微快一点。

这种方法的缺点是,现在你有了没有堆栈跟踪的异常。这并不是什么大问题:如果这些隐式异常经常被抛出,那么你可能不需要它们的堆栈跟踪。但如果这个假设是错误的,你的日志中就会出现没有跟踪的异常。为了防止这种情况发生,你可以使用-XX:-OmitStackTraceInFastThrow

2
您发布的版本说明解释了以下功能:
阅读您发布的版本说明:“服务器VM中的编译器现在为所有“冷”内置异常提供正确的堆栈回溯。出于性能考虑,当抛出这种异常几次时,可能会重新编译该方法。重新编译后,编译器可以选择使用预分配的异常采用更快的策略,这些异常不提供堆栈跟踪。”
是的,这是一种优化。
为了增加异常处理的速度,经常抛出的异常可能会被预先分配。
这样做可以避免每次出现异常时都需要不断创建新的异常,但代价是丢失堆栈跟踪信息。
您可以使用标志-XX:-OmitStackTraceInFastThrow来取消此功能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接