性能文章>JVM Code Cache空间不足,导致服务性能变慢>

JVM Code Cache空间不足,导致服务性能变慢原创

2年前
896817

image.png

有业务反馈,线上一个应用运行了一段时间之后,在高峰期之后,突然发现处理能力下降,接口的响应时间变长,但是看Cat上的GC数据,一切都很正常。

通过跳板机上机器查看日志,发现一段平时很少见到的日志:

Java HotSpot(TM) 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled.
Java HotSpot(TM) 64-Bit Server VM warning: Try increasing the code cache size using -XX:ReservedCodeCacheSize=.
...
“CompilerThread0” java.lang.OutOfMemoryError: requested 2854248 bytes for Chunk::new. Out of swap space?

其中CodeCache is full,说明Code Cache已经满了,导致Compiler失效,这是为什么?

首先,我们得了解什么是Code Cache。

什么是Code Cache

Java代码在执行次数达到一个阈值会触发JIT编译,一旦代码块被编译成本地机器码,下次执行的时候会直接运行编译后的本地机器码。所以这本地机器码必须被缓存起来,而缓存这个本地机器码的内存区域就是Code Cache,它并不属于Java堆的一部分,除了JIT编译的代码之外,Java所使用的本地方法代码(JNI)也会存在codeCache中。

Code Cache 调优

由于Code Cache是一块内存区域,那么肯定有大小的限制,但是不同版本的JVM、不同的启动方式,Code Cache的默认大小也不同,可通过 jinfo-flagReservedCodeCacheSize 进行查看。

服务启动之后,随着时间的推移,肯定会有越来越多的方法被JIT编译成本地机器码,并存放到Code Cache,由于Code Cache大小是固定的,那么就存在被用完的风险。

一旦Code Cache被填满,就会出现下面情况:

  • JVM的JIT功能会被停止,将不会编译任何额外的代码。

  • 被编译过的代码仍然以编译方式执行,但是尚未被编译的代码只能以解释方式执行了。

这种情况下,如果应用中还有很多代码以解释方式执行,其性能会大大降低。为了避免这种情况,就需要对Code Cache比较深入的理解。

JVM启动的时候,Code Cache所需内存会被单独初始化,这时候Java堆还会被初始化,所以Code Cache和Java堆是两块独立内存区域。

codeCache.cppCodeCache::initialize()方法中,实现了Code Cache的初始化

image.png

Code Cache包含了3种数据:

  • NonNMethodCode

  • ProfiledCode

  • NonProfiledCode

通过 SegmentedCodeCache参数可以选择按照整体初始化,还是分段初始化。

通过 -XX:ReservedCodeCacheSize参数可以指定Code Cache的初始化大小,这个默认值在不同的JDK版本也不同,目前我这边调试的是OpenJDK11,默认大小是240M,这个已经够用了。

可以看下其它版本的默认大小:

image.png

对于那些只有32M、48M的就可能存在Code Cache不足的隐患,增加 ReservedCodeCacheSize可以是一个解决方案,但这通常只是一个临时的解决方案。

幸运的是,JVM提供了一种比较激进的codeCache回收方式:Speculative flushing。

在JDK1.7.0_4之后这种回收方式默认开启,而之前的版本需要通过一个参数来开启: -XX:+UseCodeCacheFlushing

在Speculative flushing开启的情况下,当Code Cache不足时:

  • 最早被编译的一半方法将会被放到一个old列表中等待回收;

  • 在一定时间间隔内,如果old列表中方法没有被调用,这个方法就会被从Code Cache清除;

很不幸的是,在JDK1.7中,Speculative flushing释放了一部分空间,但是从编译日志来看,JIT并没有恢复正常,并且系统整体性能下降很多,出现了大量超时。

在Oracle官网上,有这样一个Bug:http://bugs.java.com/bugdatabase/viewbug.do?bugid=8006952

由于算法问题,当Code Cache不足之后会导致编译线程无法继续,并且消耗大量CPU,导致系统运行变慢。

image.png

这个bug在7u101及8以后的版本已经得到修复。

请先登录,再评论

狼哥,你这里用的是OpenJDK11,默认的240M,且Speculative flushing是默认开启状态,那么是如何出现CodeCache is full这个情况的,最终是如何解决的?通过-XX:ReservedCodeCacheSize把CodeCache再设置大点吗?

2年前
回复 串一串:

这种情况一般都是增大codecache size来解决

2年前回复

为你推荐

不起眼,但是足以让你有收获的JVM内存分析案例
分析 这个问题说白了,就是说有些int[]对象不知道是哪里来的,于是我拿他的例子跑了跑,好像还真有这么回事。点该 dump 文件详情,查看相关的 int[] 数组,点该对象的“被引用对象”,发现所
从一起GC血案谈到反射原理
前言 首先回答一下提问者的问题。这主要是由于存在大量反射而产生的临时类加载器和 ASM 临时生成的类,这些类会被保留在 Metaspace,一旦 Metaspace 即将满的时候,就会触发 Fu
关于内存溢出,咱再聊点有意思的?
概述 上篇文章讲了JVM在GC上的一个设计缺陷,揪出一个导致GC慢慢变长的JVM设计缺陷,可能有不少人还是没怎么看明白的,今天准备讲的大家应该都很容易看明白 本文其实很犹豫写不写,因为感觉没有
协助美团kafka团队定位到的一个JVM Crash问题
概述 有挺长一段时间没写技术文章了,正好这两天美团kafka团队有位小伙伴加了我微信,然后咨询了一个JVM crash的问题,大家对crash的问题都比较无奈,因为没有现场,信息量不多,碰到这类问题我
又发现一个导致JVM物理内存消耗大的Bug(已提交Patch)
概述 最近我们公司在帮一个客户查一个JVM的问题(JDK1.8.0_191-b12),发现一个系统老是被OS Kill掉,是内存泄露导致的。在查的过程中,阴差阳错地发现了JVM另外的一个Bug。这个B
JVM实战:优化我的IDEA GC
IDEA是个好东西,可以说是地球上最好的Java开发工具,但是偶尔也会卡顿,仔细想想IDEA也是Java开发的,会不会和GC有关,于是就有了接下来对IDEA的GC进行调优 IDEA默认JVM参数: -
不起眼,但是足以让你收获的JVM内存案例
今天的这个案例我觉得应该会让你涨姿势吧,不管你对JVM有多熟悉,看到这篇文章,应该还是会有点小惊讶的,不过我觉得这个案例我分享出来,是想表达不管多么奇怪的现象请一定要追究下去,会让你慢慢变得强大起来,
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得