色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一文詳解Java動(dòng)態(tài)調(diào)試技術(shù)

h1654155282.3538 ? 來(lái)源:美團(tuán)技術(shù)團(tuán)隊(duì) ? 作者:美團(tuán)技術(shù)團(tuán)隊(duì) ? 2020-10-18 11:33 ? 次閱讀

調(diào)試是發(fā)現(xiàn)和減少計(jì)算機(jī)程序或電子儀器設(shè)備中程序錯(cuò)誤的一個(gè)過(guò)程。最常用的斷點(diǎn)調(diào)試技術(shù)會(huì)在斷點(diǎn)位置停頓,導(dǎo)致應(yīng)用停止響應(yīng)。本文將介紹一種Java動(dòng)態(tài)調(diào)試技術(shù),希望能對(duì)大家有幫助。

1. 動(dòng)態(tài)調(diào)試要解決的問(wèn)題

斷點(diǎn)調(diào)試是我們最常使用的調(diào)試手段,它可以獲取到方法執(zhí)行過(guò)程中的變量信息,并可以觀察到方法的執(zhí)行路徑。但斷點(diǎn)調(diào)試會(huì)在斷點(diǎn)位置停頓,使得整個(gè)應(yīng)用停止響應(yīng)。在線上停頓應(yīng)用是致命的,動(dòng)態(tài)調(diào)試技術(shù)給了我們創(chuàng)造新的調(diào)試模式的想象空間。本文將研究Java語(yǔ)言中的動(dòng)態(tài)調(diào)試技術(shù),首先概括Java動(dòng)態(tài)調(diào)試所涉及的技術(shù)基礎(chǔ),接著介紹我們?cè)贘ava動(dòng)態(tài)調(diào)試領(lǐng)域的思考及實(shí)踐,通過(guò)結(jié)合實(shí)際業(yè)務(wù)場(chǎng)景,設(shè)計(jì)并實(shí)現(xiàn)了一種具備動(dòng)態(tài)性的斷點(diǎn)調(diào)試工具Java-debug-tool,顯著提高了故障排查效率。

2. Java Agent技術(shù)

JVMTI (JVM Tool Interface)是Java虛擬機(jī)對(duì)外提供的Native編程接口,通過(guò)JVMTI,外部進(jìn)程可以獲取到運(yùn)行時(shí)JVM的諸多信息,比如線程、GC等。Agent是一個(gè)運(yùn)行在目標(biāo)JVM的特定程序,它的職責(zé)是負(fù)責(zé)從目標(biāo)JVM中獲取數(shù)據(jù),然后將數(shù)據(jù)傳遞給外部進(jìn)程。加載Agent的時(shí)機(jī)可以是目標(biāo)JVM啟動(dòng)之時(shí),也可以是在目標(biāo)JVM運(yùn)行時(shí)進(jìn)行加載,而在目標(biāo)JVM運(yùn)行時(shí)進(jìn)行Agent加載具備動(dòng)態(tài)性,對(duì)于時(shí)機(jī)未知的Debug場(chǎng)景來(lái)說(shuō)非常實(shí)用。下面將詳細(xì)分析Java Agent技術(shù)的實(shí)現(xiàn)細(xì)節(jié)。

2.1 Agent的實(shí)現(xiàn)模式

JVMTI是一套Native接口,在Java SE 5之前,要實(shí)現(xiàn)一個(gè)Agent只能通過(guò)編寫Native代碼來(lái)實(shí)現(xiàn)。從Java SE 5開(kāi)始,可以使用Java的Instrumentation接口(java.lang.instrument)來(lái)編寫Agent。無(wú)論是通過(guò)Native的方式還是通過(guò)Java Instrumentation接口的方式來(lái)編寫Agent,它們的工作都是借助JVMTI來(lái)進(jìn)行完成,下面介紹通過(guò)Java Instrumentation接口編寫Agent的方法。

2.1.1 通過(guò)Java Instrumentation API

實(shí)現(xiàn)Agent啟動(dòng)方法

Java Agent支持目標(biāo)JVM啟動(dòng)時(shí)加載,也支持在目標(biāo)JVM運(yùn)行時(shí)加載,這兩種不同的加載模式會(huì)使用不同的入口函數(shù),如果需要在目標(biāo)JVM啟動(dòng)的同時(shí)加載Agent,那么可以選擇實(shí)現(xiàn)下面的方法:

JVM將首先尋找[1],如果沒(méi)有發(fā)現(xiàn)[1],再尋找[2]。如果希望在目標(biāo)JVM運(yùn)行時(shí)加載Agent,則需要實(shí)現(xiàn)下面的方法:

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

指定Main-Class

Agent需要打包成一個(gè)jar包,在ManiFest屬性中指定“Premain-Class”或者“Agent-Class”:

掛載到目標(biāo)JVM

將編寫的Agent打成jar包后,就可以掛載到目標(biāo)JVM上去了。如果選擇在目標(biāo)JVM啟動(dòng)時(shí)加載Agent,則可以使用 “-javaagent:《jarpath》[=《option》]”,具體的使用方法可以使用“Java -Help”來(lái)查看。如果想要在運(yùn)行時(shí)掛載Agent到目標(biāo)JVM,就需要做一些額外的開(kāi)發(fā)了。

com.sun.tools.attach.VirtualMachine 這個(gè)類代表一個(gè)JVM抽象,可以通過(guò)這個(gè)類找到目標(biāo)JVM,并且將Agent掛載到目標(biāo)JVM上。下面是使用com.sun.tools.attach.VirtualMachine進(jìn)行動(dòng)態(tài)掛載Agent的一般實(shí)現(xiàn):

首先通過(guò)指定的進(jìn)程ID找到目標(biāo)JVM,然后通過(guò)Attach掛載到目標(biāo)JVM上,執(zhí)行加載Agent操作。VirtualMachine的Attach方法就是用來(lái)將Agent掛載到目標(biāo)JVM上去的,而Detach則是將Agent從目標(biāo)JVM卸載。關(guān)于Agent是如何掛載到目標(biāo)JVM上的具體技術(shù)細(xì)節(jié),將在下文中進(jìn)行分析。

2.2 啟動(dòng)時(shí)加載Agent

2.2.1 參數(shù)解析

創(chuàng)建JVM時(shí),JVM會(huì)進(jìn)行參數(shù)解析,即解析那些用來(lái)配置JVM啟動(dòng)的參數(shù),比如堆大小、GC等;本文主要關(guān)注解析的參數(shù)為-agentlib、 -agentpath、 -javaagent,這幾個(gè)參數(shù)用來(lái)指定Agent,JVM會(huì)根據(jù)這幾個(gè)參數(shù)加載Agent。下面來(lái)分析一下JVM是如何解析這幾個(gè)參數(shù)的。

上面的代碼片段截取自hotspot/src/share/vm/runtime/arguments.cpp中的Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, bool* patch_mod_javabase, Flag::Flags origin) 函數(shù),該函數(shù)用來(lái)解析一個(gè)具體的JVM參數(shù)。這段代碼的主要功能是解析出需要加載的Agent路徑,然后調(diào)用add_init_agent函數(shù)進(jìn)行解析結(jié)果的存儲(chǔ)。下面先看一下add_init_agent函數(shù)的具體實(shí)現(xiàn):

AgentLibraryList是一個(gè)簡(jiǎn)單的鏈表結(jié)構(gòu),add_init_agent函數(shù)將解析好的、需要加載的Agent添加到這個(gè)鏈表中,等待后續(xù)的處理。

這里需要注意,解析-javaagent參數(shù)有一些特別之處,這個(gè)參數(shù)用來(lái)指定一個(gè)我們通過(guò)Java Instrumentation API來(lái)編寫的Agent,Java Instrumentation API底層依賴的是JVMTI,對(duì)-JavaAgent的處理也說(shuō)明了這一點(diǎn),在調(diào)用add_init_agent函數(shù)時(shí)第一個(gè)參數(shù)是“instrument”,關(guān)于加載Agent這個(gè)問(wèn)題在下一小節(jié)進(jìn)行展開(kāi)。到此,我們知道在啟動(dòng)JVM時(shí)指定的Agent已經(jīng)被JVM解析完存放在了一個(gè)鏈表結(jié)構(gòu)中。下面來(lái)分析一下JVM是如何加載這些Agent的。

2.2.2 執(zhí)行加載操作

在創(chuàng)建JVM進(jìn)程的函數(shù)中,解析完JVM參數(shù)之后,下面的這段代碼和加載Agent相關(guān):

當(dāng)JVM判斷出上一小節(jié)中解析出來(lái)的Agent不為空的時(shí)候,就要去調(diào)用函數(shù)create_vm_init_agents來(lái)加載Agent,下面來(lái)分析一下create_vm_init_agents函數(shù)是如何加載Agent的。

create_vm_init_agents這個(gè)函數(shù)通過(guò)遍歷Agent鏈表來(lái)逐個(gè)加載Agent。通過(guò)這段代碼可以看出,首先通過(guò)lookup_agent_on_load來(lái)加載Agent并且找到Agent_OnLoad函數(shù),這個(gè)函數(shù)是Agent的入口函數(shù)。如果沒(méi)找到這個(gè)函數(shù),則認(rèn)為是加載了一個(gè)不合法的Agent,則什么也不做,否則調(diào)用這個(gè)函數(shù),這樣Agent的代碼就開(kāi)始執(zhí)行起來(lái)了。對(duì)于使用Java Instrumentation API來(lái)編寫Agent的方式來(lái)說(shuō),在解析階段觀察到在add_init_agent函數(shù)里面?zhèn)鬟f進(jìn)去的是一個(gè)叫做“instrument”的字符串,其實(shí)這是一個(gè)動(dòng)態(tài)鏈接庫(kù)。在Linux里面,這個(gè)庫(kù)叫做libinstrument.so,在BSD系統(tǒng)中叫做libinstrument.dylib,該動(dòng)態(tài)鏈接庫(kù)在{JAVA_HOME}/jre/lib/目錄下。

2.2.3 Instrument動(dòng)態(tài)鏈接庫(kù)

libinstrument用來(lái)支持使用Java Instrumentation API來(lái)編寫Agent,在libinstrument中有一個(gè)非常重要的類稱為:JPLISAgent(Java Programming Language Instrumentation Services Agent),它的作用是初始化所有通過(guò)Java Instrumentation API編寫的Agent,并且也承擔(dān)著通過(guò)JVMTI實(shí)現(xiàn)Java Instrumentation中暴露API的責(zé)任。

我們已經(jīng)知道,在JVM啟動(dòng)的時(shí)候,JVM會(huì)通過(guò)-javaagent參數(shù)加載Agent。最開(kāi)始加載的是libinstrument動(dòng)態(tài)鏈接庫(kù),然后在動(dòng)態(tài)鏈接庫(kù)里面找到JVMTI的入口方法:Agent_OnLoad。下面就來(lái)分析一下在libinstrument動(dòng)態(tài)鏈接庫(kù)中,Agent_OnLoad函數(shù)是怎么實(shí)現(xiàn)的。

上述代碼片段是經(jīng)過(guò)精簡(jiǎn)的libinstrument中Agent_OnLoad實(shí)現(xiàn)的,大概的流程就是:先創(chuàng)建一個(gè)JPLISAgent,然后將ManiFest中設(shè)定的一些參數(shù)解析出來(lái), 比如(Premain-Class)等。創(chuàng)建了JPLISAgent之后,調(diào)用initializeJPLISAgent對(duì)這個(gè)Agent進(jìn)行初始化操作。跟進(jìn)initializeJPLISAgent看一下是如何初始化的:

這里,我們關(guān)注callbacks.VMInit = &eventHandlerVMInit;這行代碼,這里設(shè)置了一個(gè)VMInit事件的回調(diào)函數(shù),表示在JVM初始化的時(shí)候會(huì)回調(diào)eventHandlerVMInit函數(shù)。下面來(lái)看一下這個(gè)函數(shù)的實(shí)現(xiàn)細(xì)節(jié),猜測(cè)就是在這里調(diào)用了Premain方法:

看到這里,Instrument已經(jīng)實(shí)例化,invokeJavaAgentMainMethod這個(gè)方法將我們的Premain方法執(zhí)行起來(lái)了。接著,我們就可以根據(jù)Instrument實(shí)例來(lái)做我們想要做的事情了。

2.3 運(yùn)行時(shí)加載Agent

比起JVM啟動(dòng)時(shí)加載Agent,運(yùn)行時(shí)加載Agent就比較有誘惑力了,因?yàn)檫\(yùn)行時(shí)加載Agent的能力給我們提供了很強(qiáng)的動(dòng)態(tài)性,我們可以在需要的時(shí)候加載Agent來(lái)進(jìn)行一些工作。因?yàn)槭莿?dòng)態(tài)的,我們可以按照需求來(lái)加載所需要的Agent,下面來(lái)分析一下動(dòng)態(tài)加載Agent的相關(guān)技術(shù)細(xì)節(jié)。

2.3.1 AttachListener

Attach機(jī)制通過(guò)Attach Listener線程來(lái)進(jìn)行相關(guān)事務(wù)的處理,下面來(lái)看一下Attach Listener線程是如何初始化的。

我們知道,一個(gè)線程啟動(dòng)之后都需要指定一個(gè)入口來(lái)執(zhí)行代碼,Attach Listener線程的入口是attach_listener_thread_entry,下面看一下這個(gè)函數(shù)的具體實(shí)現(xiàn):

整個(gè)函數(shù)執(zhí)行邏輯,大概是這樣的:

拉取一個(gè)需要執(zhí)行的任務(wù):AttachListener::dequeue。

查詢匹配的命令處理函數(shù)。

執(zhí)行匹配到的命令執(zhí)行函數(shù)。

其中第二步里面存在一個(gè)命令函數(shù)表,整個(gè)表如下:

對(duì)于加載Agent來(lái)說(shuō),命令就是“l(fā)oad”。現(xiàn)在,我們知道了Attach Listener大概的工作模式,但是還是不太清楚任務(wù)從哪來(lái),這個(gè)秘密就藏在AttachListener::dequeue這行代碼里面,接下來(lái)我們來(lái)分析一下dequeue這個(gè)函數(shù):

這是Linux上的實(shí)現(xiàn),不同的操作系統(tǒng)實(shí)現(xiàn)方式不太一樣。上面的代碼表面,Attach Listener在某個(gè)端口監(jiān)聽(tīng)著,通過(guò)accept來(lái)接收一個(gè)連接,然后從這個(gè)連接里面將請(qǐng)求讀取出來(lái),然后將請(qǐng)求包裝成一個(gè)AttachOperation類型的對(duì)象,之后就會(huì)從表里查詢對(duì)應(yīng)的處理函數(shù),然后進(jìn)行處理。

Attach Listener使用一種被稱為“懶加載”的策略進(jìn)行初始化,也就是說(shuō),JVM啟動(dòng)的時(shí)候Attach Listener并不一定會(huì)啟動(dòng)起來(lái)。下面我們來(lái)分析一下這種“懶加載”策略的具體實(shí)現(xiàn)方案。

上面的代碼截取自create_vm函數(shù),DisableAttachMechanism、StartAttachListener和ReduceSignalUsage這三個(gè)變量默認(rèn)都是false,所以AttachListener::init();這行代碼不會(huì)在create_vm的時(shí)候執(zhí)行,而vm_start會(huì)執(zhí)行。下面來(lái)看一下這個(gè)函數(shù)的實(shí)現(xiàn)細(xì)節(jié):

這是在Linux上的實(shí)現(xiàn),是將/tmp/目錄下的.java_pid{pid}文件刪除,后面在創(chuàng)建Attach Listener線程的時(shí)候會(huì)創(chuàng)建出來(lái)這個(gè)文件。上面說(shuō)到,AttachListener::init()這行代碼不會(huì)在create_vm的時(shí)候執(zhí)行,這行代碼的實(shí)現(xiàn)已經(jīng)在上文中分析了,就是創(chuàng)建Attach Listener線程,并監(jiān)聽(tīng)其他JVM的命令請(qǐng)求。現(xiàn)在來(lái)分析一下這行代碼是什么時(shí)候被調(diào)用的,也就是“懶加載”到底是怎么加載起來(lái)的。

這是create_vm中的一段代碼,看起來(lái)跟信號(hào)相關(guān),其實(shí)Attach機(jī)制就是使用信號(hào)來(lái)實(shí)現(xiàn)“懶加載“的。下面我們來(lái)仔細(xì)地分析一下這個(gè)過(guò)程。

JVM創(chuàng)建了一個(gè)新的進(jìn)程來(lái)實(shí)現(xiàn)信號(hào)處理,這個(gè)線程叫“Signal Dispatcher”,一個(gè)線程創(chuàng)建之后需要有一個(gè)入口,“Signal Dispatcher”的入口是signal_thread_entry:

這段代碼截取自signal_thread_entry函數(shù),截取中的內(nèi)容是和Attach機(jī)制信號(hào)處理相關(guān)的代碼。這段代碼的意思是,當(dāng)接收到“SIGBREAK”信號(hào),就執(zhí)行接下來(lái)的代碼,這個(gè)信號(hào)是需要Attach到JVM上的信號(hào)發(fā)出來(lái),這個(gè)后面會(huì)再分析。我們先來(lái)看一句關(guān)鍵的代碼:AttachListener::is_init_trigger():

首先檢查了一下是否在JVM啟動(dòng)時(shí)啟動(dòng)了Attach Listener,或者是否已經(jīng)啟動(dòng)過(guò)。如果沒(méi)有,才繼續(xù)執(zhí)行,在/tmp目錄下創(chuàng)建一個(gè)叫做.attach_pid%d的文件,然后執(zhí)行AttachListener的init函數(shù),這個(gè)函數(shù)就是用來(lái)創(chuàng)建Attach Listener線程的函數(shù),上面已經(jīng)提到多次并進(jìn)行了分析。到此,我們知道Attach機(jī)制的奧秘所在,也就是Attach Listener線程的創(chuàng)建依靠Signal Dispatcher線程,Signal Dispatcher是用來(lái)處理信號(hào)的線程,當(dāng)Signal Dispatcher線程接收到“SIGBREAK”信號(hào)之后,就會(huì)執(zhí)行初始化Attach Listener的工作。

2.3.2 運(yùn)行時(shí)加載Agent的實(shí)現(xiàn)

我們繼續(xù)分析,到底是如何將一個(gè)Agent掛載到運(yùn)行著的目標(biāo)JVM上,在上文中提到了一段代碼,用來(lái)進(jìn)行運(yùn)行時(shí)掛載Agent,可以參考上文中展示的關(guān)于“attachAgentToTargetJvm”方法的代碼。這個(gè)方法里面的關(guān)鍵是調(diào)用VirtualMachine的attach方法進(jìn)行Agent掛載的功能。下面我們就來(lái)分析一下VirtualMachine的attach方法具體是怎么實(shí)現(xiàn)的。

這個(gè)方法通過(guò)attachVirtualMachine方法進(jìn)行attach操作,在MacOS系統(tǒng)中,AttachProvider的實(shí)現(xiàn)類是BsdAttachProvider。我們來(lái)看一下BsdAttachProvider的attachVirtualMachine方法是如何實(shí)現(xiàn)的:

findSocketFile方法用來(lái)查詢目標(biāo)JVM上是否已經(jīng)啟動(dòng)了Attach Listener,它通過(guò)檢查“tmp/”目錄下是否存在java_pid{pid}來(lái)進(jìn)行實(shí)現(xiàn)。如果已經(jīng)存在了,則說(shuō)明Attach機(jī)制已經(jīng)準(zhǔn)備就緒,可以接受客戶端的命令了,這個(gè)時(shí)候客戶端就可以通過(guò)connect連接到目標(biāo)JVM進(jìn)行命令的發(fā)送,比如可以發(fā)送“l(fā)oad”命令來(lái)加載Agent。如果java_pid{pid}文件還不存在,則需要通過(guò)sendQuitTo方法向目標(biāo)JVM發(fā)送一個(gè)“SIGBREAK”信號(hào),讓它初始化Attach Listener線程并準(zhǔn)備接受客戶端連接。可以看到,發(fā)送了信號(hào)之后客戶端會(huì)循環(huán)等待java_pid{pid}這個(gè)文件,之后再通過(guò)connect連接到目標(biāo)JVM上。

2.3.3 load命令的實(shí)現(xiàn)

下面來(lái)分析一下,“l(fā)oad”命令在JVM層面的實(shí)現(xiàn):

這個(gè)函數(shù)先確保加載了java.instrument模塊,之后真正執(zhí)行Agent加載的函數(shù)是 load_agent_library ,這個(gè)函數(shù)的套路就是加載Agent動(dòng)態(tài)鏈接庫(kù),如果是通過(guò)Java instrument API實(shí)現(xiàn)的Agent,則加載的是libinstrument動(dòng)態(tài)鏈接庫(kù),然后通過(guò)libinstrument里面的代碼實(shí)現(xiàn)運(yùn)行agentmain方法的邏輯,這一部分內(nèi)容和libinstrument實(shí)現(xiàn)premain方法運(yùn)行的邏輯其實(shí)差不多,這里不再做分析。至此,我們對(duì)Java Agent技術(shù)已經(jīng)有了一個(gè)全面而細(xì)致的了解。

3. 動(dòng)態(tài)替換類字節(jié)碼技術(shù)

3.1 動(dòng)態(tài)字節(jié)碼修改的限制

上文中已經(jīng)詳細(xì)分析了Agent技術(shù)的實(shí)現(xiàn),我們使用Java Instrumentation API來(lái)完成動(dòng)態(tài)類修改的功能,在Instrumentation接口中,通過(guò)addTransformer方法來(lái)增加一個(gè)類轉(zhuǎn)換器,類轉(zhuǎn)換器由類ClassFileTransformer接口實(shí)現(xiàn)。ClassFileTransformer接口中唯一的方法transform用于實(shí)現(xiàn)類轉(zhuǎn)換,當(dāng)類被加載的時(shí)候,就會(huì)調(diào)用transform方法,進(jìn)行類轉(zhuǎn)換。在運(yùn)行時(shí),我們可以通過(guò)Instrumentation的redefineClasses方法進(jìn)行類重定義,在方法上有一段注釋需要特別注意:

這里面提到,我們不可以增加、刪除或者重命名字段和方法,改變方法的簽名或者類的繼承關(guān)系。認(rèn)識(shí)到這一點(diǎn)很重要,當(dāng)我們通過(guò)ASM獲取到增強(qiáng)的字節(jié)碼之后,如果增強(qiáng)后的字節(jié)碼沒(méi)有遵守這些規(guī)則,那么調(diào)用redefineClasses方法來(lái)進(jìn)行類的重定義就會(huì)失敗。那redefineClasses方法具體是怎么實(shí)現(xiàn)類的重定義的呢?它對(duì)運(yùn)行時(shí)的JVM會(huì)造成什么樣的影響呢?下面來(lái)分析redefineClasses的實(shí)現(xiàn)細(xì)節(jié)。

3.2 重定義類字節(jié)碼的實(shí)現(xiàn)細(xì)節(jié)

上文中我們提到,libinstrument動(dòng)態(tài)鏈接庫(kù)中,JPLISAgent不僅實(shí)現(xiàn)了Agent入口代碼執(zhí)行的路由,而且還是Java代碼與JVMTI之間的一道橋梁。我們?cè)贘ava代碼中調(diào)用Java Instrumentation API的redefineClasses,其實(shí)會(huì)調(diào)用libinstrument中的相關(guān)代碼,我們來(lái)分析一下這條路徑。

這是InstrumentationImpl中的redefineClasses實(shí)現(xiàn),該方法的具體實(shí)現(xiàn)依賴一個(gè)Native方法redefineClasses(),我們可以在libinstrument中找到這個(gè)Native方法的實(shí)現(xiàn):

redefineClasses這個(gè)函數(shù)的實(shí)現(xiàn)比較復(fù)雜,代碼很長(zhǎng)。下面是一段關(guān)鍵的代碼片段:

可以看到,其實(shí)是調(diào)用了JVMTI的RetransformClasses函數(shù)來(lái)完成類的重定義細(xì)節(jié)。

重定義類的請(qǐng)求會(huì)被JVM包裝成一個(gè)VM_RedefineClasses類型的VM_Operation,VM_Operation是JVM內(nèi)部的一些操作的基類,包括GC操作等。VM_Operation由VMThread來(lái)執(zhí)行,新的VM_Operation操作會(huì)被添加到VMThread的運(yùn)行隊(duì)列中去,VMThread會(huì)不斷從隊(duì)列里面拉取VM_Operation并調(diào)用其doit等函數(shù)執(zhí)行具體的操作。VM_RedefineClasses函數(shù)的流程較為復(fù)雜,下面是VM_RedefineClasses的大致流程:

加載新的字節(jié)碼,合并常量池,并且對(duì)新的字節(jié)碼進(jìn)行校驗(yàn)工作

清除方法上的斷點(diǎn)

JIT逆優(yōu)化

進(jìn)行字節(jié)碼替換工作,需要進(jìn)行更新類itable/vtable等操作

進(jìn)行類重定義通知

VM_RedefineClasses實(shí)現(xiàn)比較復(fù)雜的,詳細(xì)實(shí)現(xiàn)可以參考 RedefineClasses的實(shí)現(xiàn)。

4. Java-debug-tool設(shè)計(jì)與實(shí)現(xiàn)

Java-debug-tool是一個(gè)使用Java Instrument API來(lái)實(shí)現(xiàn)的動(dòng)態(tài)調(diào)試工具,它通過(guò)在目標(biāo)JVM上啟動(dòng)一個(gè)TcpServer來(lái)和調(diào)試客戶端通信。調(diào)試客戶端通過(guò)命令行來(lái)發(fā)送調(diào)試命令給TcpServer,TcpServer中有專門用來(lái)處理命令的handler,handler處理完命令之后會(huì)將結(jié)果發(fā)送回客戶端,客戶端通過(guò)處理將調(diào)試結(jié)果展示出來(lái)。下面將詳細(xì)介紹Java-debug-tool的整體設(shè)計(jì)和實(shí)現(xiàn)。

4.1 Java-debug-tool整體架構(gòu)

Java-debug-tool包括一個(gè)Java Agent和一個(gè)用于處理調(diào)試命令的核心API,核心API通過(guò)一個(gè)自定義的類加載器加載進(jìn)來(lái),以保證目標(biāo)JVM的類不會(huì)被污染。整體上Java-debug-tool的設(shè)計(jì)是一個(gè)Client-Server的架構(gòu),命令客戶端需要完整的完成一個(gè)命令之后才能繼續(xù)執(zhí)行下一個(gè)調(diào)試命令。Java-debug-tool支持多人同時(shí)進(jìn)行調(diào)試,下面是整體架構(gòu)圖:

圖4-1-1

下面對(duì)每一層做簡(jiǎn)單介紹:

交互層:負(fù)責(zé)將程序員的輸入轉(zhuǎn)換成調(diào)試交互協(xié)議,并且將調(diào)試信息呈現(xiàn)出來(lái)。

連接管理層:負(fù)責(zé)管理客戶端連接,從連接中讀調(diào)試協(xié)議數(shù)據(jù)并解碼,對(duì)調(diào)試結(jié)果編碼并將其寫到連接中去;同時(shí)將那些超時(shí)未活動(dòng)的連接關(guān)閉。

業(yè)務(wù)邏輯層:實(shí)現(xiàn)調(diào)試命令處理,包括命令分發(fā)、數(shù)據(jù)收集、數(shù)據(jù)處理等過(guò)程。

基礎(chǔ)實(shí)現(xiàn)層:Java-debug-tool實(shí)現(xiàn)的底層依賴,通過(guò)Java Instrumentation提供的API進(jìn)行類查找、類重定義等能力,Java Instrumentation底層依賴JVMTI來(lái)完成具體的功能。

在Agent被掛載到目標(biāo)JVM上之后,Java-debug-tool會(huì)安排一個(gè)Spy在目標(biāo)JVM內(nèi)活動(dòng),這個(gè)Spy負(fù)責(zé)將目標(biāo)JVM內(nèi)部的相關(guān)調(diào)試數(shù)據(jù)轉(zhuǎn)移到命令處理模塊,命令處理模塊會(huì)處理這些數(shù)據(jù),然后給客戶端返回調(diào)試結(jié)果。命令處理模塊會(huì)增強(qiáng)目標(biāo)類的字節(jié)碼來(lái)達(dá)到數(shù)據(jù)獲取的目的,多個(gè)客戶端可以共享一份增強(qiáng)過(guò)的字節(jié)碼,無(wú)需重復(fù)增強(qiáng)。下面從Java-debug-tool的字節(jié)碼增強(qiáng)方案、命令設(shè)計(jì)與實(shí)現(xiàn)等角度詳細(xì)說(shuō)明。

4.2 Java-debug-tool的字節(jié)碼增強(qiáng)方案

Java-debug-tool使用字節(jié)碼增強(qiáng)來(lái)獲取到方法運(yùn)行時(shí)的信息,比如方法入?yún)ⅰ⒊鰠⒌龋梢栽诓煌淖止?jié)碼位置進(jìn)行增強(qiáng),這種行為可以稱為“插樁”,每個(gè)“樁”用于獲取數(shù)據(jù)并將他轉(zhuǎn)儲(chǔ)出去。Java-debug-tool具備強(qiáng)大的插樁能力,不同的樁負(fù)責(zé)獲取不同類別的數(shù)據(jù),下面是Java-debug-tool目前所支持的“樁”:

方法進(jìn)入點(diǎn):用于獲取方法入?yún)⑿畔ⅰ?/p>

Fields獲取點(diǎn)1:在方法執(zhí)行前獲取到對(duì)象的字段信息。

變量存儲(chǔ)點(diǎn):獲取局部變量信息。

Fields獲取點(diǎn)2:在方法退出前獲取到對(duì)象的字段信息。

方法退出點(diǎn):用于獲取方法返回值。

拋出異常點(diǎn):用于獲取方法拋出的異常信息。

通過(guò)上面這些代碼樁,Java-debug-tool可以收集到豐富的方法執(zhí)行信息,經(jīng)過(guò)處理可以返回更加可視化的調(diào)試結(jié)果。

4.2.1 字節(jié)碼增強(qiáng)

Java-debug-tool在實(shí)現(xiàn)上使用了ASM工具來(lái)進(jìn)行字節(jié)碼增強(qiáng),并且每個(gè)插樁點(diǎn)都可以進(jìn)行配置,如果不想要什么信息,則沒(méi)必要進(jìn)行對(duì)應(yīng)的插樁操作。這種可配置的設(shè)計(jì)是非常有必要的,因?yàn)橛袝r(shí)候我們僅僅是想要知道方法的入?yún)⒑统鰠ⅲ獼ava-debug-tool卻給我們返回了所有的調(diào)試信息,這樣我們就得在眾多的輸出中找到我們所關(guān)注的內(nèi)容。如果可以進(jìn)行配置,則除了入?yún)Ⅻc(diǎn)和出參點(diǎn)外其他的樁都不插,那么就可以快速看到我們想要的調(diào)試數(shù)據(jù),這種設(shè)計(jì)的本質(zhì)是為了讓調(diào)試者更加專注。下面是Java-debug-tool的字節(jié)碼增強(qiáng)工作方式:

圖4-2-1

如圖4-2-1所示,當(dāng)調(diào)試者發(fā)出調(diào)試命令之后,Java-debug-tool會(huì)識(shí)別命令并判斷是否需要進(jìn)行字節(jié)碼增強(qiáng),如果命令需要增強(qiáng)字節(jié)碼,則判斷當(dāng)前類+當(dāng)前方法是否已經(jīng)被增強(qiáng)過(guò)。上文已經(jīng)提到,字節(jié)碼替換是有一定損耗的,這種具有損耗的操作發(fā)生的次數(shù)越少越好,所以字節(jié)碼替換操作會(huì)被記錄起來(lái),后續(xù)命令直接使用即可,不需要重復(fù)進(jìn)行字節(jié)碼增強(qiáng),字節(jié)碼增強(qiáng)還涉及多個(gè)調(diào)試客戶端的協(xié)同工作問(wèn)題,當(dāng)一個(gè)客戶端增強(qiáng)了一個(gè)類的字節(jié)碼之后,這個(gè)客戶端就鎖定了該字節(jié)碼,其他客戶端變成只讀,無(wú)法對(duì)該類進(jìn)行字節(jié)碼增強(qiáng),只有當(dāng)持有鎖的客戶端主動(dòng)釋放鎖或者斷開(kāi)連接之后,其他客戶端才能繼續(xù)增強(qiáng)該類的字節(jié)碼。

字節(jié)碼增強(qiáng)模塊收到字節(jié)碼增強(qiáng)請(qǐng)求之后,會(huì)判斷每個(gè)增強(qiáng)點(diǎn)是否需要插樁,這個(gè)判斷的根據(jù)就是上文提到的插樁配置,之后字節(jié)碼增強(qiáng)模塊會(huì)生成新的字節(jié)碼,Java-debug-tool將執(zhí)行字節(jié)碼替換操作,之后就可以進(jìn)行調(diào)試數(shù)據(jù)收集了。

經(jīng)過(guò)字節(jié)碼增強(qiáng)之后,原來(lái)的方法中會(huì)插入收集運(yùn)行時(shí)數(shù)據(jù)的代碼,這些代碼在方法被調(diào)用的時(shí)候執(zhí)行,獲取到諸如方法入?yún)ⅰ⒕植孔兞康刃畔ⅲ@些信息將傳遞給數(shù)據(jù)收集裝置進(jìn)行處理。數(shù)據(jù)收集的工作通過(guò)Advice完成,每個(gè)客戶端同一時(shí)間只能注冊(cè)一個(gè)Advice到Java-debug-tool調(diào)試模塊上,多個(gè)客戶端可以同時(shí)注冊(cè)自己的Advice到調(diào)試模塊上。Advice負(fù)責(zé)收集數(shù)據(jù)并進(jìn)行判斷,如果當(dāng)前數(shù)據(jù)符合調(diào)試命令的要求,Java-debug-tool就會(huì)卸載這個(gè)Advice,Advice的數(shù)據(jù)就會(huì)被轉(zhuǎn)移到Java-debug-tool的命令結(jié)果處理模塊進(jìn)行處理,并將結(jié)果發(fā)送到客戶端。

4.2.2 Advice的工作方式

Advice是調(diào)試數(shù)據(jù)收集器,不同的調(diào)試策略會(huì)對(duì)應(yīng)不同的Advice。Advice是工作在目標(biāo)JVM的線程內(nèi)部的,它需要輕量級(jí)和高效,意味著Advice不能做太過(guò)于復(fù)雜的事情,它的核心接口“match”用來(lái)判斷本次收集到的調(diào)試數(shù)據(jù)是否滿足調(diào)試需求。如果滿足,那么Java-debug-tool就會(huì)將其卸載,否則會(huì)繼續(xù)讓他收集調(diào)試數(shù)據(jù),這種“加載Advice” -》 “卸載Advice”的工作模式具備很好的靈活性。

關(guān)于Advice,需要說(shuō)明的另外一點(diǎn)就是線程安全,因?yàn)樗虞d之后會(huì)運(yùn)行在目標(biāo)JVM的線程中,目標(biāo)JVM的方法極有可能是多線程訪問(wèn)的,這也就是說(shuō),Advice需要有能力處理多個(gè)線程同時(shí)訪問(wèn)方法的能力,如果Advice處理不當(dāng),則可能會(huì)收集到雜亂無(wú)章的調(diào)試數(shù)據(jù)。下面的圖片展示了Advice和Java-debug-tool調(diào)試分析模塊、目標(biāo)方法執(zhí)行以及調(diào)試客戶端等模塊的關(guān)系。

圖4-2-2

Advice的首次掛載由Java-debug-tool的命令處理器完成,當(dāng)一次調(diào)試數(shù)據(jù)收集完成之后,調(diào)試數(shù)據(jù)處理模塊會(huì)自動(dòng)卸載Advice,然后進(jìn)行判斷,如果調(diào)試數(shù)據(jù)符合Advice的策略,則直接將數(shù)據(jù)交由數(shù)據(jù)處理模塊進(jìn)行處理,否則會(huì)清空調(diào)試數(shù)據(jù),并再次將Advice掛載到目標(biāo)方法上去,等待下一次調(diào)試數(shù)據(jù)。非首次掛載由調(diào)試數(shù)據(jù)處理模塊進(jìn)行,它借助Advice按需取數(shù)據(jù),如果不符合需求,則繼續(xù)掛載Advice來(lái)獲取數(shù)據(jù),否則對(duì)調(diào)試數(shù)據(jù)進(jìn)行處理并返回給客戶端。

4.3 Java-debug-tool的命令設(shè)計(jì)與實(shí)現(xiàn)

4.3.1 命令執(zhí)行

上文已經(jīng)完整的描述了Java-debug-tool的設(shè)計(jì)以及核心技術(shù)方案,本小節(jié)將詳細(xì)介紹Java-debug-tool的命令設(shè)計(jì)與實(shí)現(xiàn)。首先需要將一個(gè)調(diào)試命令的執(zhí)行流程描述清楚,下面是一張用來(lái)表示命令請(qǐng)求處理流程的圖片:

圖4-3-1

圖4-3-1簡(jiǎn)單的描述了Java-debug-tool的命令處理方式,客戶端連接到服務(wù)端之后,會(huì)進(jìn)行一些協(xié)議解析、協(xié)議認(rèn)證、協(xié)議填充等工作,之后將進(jìn)行命令分發(fā)。服務(wù)端如果發(fā)現(xiàn)客戶端的命令不合法,則會(huì)立即返回錯(cuò)誤信息,否則再進(jìn)行命令處理。命令處理屬于典型的三段式處理,前置命令處理、命令處理以及后置命令處理,同時(shí)會(huì)對(duì)命令處理過(guò)程中的異常信息進(jìn)行捕獲處理,三段式處理的好處是命令處理被拆成了多個(gè)階段,多個(gè)階段負(fù)責(zé)不同的職責(zé)。前置命令處理用來(lái)做一些命令權(quán)限控制的工作,并填充一些類似命令處理開(kāi)始時(shí)間戳等信息,命令處理就是通過(guò)字節(jié)碼增強(qiáng),掛載Advice進(jìn)行數(shù)據(jù)收集,再經(jīng)過(guò)數(shù)據(jù)處理來(lái)產(chǎn)生命令結(jié)果的過(guò)程,后置處理則用來(lái)處理一些連接關(guān)閉、字節(jié)碼解鎖等事項(xiàng)。

Java-debug-tool允許客戶端設(shè)置一個(gè)命令執(zhí)行超時(shí)時(shí)間,超過(guò)這個(gè)時(shí)間則認(rèn)為命令沒(méi)有結(jié)果,如果客戶端沒(méi)有設(shè)置自己的超時(shí)時(shí)間,就使用默認(rèn)的超時(shí)時(shí)間進(jìn)行超時(shí)控制。Java-debug-tool通過(guò)設(shè)計(jì)了兩階段的超時(shí)檢測(cè)機(jī)制來(lái)實(shí)現(xiàn)命令執(zhí)行超時(shí)功能:首先,第一階段超時(shí)觸發(fā),則Java-debug-tool會(huì)友好的警告命令處理模塊處理時(shí)間已經(jīng)超時(shí),需要立即停止命令執(zhí)行,這允許命令自己做一些現(xiàn)場(chǎng)清理工作,當(dāng)然需要命令執(zhí)行線程自己感知到這種超時(shí)警告;當(dāng)?shù)诙A段超時(shí)觸發(fā),則Java-debug-tool認(rèn)為命令必須結(jié)束執(zhí)行,會(huì)強(qiáng)行打斷命令執(zhí)行線程。超時(shí)機(jī)制的目的是為了不讓命令執(zhí)行太長(zhǎng)時(shí)間,命令如果長(zhǎng)時(shí)間沒(méi)有收集到調(diào)試數(shù)據(jù),則應(yīng)該停止執(zhí)行,并思考是否調(diào)試了一個(gè)錯(cuò)誤的方法。當(dāng)然,超時(shí)機(jī)制還可以定期清理那些因?yàn)槲粗驍嚅_(kāi)連接的客戶端持有的調(diào)試資源,比如字節(jié)碼鎖。

4.3.4 獲取方法執(zhí)行視圖

Java-debug-tool通過(guò)下面的信息來(lái)向調(diào)試者呈現(xiàn)出一次方法執(zhí)行的視圖:

正在調(diào)試的方法信息。

方法調(diào)用堆棧。

調(diào)試耗時(shí),包括對(duì)目標(biāo)JVM造成的STW時(shí)間。

方法入?yún)ⅲㄈ雲(yún)⒌念愋图皡?shù)值。

方法的執(zhí)行路徑。

代碼執(zhí)行耗時(shí)。

局部變量信息。

方法返回結(jié)果。

方法拋出的異常。

對(duì)象字段值快照。

圖4-3-2展示了Java-debug-tool獲取到正在運(yùn)行的方法的執(zhí)行視圖的信息。

圖4-3-2

4.4 Java-debug-tool與同類產(chǎn)品對(duì)比分析

Java-debug-tool的同類產(chǎn)品主要是greys,其他類似的工具大部分都是基于greys進(jìn)行的二次開(kāi)發(fā),所以直接選擇greys來(lái)和Java-debug-tool進(jìn)行對(duì)比。

5. 總結(jié)

本文詳細(xì)剖析了Java動(dòng)態(tài)調(diào)試關(guān)鍵技術(shù)的實(shí)現(xiàn)細(xì)節(jié),并介紹了我們基于Java動(dòng)態(tài)調(diào)試技術(shù)結(jié)合實(shí)際故障排查場(chǎng)景進(jìn)行的一點(diǎn)探索實(shí)踐;動(dòng)態(tài)調(diào)試技術(shù)為研發(fā)人員進(jìn)行線上問(wèn)題排查提供了一種新的思路,我們基于動(dòng)態(tài)調(diào)試技術(shù)解決了傳統(tǒng)斷點(diǎn)調(diào)試存在的問(wèn)題,使得可以將斷點(diǎn)調(diào)試這種技術(shù)應(yīng)用在線上,以線下調(diào)試的思維來(lái)進(jìn)行線上調(diào)試,提高問(wèn)題排查效率。
責(zé)任編輯人:CC

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2966

    瀏覽量

    104702
  • 動(dòng)態(tài)調(diào)試

    關(guān)注

    0

    文章

    2

    瀏覽量

    5751
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Java 23功能介紹

    功能。 跟上 Java 新版本的發(fā)布節(jié)奏可能很難,這意味著要解決連串的問(wèn)題——更改是什么、為什么要更改以及如何使用全新和更新的功能。 在這篇博文中,我將介紹 Java 23 的
    的頭像 發(fā)表于 12-04 10:02 ?194次閱讀
    <b class='flag-5'>Java</b> 23功能介紹

    對(duì)比Python與Java編程語(yǔ)言

    Python與Java都是目前非常流行的編程語(yǔ)言,它們各有其獨(dú)特的優(yōu)勢(shì)和適用場(chǎng)景。以下是對(duì)這兩種編程語(yǔ)言的對(duì)比: 、語(yǔ)法和易用性 Python 語(yǔ)法簡(jiǎn)潔,代碼更易讀,非常適合初學(xué)者。 動(dòng)態(tài)類型系統(tǒng)
    的頭像 發(fā)表于 11-15 09:31 ?289次閱讀

    詳解RAID技術(shù)

    RAID(Redundant Array of Independent Disks)即獨(dú)立磁盤冗余陣列,RAID技術(shù)將多個(gè)單獨(dú)的物理硬盤以不同的方式組合成個(gè)邏輯硬盤,從而提高硬盤的讀寫性能和數(shù)據(jù)安全性。
    的頭像 發(fā)表于 11-06 18:06 ?356次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>詳解</b>RAID<b class='flag-5'>技術(shù)</b>

    智慧公交是什么?帶你詳解智慧公交的解決方案!

    智慧公交是什么?帶你詳解智慧公交的解決方案!
    的頭像 發(fā)表于 11-05 12:26 ?271次閱讀
    智慧公交是什么?<b class='flag-5'>一</b><b class='flag-5'>文</b>帶你<b class='flag-5'>詳解</b>智慧公交的解決方案!

    CPK為什么要大于1.33?詳解CPK計(jì)算

    原文標(biāo)題:CPK為什么要大于1.33?詳解CPK計(jì)算
    的頭像 發(fā)表于 11-01 11:08 ?307次閱讀

    甲骨發(fā)布Java 23

    全球領(lǐng)先的軟件開(kāi)發(fā)企業(yè)甲骨(Oracle)近日隆重宣布推出Java 23(Oracle JDK 23),這里程碑式的更新標(biāo)志著全球排名第的編程語(yǔ)言和開(kāi)發(fā)平臺(tái)再次邁出堅(jiān)實(shí)步伐。
    的頭像 發(fā)表于 09-19 16:36 ?345次閱讀

    弄懂實(shí)時(shí)動(dòng)態(tài)載波相位差分技術(shù)和偽距差分技術(shù)的區(qū)別

    在全球?qū)Ш叫l(wèi)星系統(tǒng)(GNSS)中,實(shí)時(shí)動(dòng)態(tài)載波相位差分和偽距差分是常見(jiàn)的兩種差分定位技術(shù)。這兩種技術(shù)在定位精度、可用性和適用性等方面存在著些明顯的區(qū)別。本文將介紹實(shí)時(shí)
    的頭像 發(fā)表于 09-13 11:14 ?1071次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b>弄懂實(shí)時(shí)<b class='flag-5'>動(dòng)態(tài)</b>載波相位差分<b class='flag-5'>技術(shù)</b>和偽距差分<b class='flag-5'>技術(shù)</b>的區(qū)別

    ?介紹Java開(kāi)發(fā)的開(kāi)源MES系統(tǒng)

    ?介紹Java開(kāi)發(fā)的開(kāi)源MES系統(tǒng),萬(wàn)界星空科技開(kāi)源的MES系統(tǒng)。該系統(tǒng)基于Java開(kāi)發(fā),具有廣泛的適用性和高度的可定制性,能夠滿足不同行業(yè)、不同規(guī)模企業(yè)的智能制造需求。
    的頭像 發(fā)表于 09-05 17:39 ?642次閱讀
    ?介紹<b class='flag-5'>一</b>款<b class='flag-5'>Java</b>開(kāi)發(fā)的開(kāi)源MES系統(tǒng)

    詳解動(dòng)態(tài)多點(diǎn)VPN技術(shù)

    引言 動(dòng)態(tài)多點(diǎn)VPN(Dynamic Multipoint VPN)是mGRE、NHRP(Next Hop Resolution Protocol)、IPSec結(jié)合產(chǎn)生的技術(shù),簡(jiǎn)寫為DMVPN
    發(fā)表于 07-26 06:07

    華納云:java web和java有什么區(qū)別java web和java有什么區(qū)別

    的平臺(tái),Java可以用于開(kāi)發(fā)桌面應(yīng)用程序、移動(dòng)應(yīng)用程序、企業(yè)級(jí)應(yīng)用程序等。 – Java Web是Java語(yǔ)言在Web開(kāi)發(fā)領(lǐng)域的應(yīng)用,它使用Java
    的頭像 發(fā)表于 07-16 13:35 ?785次閱讀
    華納云:<b class='flag-5'>java</b> web和<b class='flag-5'>java</b>有什么區(qū)別<b class='flag-5'>java</b> web和<b class='flag-5'>java</b>有什么區(qū)別

    甲骨發(fā)布AI編程助手,助用戶編寫Java、SQL程序

    據(jù)悉,Oracle Code Assist基于甲骨先進(jìn)的云計(jì)算平臺(tái)——Oracle Cloud Infrastructure (OCI)構(gòu)建,重點(diǎn)優(yōu)化各項(xiàng)Java、SQL程序以及OCI應(yīng)用開(kāi)發(fā)環(huán)節(jié)。
    的頭像 發(fā)表于 05-13 10:19 ?536次閱讀

    介紹款基于java的滲透測(cè)試神器-CobaltStrike

    Cobalt Strike是款基于java的滲透測(cè)試神器,常被業(yè)界人稱為CS神器。
    的頭像 發(fā)表于 01-16 09:16 ?964次閱讀
    介紹<b class='flag-5'>一</b>款基于<b class='flag-5'>java</b>的滲透測(cè)試神器-CobaltStrike

    詳解硅通孔技術(shù)(TSV)

    硅通孔技術(shù)(TSV,Through Silicon Via)是通過(guò)在芯片和芯片之間、晶圓和晶圓之間制作垂直導(dǎo)通,實(shí)現(xiàn)芯片之間互連的技術(shù),是2.5D/3D 封裝的關(guān)鍵工藝之。通過(guò)垂直互連減小互連長(zhǎng)度、信號(hào)延遲,降低電容、電感,實(shí)
    的頭像 發(fā)表于 01-09 09:44 ?1.7w次閱讀
    <b class='flag-5'>一</b><b class='flag-5'>文</b><b class='flag-5'>詳解</b>硅通孔<b class='flag-5'>技術(shù)</b>(TSV)

    詳解Java DEBUG的基本原理

    Debug 的時(shí)候,都遇到過(guò)手速太快,直接跳過(guò)了自己想調(diào)試的方法、代碼的時(shí)候吧……
    的頭像 發(fā)表于 01-05 10:10 ?1365次閱讀
    <b class='flag-5'>詳解</b><b class='flag-5'>Java</b> DEBUG的基本原理

    詳解pcb回流焊溫度選擇與調(diào)整

    詳解pcb回流焊溫度選擇與調(diào)整
    的頭像 發(fā)表于 12-29 10:20 ?1642次閱讀
    主站蜘蛛池模板: 亚洲综合香蕉在线视频| a视频在线免费观看| 巨大乳hdbbw| 999久久国产精品免费人妻| 欧美色图天堂网| 超碰97人人做人人爱少妇| 爽爽影院免费观看| 国产午夜亚洲精品不卡电影| 亚洲视频欧美在线专区| 狼好色有你好看| gogogo免费视频观看| 手机精品在线| 国内视频在线精品一区| 同房交换4p好爽| 国精产品一区一区三区M| 曰产无码久久久久久精品| 高清无码中文字幕影片| 小女生RAPPER入口| 久久精品无码一区二区日韩av | 亚洲国产精品无码中文字幕 | 亚洲欧美中文日韩v在线| 久久久97人妻无码精品蜜桃| 99久久免费精品国产| 色琪琪无码成人AV视频| 国产成人久久精品AV| 亚洲精品另类有吗中文字幕| 老湿影院色情a| 中文字幕在线视频在线看| 欧美一区二区在线观看| 国产欧美无码亚洲毛片| 在线 中文字幕| 日本欧美高清一区二区视频| 国产制服丝袜91在线| 91久久精一区二区三区大全| 色婷婷激情AV精品影院| 精品人妻伦九区久久AAA片69| 2019一級特黃色毛片免費看| 久久精品中文字幕| YELLOW视频直播在线观看高清| 亚洲AV国产福利精品在现观看| 玛雅成人网|