成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從一起GC血案談到反射原理

開發 開發工具
這篇文章相對來說比較長,涉及到的知識點比較多,如果實在忍不住看下去,可以跳到最后看下我對這個問題的描述再反過來看這篇文章或許讓你有更清晰的認識。

 概述

公司之前有個大內存系統(70G以上)一直使用CMS GC,不過因為該系統對時間很敏感,偶爾會因為gclocker導致remark特別長(雖然加了-XX:+CMSScavReengeBeforeRemark參數,但是gclocker會導致remark前的YGC被delay),無法忍受這么長的暫停就只好遷移到了G1,經過一系列的調優之后算比較穩定了,這套參數便推到了全部機器上

可是就在上周突然有機器出現了Full GC,本來G1設計出來就是希望Full GC不在出現,出現Full GC一般是不正常,GC日志如下:

從上面日志不難發現是因為Perm觸發的Full GC,并且Full GC之后Perm就降下去了,不過需要提一下的是JDK7下正常的G1 GC是不會做類卸載的,只有Full GC的時候才會卸載,但JDK8下是提供了相關參數的可以在G1 GC某些階段做類卸載

于是要業務方先做了coredump,保存好現場再重啟系統,然后再針對coredump做了heap dump,不過heapdump有40G這么大,可以通過jmap -permstat <executable java> core.xxx來看看究竟perm里有什么東西

這篇文章相對來說比較長,涉及到的知識點比較多,如果實在忍不住看下去,可以跳到最后看下我對這個問題的描述再反過來看這篇文章或許讓你有更清晰的認識

Perm里究竟塞了什么

既然是Perm滿了,那我們得看Perm里究竟放了什么,我們知道Perm里主要存的是類的原始數據,比如我們加載了一個類,那這個類的信息會在Perm里分配內存來存儲它的一些數據結構,所以大部分情況下,Perm的使用量和加載的類個數是關系很大的,當然Perm里在低版本的時候還會存一些其他的數據,比如String(String.intern()的情況)。

另外經驗告訴我們如果真的是Perm溢出,那有地方動態構建一個類加載器加載一個類的可能性會很大,通過上面的jmap命令,我們可以統計下sun.reflect.DelegatingClassLoader的個數居然達到了415737個

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

反射的原理

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

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

method.invoke(target,params)

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

獲取Method

要調用首先要獲取Method,而獲取Method的邏輯是通過Class這個類來的,而關鍵的幾個方法和屬性如下:

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

這個屬性主要是SoftReference的,也就是在某些內存比較苛刻的情況下是可能被回收的,不過正常情況下可以通過-XX:SoftRefLRUPolicyMSPerMB這個參數來控制回收的時機,一旦時機到了,只要GC發生就會將其回收,那回收之后意味著再有需求的時候要重新創建一個這樣的對象,同時也需要從JVM里重新拿一份數據,那這個數據結構關聯的Method,Field字段等都是重新生成的對象。如果是重新生成的對象那可能有什么麻煩?講到后面就明白了

getDeclaredMethod方法其實很簡單,就是從privateGetDeclaredMethods返回的方法列表里復制一個Method對象返回。而這個復制的過程是通過searchMethods實現的

如果reflectionData這個屬性的declaredMethods非空,那privateGetDeclaredMethods就直接返回其就可以了,否則就從JVM里去撈一把出來,并賦值給reflectionData的字段,這樣下次再調用privateGetDeclaredMethods時候就可以用緩存數據了,不用每次調到JVM里去獲取數據,因為reflectionData是Softreference,所以存在取不到值的風險,一旦取不到就又去JVM里撈了

searchMethods將從privateGetDeclaredMethods返回的方法列表里找到一個同名的匹配的方法,然后復制一個方法對象出來,這個復制的具體實現,其實就是Method.copy方法:

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

Method調用

有了Method之后,那就可以調用其invoke方法了,那先看看Method的幾個關鍵信息

root屬性其實上面已經說了,主要指向緩存里的Method對象,也就是當前這個Method對象其實是根據root這個Method構建出來的,因此存在一個root Method派生出多個Method的情況。

methodAccessor這個很關鍵了,其實Method.invoke方法就是調用methodAccessor的invoke方法,methodAccessor這個屬性如果root本身已經有了,那就直接用root的methodAccessor賦值過來,否則的話就創建一個

MethodAccessor的實現

MethodAccessor本身就是一個接口

其主要有三種實現

  • DelegatingMethodAccessorImpl
  • NativeMethodAccessorImpl
  • GeneratedMethodAccessorXXX

其中DelegatingMethodAccessorImpl是最終注入給Method的methodAccessor的,也就是某個Method的所有的invoke方法都會調用到這個DelegatingMethodAccessorImpl.invoke,正如其名一樣的,是做代理的,也就是真正的實現可以是下面的兩種

如果是NativeMethodAccessorImpl,那顧名思義,該實現主要是native實現的,而GeneratedMethodAccessorXXX是為每個需要反射調用的Method動態生成的類,后的XXX是一個數字,不斷遞增的

并且所有的方法反射都是先走NativeMethodAccessorImpl,默認調了15次之后,才生成一個GeneratedMethodAccessorXXX類,生成好之后就會走這個生成的類的invoke方法了

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

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

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

那生成的GeneratedMethodAccessorXXX究竟長什么樣呢,大概這樣了

其實就是直接調用目標對象的具體方法了,和正常的方法調用沒什么區別

GeneratedMethodAccessorXXX的類加載器

那加載GeneratedMethodAccessorXXX的類加載器是什么呢,在生成好了字節碼之后會調用下面的方法做類定義

所以GeneratedMethodAccessorXXX的類加載器其實是一個DelegatingClassLoader類加載器

之所以搞一個新的類加載器,是為了性能考慮,在某些情況下可以卸載這些生成的類,因為類的卸載是只有在類加載器可以被回收的情況下才會被回收的,如果用了原來的類加載器,那可能導致這些新創建的類一直無法被卸載,從其設計來看本身就不希望他們一直存在內存里的,在需要的時候有就行了,在內存緊俏的時候可以釋放掉內存

并發導致垃圾類創建

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

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

有了上面對反射原理的了解之后,我們知道了在反射執行到一定次數之后,其實會動態構建一個類,在這個類里會直接調用目標對象的對應的方法,我們從heap dump里看到了有大量的DelegatingClassLoader類加載器加載了GeneratedMethodAccessorXXX類,那這些類到底是調用了什么方法呢,于是我們不得不做一件事,那就是將內存里的這些類都dump下來,然后對字節碼做一個統計分析一下

運行時Dump類字節碼

我們可以利用SA的接口從coredump里或者live進程里將對應的類dump下來,為了dump下來我們特定的類,首先我們寫一個Filter類

使用SA的jar($JAVA_HOME/lib/sa-jdi.jar)編譯好類之后,然后我們在編譯好的類目錄下調用下面的命令進行dump

這樣我們就可以將所有的GeneratedMethodAccessor給dump下來了,這個時候我們再通過javap -verbose GeneratedMethodAccessor9隨便看一個類的字節碼

看到上面關鍵的bci為36的那行,這里的方法便是我們反射調用的方法了,比如上面的那個反射調用的方法就是org/codehaus/xfire/util/ParamReader.readCode

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

dump出這些字節碼之后,我們對這些所有的類的字節碼做一個統計,就找出了所有的反射調用方法,然后發現某些model類(package都是相同的)居然產生了20多萬個類,這意味著有非常多的這些model類做反射

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

 

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

看到這里或許大家都明白了吧,前面也已經說了SoftReference是可能被GC回收掉的,時間一到在下次GC里就會被回收,如果被回收了,那就要重新獲取,然后相當于是調用的新的Method對象的invoke方法,那調用次數一多,就會產生新的動態構建的類,而這份類會一直存到直到可以回收Perm的GC。

G1回收Perm

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

解決方案

  • 升級到jdk8,可以在G1 GC過程中對類做卸載
  • 換一個序列化協議,不走方法反射的,比如hessian
  • 調整SoftRefLRUPolicyMSPerMB這個參數變大,不過這個不能治本

總結

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

這個系統在JDK7下使用G1,而這個版本的G1只有在Full GC的時候才會對Perm里的類做卸載,該系統因為大量的請求導致G1 GC發生很頻繁,同時該系統還設置了-XX:SoftRefLRUPolicyMSPerMB=0,那意味著SoftReference的生命周期不會跨GC周期,能很快被回收掉,這個系統存在大量的RPC調用,走的Xfire協議,對返回結果做反序列化的時候是走的Method.invoke的邏輯,而相關的method因此被SoftReference引用,因此很容易被回收,一旦被回收,那就創建一個新的Method對象,再調用其invoke方法,在調用到一定次數(15次)之后,就構建一個新的字節碼類,伴隨著GC的進行,同一個方法的字節碼類不斷構建,直到將Perm充滿觸發一次Full GC才得以釋放。

【本文是51CTO專欄作者李嘉鵬的原創文章,轉載請通過微信公眾號(你假笨,id:lovestblog)聯系作者本人獲取授權】

 

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

責任編輯:武曉燕 來源: 你假笨
相關推薦

2019-09-09 08:30:57

MYSQL代碼數據庫

2023-08-14 08:38:26

反射reflect結構體

2025-06-18 08:00:56

2023-06-20 06:44:14

Node.jsCPU 負載

2025-04-11 00:05:49

RPC底層分布式

2025-01-07 09:07:36

接口屬性路徑

2022-06-15 08:00:50

磁盤RedisRocketMQ

2023-11-07 08:13:53

分布式網絡

2022-12-02 14:20:09

Tetris鴻蒙

2022-11-29 16:35:02

Tetris鴻蒙

2023-03-30 09:32:27

2022-11-14 17:01:34

游戲開發畫布功能

2014-10-21 15:07:04

2022-03-31 18:59:43

數據庫InnoDBMySQL

2023-08-10 08:28:46

網絡編程通信

2021-08-27 07:06:09

DubboDocker技術

2021-01-12 05:08:49

DHCP協議模型

2022-10-18 07:33:57

Maven構建工具

2023-08-04 08:20:56

DockerfileDocker工具

2023-06-30 08:18:51

敏捷開發模式
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品国产色综合久久不卡蜜臀 | 在线免费小视频 | 亚洲精品国产电影 | 国产精品久久久久久吹潮日韩动画 | 男女羞羞视频大全 | 精品中文字幕在线 | 精久久 | 久久69精品久久久久久久电影好 | 色橹橹欧美在线观看视频高清 | 亚洲视频二区 | 久久久久亚洲视频 | 亚洲一区精品在线 | 91精品中文字幕一区二区三区 | 国产精品国产精品 | 欧美激情综合五月色丁香小说 | 欧美日韩高清在线观看 | 亚洲精品乱码久久久久久按摩观 | 国产精品视频网 | 中文字幕二区 | 丁香五月网久久综合 | 国产精品欧美日韩 | 毛片免费观看 | 91黄色免费看 | 四虎成人免费电影 | 一区二区日韩精品 | 91大神xh98xh系列全部 | 99热在线免费 | 91av小视频| 久久久久久久久久久高潮一区二区 | www在线视频 | 涩涩鲁亚洲精品一区二区 | 亚洲精品电影在线观看 | 一区二区国产精品 | 综合成人在线 | 久久精品欧美一区二区三区麻豆 | 综合五月 | 亚洲成av人片在线观看 | 国产69精品久久99不卡免费版 | 欧美最猛黑人 | 久久爱黑人激情av摘花 | 精品九九|