CodeCache 是用来保存由JIT 产生的native code 的内存区域,它是独立于JVM heap的非堆内存。
JIT(just-in-time)编译(HotSpot),是指在运行时,把频繁执行的 bytecode 转换成操作系统相关的机器码,这样程序执行时就不需要解释执行,可以提高程序性能,具体可以看下这个博客。
问题描述:
我想在应用里面试用一下greys, 当使用monitor 功能时,发现load飙高,数据库连接池撑爆,应用立即无法响应,吓死宝宝了。随即关掉greys,恢复了应用。之后我联系了作者,他说有可能是我们应用CodeCache不足,导致JIT 回收CodeCache时,占满cpu。
原因定位:
因为集团其他同事也遇到过此类问题,他用的是aliperf来定位热点代码,最后定位到JVM的largest_free_block()函数。简单的说,JVM在维护CodeCache时会维护一个freelist(空闲内存块的链表),当要分配空间时,会遍历这个freelist。而采用分层编译时会导致编译后的代码被频繁的写入或失效CodeCache,freelist变的非常碎片化,非常长,而每次JVM进行JIT操作时,会持有锁并且遍历这个list,看源码,可以看到持有了锁。
1 size_t CodeCache::largest_free_block() { 2 // This is called both with and without CodeCache_lock held so 3 // handle both cases. 4 if (CodeCache_lock->owned_by_self()) { 5 return _heap->largest_free_block(); 6 } else { 7 MutexLockerEx mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); 8 return _heap->largest_free_block(); 9 } 10 }
因此load变高,引发了后续一系列问题。jdk官方也有类似bug,在jdk8中修复。
验证:
首先我查了下我们应用默认的CodeCache大小。
1 jinfo -flag ReservedCodeCacheSize
发现是50M,然后通过jmx查看了在应用运行中使用的CodeCache大小,发现用了48M,果然已经接近极限。
greys采用了动态代理,在runtime时产生大量代理类,被编译写入CodeCache,导致CodeCache空间不足,成为最后一根稻草。
总结:
可以看到真正的原因有两部分组成,
1. CodeCache 空间不足,引发JVM回收空间,调用largest_free_block()。
2. JVM 采用了分层编译,编译器采用了C1、C2两种编译方式,CodeCache中会存在C1和C2编译过的代码
不分层编译就只有C2编译过的代码,使得CodeCache更容易填满。
问题解决:
通过添加-XX:ReservedCodeCacheSize=512M 更改CodeCache大小。
通过添加 -XX:+UseCodeCacheFlushing 使得CodeCache在接近满时,释放一半Cache,参考文章。