我们在JBoss上使用Hibernate,Spring等几个库部署了一个大型JDK7应用程序。在初始启动服务器之后,应用程序按预期运行,但在一段正常运行时间后,它变得非常慢。
使用分析器,我们已经看到每次外出应用程序的某些方面都在减慢但不总是相同的方面。虽然在一次运行中,hibernate flush可能会慢慢爬行,但在另一次运行中它可能是来自Spring的一些DI代码。
那里发生了什么?
答案 0 :(得分:8)
JDK7中有一个关于CodeCache内存区域的错误,它非常非常困难地打击了我们。
<强>解释强>
基本上Java启动并使用及时编译(JIT)在运行时期间仅编译字节码的必需部分。这使JVM能够在执行期间解除并重新编译某些代码片段。如果JVM确定,则会发生这种情况,即某个代码片段的初始编译不是最理想的。 Oracle在JDK 7中引入了一个名为分层编译的功能,它允许VM执行此操作。
JVM中的编译代码存储在CodeCache
内存区域中。直到JDK6,默认情况下这个区域将被填满,一旦达到100%,JIT将停止编译并且错误将被打印到控制台,但是应用程序将像以前一样运行:已编译的所有内容都将保持编译状态,尚未编译的所有内容都将在解释模式下执行(大约慢100倍)
此选项名为CodeCacheFlushing
,默认情况下自JDK7u4启用。这个想法是,一旦CodeCache
已满,编译代码中使用最少的部分就会从内存中刷新,以便为其他代码片段腾出空间。这将使JDK6-default-behavior(完全停止编译)过时。它还允许一个小得多的CodeCache区域(在JDK7中CodeCache默认为48M / 96M,如果启用了分层编译)。
这就是错误。在JDK7中,一旦CodeCache满了,JIT就会停止。接下来是CodeCache区域的刷新。就是这样。在冲洗完成后应该重新启用JIT,但这不会发生。此外,控制台上没有打印警告。更糟糕的是:在禁用JIT之前,大约一半已编译的代码被抛弃了。
与JDK6相反,快速的所有内容都将保持快速并且只会解释新代码,在JDK7中,您实际上已经丢失了已编译和优化的代码!应用程序中表现良好的所有突然部分都将停止这样做。这是偶然的,应用程序的哪些部分会变慢,这使得通过探查器跟踪该bugger几乎是不可能的:有时用于刷新的hibernate代码减慢,在其他时候,它的春天DI代码或你自己的appcode。
你受影响吗?
您可以使用分析器(JProfiler / YourKit)或JConsole(JVisualVM不会做)来监控CodeCache内存区域的内存消耗。通常,CodeCache金额committed
将保持非常接近used
金额(例如,committed
为23mb,使用的是22mb)。在您的应用运行时,committed
和used
会一直运行到committed
到达max
。那时used
将急剧下降到max
的1/2 - 2/3。在那之后,used
不再增长。那个bug会袭击你的地方。在JConsole中,它看起来像这样:
为什么我而不是所有其他人?
有可能,你正在使用JBoss。甲骨文很快就发现有些东西不应该是它们应该被默认禁用tiered compilation
- 但红帽凭借其无限的智慧决定,它知道更好并重新启用它。基本上我们的webapp在Weblogic上运行良好,只有JBoss受到影响,因为没有分层编译(未在weblogic中启用)CodeCache的增长如此之小,即使经过数周的运行,我们也从未实际达到48mb的阈值。
我该怎么办?
首先,决定这个bug是否会袭击你。其次,让虫子更难以伤害你。如果你禁用CodeCacheFlushing
至少击中了不会让事情变得比以前更糟糕的错误。停止tiered compilation
会使错误发生的可能性降低,就像增加可用的CodeCache-Memory数量一样。
您可以随时尝试切换到JDK8,这似乎不受影响,如果CodeCache已满,您也可以在软件中实施监控以警告您。
<强> TL; DR 强>
PRESERVE_JAVA_OPTS=true
-XX:-UseCodeCacheFlushing
)-XX:ReservedCodeCacheSize=xxM
)。