<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java高級(jí)用法,寫個(gè)代理侵入你 ?

          共 6233字,需瀏覽 13分鐘

           ·

          2021-12-12 08:55

          大家好,我是小菜。一個(gè)希望能夠成為 吹著牛X談架構(gòu) 的男人!如果你也想成為我想成為的人,不然點(diǎn)個(gè)關(guān)注做個(gè)伴,讓小菜不再孤單!

          本文主要介紹 Java高級(jí)用法之Insrument

          如有需要,可以參考

          如有幫助,不忘 點(diǎn)贊 ?

          微信公眾號(hào)已開(kāi)啟,菜農(nóng)曰,沒(méi)關(guān)注的同學(xué)們記得關(guān)注哦!

          小王是一個(gè)剛來(lái)不久的妹子,啊呸,是一個(gè)剛來(lái)不久的程序媛,經(jīng)常垂頭喪氣的~讓我很是不解,終于有一天我怕小王哪天想不開(kāi)離職了豈不是會(huì)增加我的工作量(部門為數(shù)不多的妹子 - 1)?于是乎,我主動(dòng)找小王進(jìn)行了談心找到了問(wèn)題所在,原來(lái)是小王編程經(jīng)驗(yàn)不足,不知道如何巧妙的進(jìn)行日志打印,那么因果關(guān)系就總結(jié)出來(lái)了:經(jīng)驗(yàn)不足導(dǎo)致編碼經(jīng)常出錯(cuò),編碼出錯(cuò)由于日志未打印導(dǎo)致排查困難,排查困難導(dǎo)致開(kāi)發(fā)抑郁。查到問(wèn)題的原因,那么進(jìn)行對(duì)癥下藥即可~

          其實(shí)以上問(wèn)題我相信很多小伙伴都遇到過(guò),開(kāi)發(fā)過(guò)程中未出現(xiàn)的錯(cuò)誤在上線后就頻頻出現(xiàn),那么只能不斷的進(jìn)行添加日志打印然后再打包上傳進(jìn)行問(wèn)題跟蹤,一天的時(shí)間絕大部分都浪費(fèi)在了打包上傳的上面。那么能不能直接進(jìn)行bug跟蹤,然后查看到問(wèn)題出錯(cuò)的所在?這種需求不亞于給奔跑中的汽車更換輪胎,匪夷所思卻又無(wú)可奈何~其實(shí)有開(kāi)發(fā)經(jīng)驗(yàn)的小伙伴已經(jīng)想出來(lái)一個(gè)中間件,那就是 Arthas!但是這篇文章不是介紹如何使用 Archas,而是我們自己能不能實(shí)現(xiàn)這種動(dòng)態(tài)調(diào)試的技能?那么就進(jìn)入我們今天的整體 --- Java Agent 技術(shù)

          Java Instrument

          這個(gè)玩意并不是什么 Java 的新特性,早在 JDK 1.5 的時(shí)候就誕生了,位于 ?java.lang.instrument.Instrumentation 中,它的作用就是用來(lái)在運(yùn)行的時(shí)候重新加載某個(gè)類的 calss 文件的 api。

          這種類的實(shí)現(xiàn)方式其實(shí)是一種 Java Agent 技術(shù),我們這里可以順帶了解一下什么是 Java Agent。

          一、Java Agent

          代理這個(gè)詞對(duì)于我們開(kāi)發(fā)人員來(lái)說(shuō)并不默認(rèn),我們經(jīng)常用到的 AOP 面向切面編程用到的就是代理方式。它可以動(dòng)態(tài)切入某個(gè)面,進(jìn)行代碼增強(qiáng) 。這種不用重復(fù)補(bǔ)充輪子的方式大大增加了我們開(kāi)發(fā)效率,那么這里捕獲到了一個(gè)關(guān)鍵詞 動(dòng)態(tài)。那么 Java Agent 如何實(shí)現(xiàn)?那就可以說(shuō)到 JVMTI(JVM Tool Interface) ,這是Java 虛擬機(jī)對(duì)外提供的 Native 編程接口,通過(guò)它我們可以獲取運(yùn)行時(shí)JVM的諸多信息,而 Agent 是一個(gè)運(yùn)行在目標(biāo) JVM 的特定程序,它可以從目標(biāo) JVM 獲取數(shù)據(jù),然后將數(shù)據(jù)傳遞給外部進(jìn)程,然后外部進(jìn)程可以根據(jù)獲取到的數(shù)據(jù)進(jìn)行動(dòng)態(tài)Enhance。

          那么 Java Agent 什么時(shí)候能夠加載?

          • 目標(biāo) JVM 啟動(dòng)時(shí)
          • 目標(biāo) JVM 運(yùn)行時(shí)

          那么我們關(guān)注的是 運(yùn)行時(shí) ,這樣子就能滿足我們動(dòng)態(tài)加載的需求。

          而 Java Agent看上去這么高大上,我們要如何編寫?當(dāng)然在 JDK 1.5 之前,實(shí)現(xiàn)起來(lái)是具有困難性的,我們需要編寫 Native 代碼來(lái)實(shí)現(xiàn),那么 JDK 1.5 之后我們就可以利用上面說(shuō)到的 Java Instrument 來(lái)實(shí)現(xiàn)了!

          首先我們先了解一下 Instrumentation 這個(gè)接口,其中有幾個(gè)方法:

          • addTransformer(ClassFileTransformer transformer, boolean canRetransform)

          加入一個(gè)轉(zhuǎn)換器 Transformer ,之后所有的目標(biāo)類加載都會(huì)被 Transformer 攔截,可自定義實(shí)現(xiàn) ClassFileTransformer 接口,重寫該接口的唯一方法 transform() 方法,返回值是轉(zhuǎn)換后的類字節(jié)碼文件

          • retransformClasses(Class... classes)

          對(duì) JVM 已經(jīng)加載的類重新觸發(fā)類加載,使用上面自定義的轉(zhuǎn)換器進(jìn)行處理。該方法可以修改方法體,常量池和屬性值,但不能新增、刪除、重命名屬性或方法,也不能修改方法的簽名

          • redefineClasses(ClassDefinition... definitions)

          此方法用于替換類的定義,而不引用現(xiàn)有類文件字節(jié)。

          • getObjectSize(Object objectToSize)

          獲取一個(gè)對(duì)象的大小

          • appendToBootstrapClassLoaderSearch(JarFile jarfile)

          將一個(gè) jar 文件添加到 bootstrap classload 的 classPath 中

          • getAllLoadedClasses()

          獲取當(dāng)前被 JVM 加載的所有類對(duì)象

          redefineClasses 和 retransformClasses 補(bǔ)充說(shuō)明

          • 兩者區(qū)別:

          redefineClasses 是自己提供字節(jié)碼文件替換掉已存在的 class 文件

          retransformClasses 是在已存在的字節(jié)碼文件上修改后再進(jìn)行替換

          • 替換后生效的時(shí)機(jī)

          如果一個(gè)被修改的方法已經(jīng)在棧幀中存在,則棧幀中的方法會(huì)繼續(xù)使用舊字節(jié)碼運(yùn)行,新字節(jié)碼會(huì)在新棧幀中運(yùn)行

          • 注意點(diǎn)

          兩個(gè)方法都是只能改變類的方法體、常量池和屬性值,但不能新增、刪除、重命名屬性或方法,也不能修改方法的簽名

          二、實(shí)現(xiàn) Agent

          1、編寫方法

          上面我們已經(jīng)說(shuō)到了有兩處地方可以進(jìn)行 Java Agent 的加載,分別是 目標(biāo)JVM啟動(dòng)時(shí)加載目標(biāo)JVM運(yùn)行時(shí)加載,這兩種不同的加載模式使用不同的入口函數(shù):

          1、JVM 啟動(dòng)時(shí)加載

          入口函數(shù)如下所示:

          ?//?函數(shù)1
          public?static?void?premain(String?agentArgs,?Instrumentation?inst);
          //?函數(shù)2
          public?static?void?premain(String?agentArgs);

          JVM 首先尋找函數(shù)1,如果沒(méi)有發(fā)現(xiàn)函數(shù)1,則會(huì)尋找函數(shù)2

          2、JVM 運(yùn)行時(shí)加載

          入口函數(shù)如下所示:

          //?函數(shù)1
          public?static?void?agentmain(String?agentArgs,?Instrumentation?inst);
          //?函數(shù)2
          public?static?void?agentmain(String?agentArgs);

          與上述一致,JVM 首先尋找函數(shù)1,如果沒(méi)有發(fā)現(xiàn)函數(shù)1,則會(huì)尋找函數(shù)2

          這兩組方法的第一個(gè)參數(shù) agentArgs 是隨同 “-javaagent” 一起傳入的程序參數(shù),如果這個(gè)字符串代表了多個(gè)參數(shù),就需要自己解析這參數(shù),inst 是 Instrumentation 類型的對(duì)象,是 JVM 自己傳入的,我們可以?拿這個(gè)參數(shù)進(jìn)行參數(shù)的增強(qiáng)操作。

          2、聲明方法

          當(dāng)定義完這兩組方法后,要使之生效還需要手動(dòng)聲明,聲明方式有兩種:

          1、使用 MANIFEST.MF 文件

          我們需要?jiǎng)?chuàng)建resources/META-INF.MANIFEST.MF 文件,當(dāng) jar包打包時(shí)將文件一并打包,文件內(nèi)容如下:

          Manifest-Version:?1.0
          Can-Redefine-Classes:?true???#?true表示能重定義此代理所需的類,默認(rèn)值為?false(可選)
          Can-Retransform-Classes:?true????#?true?表示能重轉(zhuǎn)換此代理所需的類,默認(rèn)值為?false?(可選)
          Premain-Class:?cbuc.life.agent.MainAgentDemo???#premain方法所在類的位置
          Agentmain-Class:?cbuc.life.agent.MainAgentDemo???#agentmain方法所在類的位置

          2、如果是maven項(xiàng)目,在pom.xml加入

          3、指定 agent

          要讓目標(biāo)JVM認(rèn)你這個(gè) Agent ,你就要給目標(biāo)JVM介紹這個(gè) Agent

          1、JVM 啟動(dòng)時(shí)加載

          我們直接在 JVM 啟動(dòng)參數(shù)中加入 -javaagent 參數(shù)并指定 jar 文件的位置

          #?將該類編譯成?class?文件
          javac?TargetJvm.java
          #?指定agent程序并運(yùn)行該類
          java?-javaagent:./java-agent.jar?TargetJvm

          2、JVM 運(yùn)行時(shí)加載

          要實(shí)現(xiàn)動(dòng)態(tài)調(diào)試,我們就不能將目標(biāo)JVM停機(jī)后再重新啟動(dòng),這不符合我們的初衷,因此我們可以使用 JDK 的 Attach Api 來(lái)實(shí)現(xiàn)運(yùn)行時(shí)掛載 Agent。

          Attach Api 是 SUN 公司提供的一套擴(kuò)展 API,用來(lái)向目標(biāo) JVM 附著(attach)在目標(biāo)程序上,有了它我們可以很方便地監(jiān)控一個(gè) JVM。Attach Api 對(duì)應(yīng)的代碼位于 com.sun.tools.attach包下,提供的功能也非常簡(jiǎn)單:

          • 列出當(dāng)前所有的 JVM 實(shí)例描述
          • Attach 到其中一個(gè) JVM 上,建立通信管道
          • 讓目標(biāo)JVM加載Agent

          該包下有一個(gè)類 VirtualMachine,它提供了兩個(gè)重要的方法:

          • VirtualMachine attach(String var0)

          傳遞一個(gè)進(jìn)程號(hào),返回目標(biāo) JVM 進(jìn)程的 vm 對(duì)象,該方法是 JVM進(jìn)程之間指令傳遞的橋梁,底層是通過(guò) socket 進(jìn)行通信

          • void loadAgent(String var1)

          該方法允許我們將 agent 對(duì)應(yīng)的 jar 文件地址作為參數(shù)傳遞給目標(biāo) JVM,目標(biāo) JVM 收到該命令后會(huì)加載這個(gè) Agent

          有了 Attach Api ,我們就可以創(chuàng)建一個(gè)java進(jìn)程,用它attach到對(duì)應(yīng)的jvm,并加載agent。

          以下是簡(jiǎn)單的 Attach 代碼實(shí)現(xiàn):

          注意:在mac上安裝了的jdk是能直接找到 VirtualMachine 類的,但是在windows中安裝的jdk無(wú)法找到,如果你遇到這種情況,請(qǐng)手動(dòng)將你jdk安裝目錄下:lib目錄中的tools.jar添加進(jìn)當(dāng)前工程的Libraries中。

          上面代碼十分簡(jiǎn)易的實(shí)現(xiàn)了 Attach 的方式,通過(guò)尋找當(dāng)前系統(tǒng)中所有運(yùn)行的 JVM 進(jìn)程,然后通過(guò)比對(duì) PID 來(lái)篩選出目標(biāo)JVM,然后讓 Agent 附著在目標(biāo) JVM 上。當(dāng)然這邊已經(jīng)簡(jiǎn)易到直接在代碼中指定目標(biāo)JVM的 PID,這種方式在實(shí)際生產(chǎn)中是十分不可取的,我們可以通過(guò)動(dòng)態(tài)參數(shù)的方式傳入 PID~!而 Attach 的執(zhí)行原理也不復(fù)雜,簡(jiǎn)單流程如下:

          三、案例說(shuō)明

          我們上述簡(jiǎn)單聊了下 Java Agent 的實(shí)現(xiàn)過(guò)程,那我們下面也簡(jiǎn)單寫個(gè)案例來(lái)理解一下 Java Agent 的實(shí)現(xiàn)過(guò)程~

          我們上面說(shuō)到可以使用 Java Instrumentation 來(lái)完成動(dòng)態(tài)類修改的功能,并且在 Instrumentation 接口中我們可以通過(guò) addTransformer() 方法來(lái)增加一個(gè)類轉(zhuǎn)換器,類轉(zhuǎn)換器由類 ClassFileTransformer 接口實(shí)現(xiàn)。該接口中有一個(gè)唯一的方法 transform() 用于實(shí)現(xiàn)類的轉(zhuǎn)換,也就是我們可以增強(qiáng)類處理的地方!當(dāng)類被加載的時(shí)候就會(huì)調(diào)用 transform()方法,實(shí)現(xiàn)對(duì)類加載的事件進(jìn)行攔截并返回轉(zhuǎn)換后新的字節(jié)碼,通過(guò) redefineClasses()retransformClasses()都可以觸發(fā)類的重新加載事件。

          實(shí)際操作
          1)準(zhǔn)備目標(biāo)JVM

          我們這里直接使用一個(gè) SpringBoot 項(xiàng)目來(lái)試驗(yàn),方便大家增強(qiáng)改造~ 項(xiàng)目結(jié)構(gòu)如下:

          target-jvm
          ????├─src
          ???????├─main
          ??????????├─java
          ?????????????└─cbuc
          ?????????????????└─life
          ?????????????????????└─targetjvm
          ?????????????????????????├─controller
          ?????????????????????????|?????└─TestController.java
          ?????????????????????????└─service
          ?????????????????????????|?????└─SimpleService.java
          ?????????????????????????└─TargetJvmApplication.java

          其中 TestControllerSimpleService 兩個(gè)類的內(nèi)容也很簡(jiǎn)單,直接貼代碼

          2)準(zhǔn)備 Agent
          1、編寫方法

          然后編寫我們的Agent jar包。因?yàn)閼卸瑁晕疫@邊將 premain 和 agentmain 兩個(gè)方法寫在同一個(gè) jar 包中,然后分別以 啟動(dòng)時(shí)運(yùn)行時(shí) 來(lái)模擬場(chǎng)景~

          很簡(jiǎn)單,一個(gè)類中包含了我們需要的所有功能~ 防止圖片內(nèi)容過(guò)于擁擠,小菜貼心地分別粘貼出核心代碼:

          • premain
          • agentmain
          • ClassFileTransformer
          2)聲明方法

          然后將 Agent 打包,打包的時(shí)候需要在 pom.xml 文件中添加以下內(nèi)容

          然后運(yùn)行mvn assembly:assembly 既可

          3)啟動(dòng) Agent

          當(dāng)我們已經(jīng)準(zhǔn)備好了兩個(gè) jar 包便可以開(kāi)始測(cè)試了!

          1、啟動(dòng)時(shí)加載
          nohup?java?-javaagent:./java-agent-jar-with-dependencies.jar?-jar?target-jvm.jar?&

          我們直接啟動(dòng)時(shí)添加參數(shù),帶上我們的 Agent jar包

          結(jié)果并沒(méi)有讓小菜太尷尬,成功的實(shí)現(xiàn)我們想要的功能,但是這只是啟動(dòng)時(shí)加載,明顯不是我們想要的~ 我們來(lái)試下運(yùn)行時(shí)如何加載

          2、運(yùn)行時(shí)加載

          正常運(yùn)行下,方法并沒(méi)有做耗時(shí)統(tǒng)計(jì),我們的需求就來(lái)了,我們想要統(tǒng)計(jì)該方法的耗時(shí),首先獲取該進(jìn)程ID

          然后通過(guò) Attach 方式(調(diào)用controller 的 active() 方法)附著 Agent,我們可以實(shí)時(shí)查看控制臺(tái)

          已經(jīng)可以看到 Agent 似乎已經(jīng)成功附著了,然后我們繼續(xù)請(qǐng)求 test 接口

          可以發(fā)現(xiàn) resolve 方法已經(jīng)被我們?cè)鰪?qiáng)了!

          四、題外話

          上面我們已經(jīng)簡(jiǎn)單的實(shí)現(xiàn)了動(dòng)態(tài)操作目標(biāo)類文件,文章開(kāi)頭就說(shuō)明了給奔跑中的汽車更換輪胎是一個(gè)匪夷所思卻又無(wú)可奈何的需求,但是這個(gè)需求能不能讓別人實(shí)現(xiàn),其實(shí)是可以的,而這個(gè)就是小菜的主要目的,我們了解了如何實(shí)現(xiàn)動(dòng)態(tài)換輪胎的原理后,當(dāng)我們運(yùn)用其成熟的中間件也能更加應(yīng)手而不會(huì)不知所措,知識(shí)不能讓我們只學(xué)會(huì)臥槽兩個(gè)字,而是當(dāng)別人實(shí)現(xiàn)的時(shí)候我們能默默思考,思考后再說(shuō)出牛逼~!感興趣的同學(xué)不妨拉取一下源碼演練一番:Arthas gitee,已經(jīng)使用過(guò)類似 Arthas 或 BTrace 的同學(xué),看完相信會(huì)更加了解其工作運(yùn)行原理,沒(méi)使用過(guò)的同學(xué)下次用到的時(shí)候也不會(huì)那么戰(zhàn)戰(zhàn)兢兢!

          不要空談,不要貪懶,和小菜一起做個(gè)吹著牛X做架構(gòu)的程序猿吧~點(diǎn)個(gè)關(guān)注做個(gè)伴,讓小菜不再孤單。咱們下文見(jiàn)!

          今天的你多努力一點(diǎn),明天的你就能少說(shuō)一句求人的話!我是小菜,一個(gè)和你一起變強(qiáng)的男人。 ??微信公眾號(hào)已開(kāi)啟,菜農(nóng)曰,沒(méi)關(guān)注的同學(xué)們記得關(guān)注哦!


          瀏覽 52
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲视频在线观看视频 | 91亚洲成人电影 | 97人妻色色 | 永久免费一区二区三区 | 九一九色国 |