日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
從一起GC血案談到反射原理

 概述

創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),四子王企業(yè)網(wǎng)站建設(shè),四子王品牌網(wǎng)站建設(shè),網(wǎng)站定制,四子王網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷,網(wǎng)絡(luò)優(yōu)化,四子王網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

公司之前有個(gè)大內(nèi)存系統(tǒng)(70G以上)一直使用CMS GC,不過因?yàn)樵撓到y(tǒng)對(duì)時(shí)間很敏感,偶爾會(huì)因?yàn)間clocker導(dǎo)致remark特別長(zhǎng)(雖然加了-XX:+CMSScavReengeBeforeRemark參數(shù),但是gclocker會(huì)導(dǎo)致remark前的YGC被delay),無法忍受這么長(zhǎng)的暫停就只好遷移到了G1,經(jīng)過一系列的調(diào)優(yōu)之后算比較穩(wěn)定了,這套參數(shù)便推到了全部機(jī)器上

可是就在上周突然有機(jī)器出現(xiàn)了Full GC,本來G1設(shè)計(jì)出來就是希望Full GC不在出現(xiàn),出現(xiàn)Full GC一般是不正常,GC日志如下:

從上面日志不難發(fā)現(xiàn)是因?yàn)镻erm觸發(fā)的Full GC,并且Full GC之后Perm就降下去了,不過需要提一下的是JDK7下正常的G1 GC是不會(huì)做類卸載的,只有Full GC的時(shí)候才會(huì)卸載,但JDK8下是提供了相關(guān)參數(shù)的可以在G1 GC某些階段做類卸載

于是要業(yè)務(wù)方先做了coredump,保存好現(xiàn)場(chǎng)再重啟系統(tǒng),然后再針對(duì)coredump做了heap dump,不過heapdump有40G這么大,可以通過jmap -permstat core.xxx來看看究竟perm里有什么東西

這篇文章相對(duì)來說比較長(zhǎng),涉及到的知識(shí)點(diǎn)比較多,如果實(shí)在忍不住看下去,可以跳到最后看下我對(duì)這個(gè)問題的描述再反過來看這篇文章或許讓你有更清晰的認(rèn)識(shí)

Perm里究竟塞了什么

既然是Perm滿了,那我們得看Perm里究竟放了什么,我們知道Perm里主要存的是類的原始數(shù)據(jù),比如我們加載了一個(gè)類,那這個(gè)類的信息會(huì)在Perm里分配內(nèi)存來存儲(chǔ)它的一些數(shù)據(jù)結(jié)構(gòu),所以大部分情況下,Perm的使用量和加載的類個(gè)數(shù)是關(guān)系很大的,當(dāng)然Perm里在低版本的時(shí)候還會(huì)存一些其他的數(shù)據(jù),比如String(String.intern()的情況)。

另外經(jīng)驗(yàn)告訴我們?nèi)绻娴氖荘erm溢出,那有地方動(dòng)態(tài)構(gòu)建一個(gè)類加載器加載一個(gè)類的可能性會(huì)很大,通過上面的jmap命令,我們可以統(tǒng)計(jì)下sun.reflect.DelegatingClassLoader的個(gè)數(shù)居然達(dá)到了415737個(gè)

那基本可以鎖定是反射類加載器導(dǎo)致Perm溢出的原因了,那究竟為什么會(huì)有這么多反射類加載器呢,反射類加載器又是什么,接下來先簡(jiǎn)單說下反射的原理

反射的原理

反射大家用起來很方便,由于性能其實(shí)也比較不錯(cuò)了,因此用得挺廣的,我們通常這么用反射

Method method = XXX.class.getDeclaredMethod(xx,xx);

method.invoke(target,params)

不過這里我不準(zhǔn)備用大量的代碼來描述其原理,而是講幾個(gè)關(guān)鍵的東西,然后將他們串起來

獲取Method

要調(diào)用首先要獲取Method,而獲取Method的邏輯是通過Class這個(gè)類來的,而關(guān)鍵的幾個(gè)方法和屬性如下:

在Class里有個(gè)關(guān)鍵的屬性叫做reflectionData,這里主要存的是每次從jvm里獲取到的一些類屬性,比如方法,字段等,大概長(zhǎng)這樣

這個(gè)屬性主要是SoftReference的,也就是在某些內(nèi)存比較苛刻的情況下是可能被回收的,不過正常情況下可以通過-XX:SoftRefLRUPolicyMSPerMB這個(gè)參數(shù)來控制回收的時(shí)機(jī),一旦時(shí)機(jī)到了,只要GC發(fā)生就會(huì)將其回收,那回收之后意味著再有需求的時(shí)候要重新創(chuàng)建一個(gè)這樣的對(duì)象,同時(shí)也需要從JVM里重新拿一份數(shù)據(jù),那這個(gè)數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián)的Method,F(xiàn)ield字段等都是重新生成的對(duì)象。如果是重新生成的對(duì)象那可能有什么麻煩?講到后面就明白了

getDeclaredMethod方法其實(shí)很簡(jiǎn)單,就是從privateGetDeclaredMethods返回的方法列表里復(fù)制一個(gè)Method對(duì)象返回。而這個(gè)復(fù)制的過程是通過searchMethods實(shí)現(xiàn)的

如果reflectionData這個(gè)屬性的declaredMethods非空,那privateGetDeclaredMethods就直接返回其就可以了,否則就從JVM里去撈一把出來,并賦值給reflectionData的字段,這樣下次再調(diào)用privateGetDeclaredMethods時(shí)候就可以用緩存數(shù)據(jù)了,不用每次調(diào)到JVM里去獲取數(shù)據(jù),因?yàn)閞eflectionData是Softreference,所以存在取不到值的風(fēng)險(xiǎn),一旦取不到就又去JVM里撈了

searchMethods將從privateGetDeclaredMethods返回的方法列表里找到一個(gè)同名的匹配的方法,然后復(fù)制一個(gè)方法對(duì)象出來,這個(gè)復(fù)制的具體實(shí)現(xiàn),其實(shí)就是Method.copy方法:

由此可見,我們每次通過調(diào)用getDeclaredMethod方法返回的Method對(duì)象其實(shí)都是一個(gè)新的對(duì)象,所以不宜多調(diào)哦,如果調(diào)用頻繁最好緩存起來。不過這個(gè)新的方法對(duì)象都有個(gè)root屬性指向reflectionData里緩存的某個(gè)方法,同時(shí)其methodAccessor也是用的緩存里的那個(gè)Method的methodAccessor。

Method調(diào)用

有了Method之后,那就可以調(diào)用其invoke方法了,那先看看Method的幾個(gè)關(guān)鍵信息

root屬性其實(shí)上面已經(jīng)說了,主要指向緩存里的Method對(duì)象,也就是當(dāng)前這個(gè)Method對(duì)象其實(shí)是根據(jù)root這個(gè)Method構(gòu)建出來的,因此存在一個(gè)root Method派生出多個(gè)Method的情況。

methodAccessor這個(gè)很關(guān)鍵了,其實(shí)Method.invoke方法就是調(diào)用methodAccessor的invoke方法,methodAccessor這個(gè)屬性如果root本身已經(jīng)有了,那就直接用root的methodAccessor賦值過來,否則的話就創(chuàng)建一個(gè)

MethodAccessor的實(shí)現(xiàn)

MethodAccessor本身就是一個(gè)接口

其主要有三種實(shí)現(xiàn)

  • DelegatingMethodAccessorImpl
  • NativeMethodAccessorImpl
  • GeneratedMethodAccessorXXX

其中DelegatingMethodAccessorImpl是最終注入給Method的methodAccessor的,也就是某個(gè)Method的所有的invoke方法都會(huì)調(diào)用到這個(gè)DelegatingMethodAccessorImpl.invoke,正如其名一樣的,是做代理的,也就是真正的實(shí)現(xiàn)可以是下面的兩種

如果是NativeMethodAccessorImpl,那顧名思義,該實(shí)現(xiàn)主要是native實(shí)現(xiàn)的,而GeneratedMethodAccessorXXX是為每個(gè)需要反射調(diào)用的Method動(dòng)態(tài)生成的類,后的XXX是一個(gè)數(shù)字,不斷遞增的

并且所有的方法反射都是先走NativeMethodAccessorImpl,默認(rèn)調(diào)了15次之后,才生成一個(gè)GeneratedMethodAccessorXXX類,生成好之后就會(huì)走這個(gè)生成的類的invoke方法了

那如何從NativeMethodAccessorImpl過度到GeneratedMethodAccessorXXX呢,來看看NativeMethodAccessorImpl的invoke方法

其中我上面說的是15次就是Reflection Factory. inflation Threshold()這個(gè)方法返回的,這個(gè)15當(dāng)然也不是一塵不變的,我們可以通過-Dsun.reflect.inflationThreshold=xxx來指定,我們還可以通過-Dsun.reflect.noInflation=true來直接繞過上面的15次Native Method AccessorImpl調(diào)用,和-Dsun.reflect.inflationThreshold=0的效果一樣的

而Generated Method AccessorXXX都是通過new MethodAccessorGenerator().generateMethod來生成的,一旦創(chuàng)建好之后就設(shè)置到Delegating Method AccessorImpl里去了,這樣下次Method.invoke就會(huì)調(diào)到這個(gè)新創(chuàng)建的Method Accessor里了。

那生成的GeneratedMethodAccessorXXX究竟長(zhǎng)什么樣呢,大概這樣了

其實(shí)就是直接調(diào)用目標(biāo)對(duì)象的具體方法了,和正常的方法調(diào)用沒什么區(qū)別

GeneratedMethodAccessorXXX的類加載器

那加載GeneratedMethodAccessorXXX的類加載器是什么呢,在生成好了字節(jié)碼之后會(huì)調(diào)用下面的方法做類定義

所以GeneratedMethodAccessorXXX的類加載器其實(shí)是一個(gè)DelegatingClassLoader類加載器

之所以搞一個(gè)新的類加載器,是為了性能考慮,在某些情況下可以卸載這些生成的類,因?yàn)轭惖男遁d是只有在類加載器可以被回收的情況下才會(huì)被回收的,如果用了原來的類加載器,那可能導(dǎo)致這些新創(chuàng)建的類一直無法被卸載,從其設(shè)計(jì)來看本身就不希望他們一直存在內(nèi)存里的,在需要的時(shí)候有就行了,在內(nèi)存緊俏的時(shí)候可以釋放掉內(nèi)存

并發(fā)導(dǎo)致垃圾類創(chuàng)建

看到這里不知道大家是否發(fā)現(xiàn)了一個(gè)問題,上面的NativeMethodAccessorImpl.invoke其實(shí)都是不加鎖的,那意味著什么?如果并發(fā)很高的時(shí)候,是不是意味著可能同時(shí)有很多線程進(jìn)入到創(chuàng)建GeneratedMethodAccessorXXX類的邏輯里,雖然說最終使用的其實(shí)只會(huì)有一個(gè),但是這些開銷是不是已然存在了,假如有1000個(gè)線程都進(jìn)入到創(chuàng)建GeneratedMethodAccessorXXX的邏輯里,那意味著多創(chuàng)建了999個(gè)無用的類,這些類會(huì)一直占著內(nèi)存,直到能回收Perm的GC發(fā)生才會(huì)回收

那究竟是什么方法在不斷反射呢

有了上面對(duì)反射原理的了解之后,我們知道了在反射執(zhí)行到一定次數(shù)之后,其實(shí)會(huì)動(dòng)態(tài)構(gòu)建一個(gè)類,在這個(gè)類里會(huì)直接調(diào)用目標(biāo)對(duì)象的對(duì)應(yīng)的方法,我們從heap dump里看到了有大量的DelegatingClassLoader類加載器加載了GeneratedMethodAccessorXXX類,那這些類到底是調(diào)用了什么方法呢,于是我們不得不做一件事,那就是將內(nèi)存里的這些類都dump下來,然后對(duì)字節(jié)碼做一個(gè)統(tǒng)計(jì)分析一下

運(yùn)行時(shí)Dump類字節(jié)碼

我們可以利用SA的接口從coredump里或者live進(jìn)程里將對(duì)應(yīng)的類dump下來,為了dump下來我們特定的類,首先我們寫一個(gè)Filter類

使用SA的jar($JAVA_HOME/lib/sa-jdi.jar)編譯好類之后,然后我們?cè)诰幾g好的類目錄下調(diào)用下面的命令進(jìn)行dump

這樣我們就可以將所有的GeneratedMethodAccessor給dump下來了,這個(gè)時(shí)候我們?cè)偻ㄟ^javap -verbose GeneratedMethodAccessor9隨便看一個(gè)類的字節(jié)碼

看到上面關(guān)鍵的bci為36的那行,這里的方法便是我們反射調(diào)用的方法了,比如上面的那個(gè)反射調(diào)用的方法就是org/codehaus/xfire/util/ParamReader.readCode

定位到具體的反射類及方法

dump出這些字節(jié)碼之后,我們對(duì)這些所有的類的字節(jié)碼做一個(gè)統(tǒng)計(jì),就找出了所有的反射調(diào)用方法,然后發(fā)現(xiàn)某些model類(package都是相同的)居然產(chǎn)生了20多萬個(gè)類,這意味著有非常多的這些model類做反射

有了這個(gè)線索之后就去看代碼究竟哪里會(huì)有調(diào)用這些model方法的反射邏輯,但是可惜沒有找到,但是這種model對(duì)象極有可能在某種情況下出現(xiàn),那就是rpc反序列化的時(shí)候,最終詢問業(yè)務(wù)方是使用的Xfire的服務(wù),而憑借我多年框架開發(fā)積累的經(jīng)驗(yàn),確定Xfire就是通過反射的方式來反序列化對(duì)象的,具體代碼如下(org.codehaus.xfire.aegis.type.basic.BeanType.writeProperty):

而javabean的PropertyDescriptor里的get/set方法,其實(shí)本身就是SoftReference包裝的

看到這里或許大家都明白了吧,前面也已經(jīng)說了SoftReference是可能被GC回收掉的,時(shí)間一到在下次GC里就會(huì)被回收,如果被回收了,那就要重新獲取,然后相當(dāng)于是調(diào)用的新的Method對(duì)象的invoke方法,那調(diào)用次數(shù)一多,就會(huì)產(chǎn)生新的動(dòng)態(tài)構(gòu)建的類,而這份類會(huì)一直存到直到可以回收Perm的GC。

G1回收Perm

注意下業(yè)務(wù)系統(tǒng)使用的是JDK7的G1,而JDK7的G1對(duì)perm其實(shí)正常情況下是不會(huì)回收的,只有在Full GC的時(shí)候才會(huì)回收Perm,這就解釋了經(jīng)過了多次G1 GC之后,那些Softreference的對(duì)象會(huì)被回收,但是新產(chǎn)生的類其實(shí)并不會(huì)被回收,所以G1 GC越頻繁,那意味著SoftReference的對(duì)象越容易被回收(雖然正常情況下是時(shí)間到了,但是如果gc不頻繁,即使時(shí)間到了,也會(huì)留在內(nèi)存里的),越容易被回收那就越容易產(chǎn)生新的類,直到Full GC發(fā)生。

解決方案

  • 升級(jí)到j(luò)dk8,可以在G1 GC過程中對(duì)類做卸載
  • 換一個(gè)序列化協(xié)議,不走方法反射的,比如hessian
  • 調(diào)整SoftRefLRUPolicyMSPerMB這個(gè)參數(shù)變大,不過這個(gè)不能治本

總結(jié)

上面涉及的內(nèi)容非常多,如果不多讀幾遍可能難以串起來,我這里將這個(gè)問題發(fā)生的情況大致描述一下:

這個(gè)系統(tǒng)在JDK7下使用G1,而這個(gè)版本的G1只有在Full GC的時(shí)候才會(huì)對(duì)Perm里的類做卸載,該系統(tǒng)因?yàn)榇罅康恼?qǐng)求導(dǎo)致G1 GC發(fā)生很頻繁,同時(shí)該系統(tǒng)還設(shè)置了-XX:SoftRefLRUPolicyMSPerMB=0,那意味著SoftReference的生命周期不會(huì)跨GC周期,能很快被回收掉,這個(gè)系統(tǒng)存在大量的RPC調(diào)用,走的Xfire協(xié)議,對(duì)返回結(jié)果做反序列化的時(shí)候是走的Method.invoke的邏輯,而相關(guān)的method因此被SoftReference引用,因此很容易被回收,一旦被回收,那就創(chuàng)建一個(gè)新的Method對(duì)象,再調(diào)用其invoke方法,在調(diào)用到一定次數(shù)(15次)之后,就構(gòu)建一個(gè)新的字節(jié)碼類,伴隨著GC的進(jìn)行,同一個(gè)方法的字節(jié)碼類不斷構(gòu)建,直到將Perm充滿觸發(fā)一次Full GC才得以釋放。

【本文是專欄作者李嘉鵬的原創(chuàng)文章,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)(你假笨,id:lovestblog)聯(lián)系作者本人獲取授權(quán)】

戳這里,看該作者更多好文


本文題目:從一起GC血案談到反射原理
URL鏈接:http://www.dlmjj.cn/article/djspsos.html