升級Java8可能會踩到的坑

1 背景

java8的正式版本已經發布了2年多了,咱們都知道java8更加高效,好比更加高效的G一、更加高效的jit、默認開啓TieredCompilation更加高效的工做模式以及和「java.lang.OutOfMemoryError: PermGen space」 撒油啦啦 等等高大上的性能優化;固然了,這都不是最主要的: 
主要緣由是由於前一段時間線上tomcat很詭異的就忽然掛掉了,經查失敗log,是命中了一個jvm crash的bug,bug詳見:
http://bugs.java.com/view_bug.do?bug_id=8021898,jdk的1.7.0_60後修復了這個問題,正好看到java8那麼多高大上的優化後,就準備一步到位直接升級到java8; 
可是理想是美好的,現實是骨感的;咱們很榮幸的掉進了坑裏了,下面主要從spring4.0的坑和新metaspace問題 這2個方面來描述。
java

2 spring4.0的問題

2.1 前言

上面說了,遇到了jvm的crash的bug,必需要升級jdk的版本;找了套測試環境作了迴歸,發現沒啥問題,很是高興,準備在線上開搞。web

2.2 現象

最開始的找了2組流量相對較小的機器,升級後重啓服務沒有發現異常現象,覺得沒啥問題了,就準備開始搞一組qps比較高的機器,替換jdk重啓以後沒啥問題,而後觀察了會,發現load在緩慢上升,緊接着服務的接口開始大量的超時,見下圖:spring


2.3 排查過程

2.3.1 stack排查

首先,是抓了幾個tomcat棧log下來,研究棧發現有大量的lock,見下圖:tomcat

圖片



往下繼續找正在運行的那個線程,以下圖:性能優化

圖片


經過上面的現象看到,不論是lock的,仍是runnable的,都是卡在的spring的代碼棧下,這裏就有點奇怪了,spring的版本沒有升級啊,先否決了,由於java8針對gc和jit有過相關的,首先來開始排查這2者,繼續往下;服務器

2.3.2 排查gc

首先排查了gc的問題,發現除了比以前更頻繁一點以外,其它沒啥區別,也沒有觸發full gc,基本能夠排除。app

2.3.3 jit排查

java8針對jit有一些默認的優化,因此發現gc沒有問題的狀況下,首先想到的就是jit的設置問題。jvm

首先懷疑的是jit的cache的內存大小問題,調大後重啓,發現只能延緩一點load升高的時間,沒有根本解決問題 
而後是調整jit的默認線程數,以前咱們是設置了2個線程,使用默認的3個線程,有必定的效果,抗住了20分鐘;而後繼續調大四、5,發現反而比以前掛了還要快,重啓以後基本上就掛了,也基本能夠排除。
ide

2.3.4 spring排查

最容易出現問題的方面排查過了,沒有發現問題,那就回歸棧日誌來排查,找不到明顯問題,問下Google吧。 
還真有發現:spring官網有人彙報過一個bug,SerializableTypeWrapper從jdk的1.7.0_51這個版本開始出現了性能瓶頸,bug地址詳見:
性能

https://jira.spring.io/browse/SPR-11335 


咱們以前的是用的jdk1.7.0_45這個版本,沒有發現這個問題;具體緣由在後面來描述。

2.4 解決方案

升級spring的版本就能夠解決這個問題。
從4.0.3開始,spring針對jdk8有較大的改善,具體可參見官網說明:
https://spring.io/blog/2014/03/27/spring-framework-4-0-3-released-with-java-8-support-now-production-ready

2.5 問題分析

2..5.1 ResolvableType

首先咱們來看下ResolvableType這個類的相關代碼,以下圖:

圖片

圖片

圖片

能夠看到1018-1022行之間,先是從cache中取,若是沒有取到,就會往cache中put一個,經過上面的棧能夠知道,equals比較耗時(會經過SerializableTypeWrapper生成相關代理),這裏get和put之間沒有任何攔截措施,get不到直接put,put內部是有lock的,就是咱們看到的上面的lock的圖,這樣就有可能致使大量的線程等待。
按理說即便會慢,也應該只是在剛開始啓動的時候,等都初始化徹底後,cache中就有了,後面就行了啊,我們繼續往下看。

2.5.2 ConcurrentReferenceHashMap的問題

ConcurrentReferenceHashMap是一個soft cache, 過時的時候會走到put裏,加鎖等待,致使更多的線程block, 線程block致使tomcat更多的線程同時運行致使gc壓力,而soft cache是一個軟cache,壓力過大時會被gc回收掉,這樣會致使soft cache 接連失效;這也就驗證了上面開始的gc比之間頻繁的問題,這種狀況在qps高的服務下會比較容易出現。

2.6 spring的修復方案

2.6.1 ResolvableType

圖片


能夠看到在get以前加了一個check操做;大致意思就是checker一下cache的有效性,若是cache已過時,把過時的信息強制剔除以提升效率。

2.6.2 SerializableTypeWrapper


能夠看到針對equals有優化操做;大致意思是在執行equals方法的時候,直接執行原始的equals,不用再通過多層代理的過濾;尤爲是針對ResolvableType這種高執行頻率的操做效果較好。

2.7 Metaspace的問題

2.7.1 前言

通過上面的折騰,終於算是把java8用上了,運行了幾天以後很正常(實際上是我想多了)。

2.7.2 現象

在大概一個星期以後的凌晨,忽然收到報警,線上服務swap有報警或者服務掛掉了……

2.7.3 問題排查

查了下緣由,問題出如今java8新搞得MetaSpace身上。

2.7.4 概念

首先來介紹2個概念:

2.7.4.1 PermGen

PermGen space的全稱是Permanent Generation space,是指內存的永久保存區域,說說爲何會內存益出:這一部分用於存放Class和Meta的信息,Class在被 Load的時候被放入PermGen space區域,它和和存放Instance的Heap區域不一樣,因此若是你的APP會LOAD不少CLASS的話,就極可能出現PermGen space錯誤。這種錯誤常見在web服務器對JSP進行pre compile的時候。

2.7.4.2 MetaSpace

Java8將移除永久區,使用本地內存來存儲類元數據信息並稱之爲:元空間(Metaspace)。
Java8的啓動參數:PermSize 和 MaxPermSize 會被忽略並給出警告(若是在啓用時設置了這兩個參數)。

圖片


這意味着不會再有java.lang.OutOfMemoryError: PermGen問題,也再也不須要你進行調優及監控內存空間的使用……但請等等,這麼說還爲時過早。在默認狀況下,這些改變是透明的,接下來咱們的展現將使你知道仍然要關注類元數據內存的佔用。請必定要牢記,這個新特性也不能神奇地消除類和類加載器致使的內存泄漏。

2.7.5 Metaspace 容量

默認狀況下,類元數據只受可用的本地內存限制(容量取決因而32位或是64位操做系統的可用虛擬內存大小)。

新參數(MaxMetaspaceSize)用於限制本地內存分配給類元數據的大小。若是沒有指定這個參數,元空間會在運行時根據須要動態調整。

2.7.6 緣由分析

在java8裏,因爲PermSize 和 MaxPermSize已經失效,而你又正好沒有設置MetaspaceSize和MaxMetaspaceSize這2個參數,那麼就有可能會致使 metaspace的空間在不停的擴展,會致使機器的內存不足;進而可能出現swap內存被吃;嚴重可能致使進程直接被系統直接kill掉。

3 總結

升級java8應該注意如下2點:

  • 需配合Spring 4.0.3以上版本使用 

  • 須要配置Metaspace大小 


暫時爲止,升級java8的問題暫時告一段落。


若是你近期有升級java8的計劃,正好你也看到了這篇文章,那麼恭喜你,你能夠少踩2個坑;java8很美好,升級需謹慎。 

還遺漏一個問題,爲何ResolvableType的equals在java8下性能會明顯降低,你們能夠本身找找源碼,後面有機會能夠再單獨出一篇文章來解釋下。

相關文章
相關標籤/搜索