自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用 Arthas 排查開源 Excel 組件問題

開發(fā)
JVMTI(JVM Tool Interface):是 JVM 暴露出來的一些供用戶擴(kuò)展的接口集合,JVMTI 是基于事件驅(qū)動的,JVM 每執(zhí)行到一定的邏輯就會調(diào)用一些事件的回調(diào)接口(如果有的話),這些接口可以供開發(fā)者去擴(kuò)展自己的邏輯。

 [[408346]]

背景介紹

項目中有使用到 com.github.dreamroute excel-helper 這個工具來輔助 Excel 文件的解析,出錯時的代碼是這樣寫的:如下所示(非源代碼)

  1. try { excelDTOS = ExcelHelper.importFromFile(ExcelType.XLSX, file, ExcelDTO.class); } catch (Exception e) { log.error("ExcelHelper importFromFile exception msg {}", e.getMessage()); } 

因為打印異常信息時,使用了 e.getMessage() 方法,沒有將異常信息打印出來。而且本地復(fù)現(xiàn)也沒有復(fù)現(xiàn)出來。所以只能考慮使用 arthas 來協(xié)助排查這個問題了。

排查過程

1、線上服務(wù)器安裝 Arthas。
https://arthas.aliyun.com/doc/install-detail.html

2、使用 watch 命令監(jiān)控指定方法,打印出異常的堆棧信息,命令如下:

watch com.github.dreamroute.excel.helper.ExcelHelper importFromFile '{params,throwExp}' -e -x 3
再次調(diào)用方法,捕獲到異常棧信息如下:

已經(jīng)捕獲到異常,并打印出堆棧信息。

3、根據(jù)對應(yīng)的堆棧信息,定位到具體的代碼,如下:

代碼很簡單,從代碼中可以很清晰的看到如果沒有從 headerInfoMap 中沒有獲取到指定的 headerInfo ,就會拋這個異常。沒有找到只有兩種情況:

headerInfoMap 中保存的信息不對。

cell 中的 columnIndex 超出的正常的范圍導(dǎo)致沒有獲取到對應(yīng) HeaderInfo 。
對于第二種情況,首先去校驗了一下上傳的 Excel 文件是否有問題,本地測試了一下 Excel 文件,沒有任何問題。本地測試也是成功的,所以主觀判斷,第二種情況的可能性不大。

所以說主要檢查第一種情況是否發(fā)生,這個時候可以再去看一下該方法的第一行代碼

  1. MapheaderInfoMap = processHeaderInfo(rows,cls); 

可以看到headerInfoMap是通過processHeaderInfo中獲取的。找到processHeaderInfo 的代碼,如下所示。

  1. public static MapproceeHeaderInfo(Iteratorrows, Class cls) { if (rows.hasNext()) { Row header = rows.next(); return CacheFactory.findHeaderInfo(cls, header); } return new HashMap<>(0);}public static MapfindHeaderInfo(Class cls, Row header) { MapheaderInfo = HEADER_INFO.get(cls); if (MapUtils.isEmpty(headerInfo)) { headerInfo = ClassAssistant.getHeaderInfo(cls, header); HEADER_INFO.put(cls, headerInfo); } return headerInfo;}public static MapgetHeaderInfo(Class cls, Row header) { IteratorcellIterator = header.cellIterator(); Listfields = ClassAssistant.getAllFields(cls); MapheaderInfo = new HashMap<>(fields.size()); while (cellIterator.hasNext()) { org.apache.poi.ss.usermodel.Cell cell = cellIterator.next(); String headerName = cell.getStringCellValue(); for (Field field : fields) { Column col = field.getAnnotation(Column.class); String name = col.name(); if (Objects.equals(headerName, name)) { HeaderInfo hi = new HeaderInfo(col.cellType(), field); headerInfo.put(cell.getColumnIndex(), hi); break; } } } return headerInfo;} 

主要通過 CacheFactory 類的 findHeaderInfo 來生成,在 findHeaderInfo 方法中,通過一個被 static final 修飾的 HEADER_INFO 變量來做緩存,被調(diào)用時先去HEADER_INFO 中查,如果有則直接返回,沒有則重新創(chuàng)建(也就說明相同的 Excel 文件,僅初始化一次 HeaderInfo )。創(chuàng)建的步驟在 ClassAssistant.getHeaderInfo() 方法中。

簡單的看一下 HeaderInfo 的生成過程,根據(jù) Excel 文件的第一行中的各個 Cell 值與自定義實體類的注解比較,如果名字相同,就存為一個鍵值對( HeaderInfo 的數(shù)據(jù)結(jié)構(gòu)為 HashMap )。

4、這個時候需要再確認(rèn)一下 HEADER_INFO 中保存的 ExcelDTO.class 相關(guān)的 HeaderInfo 是怎樣的。通過 ognl 命令或者 getstatic 命令來查看。這里使用 ognl 命令。

  1. ognl '#value=new com.tom.dto.ExcelDTO(),#valueMap=@com.github.dreamroute.excel.helper.cache.CacheFactory@HEADER_INFO,#valueMap.get(#value.getClass()).entrySet().iterator.{#this.value.name}' 

結(jié)果如下:正常情況下這個 Excel 文件有 6 列信息,為什么只產(chǎn)生了 4 個鍵值對呢?如果 HEADER_INFO 中保存了錯的,從上面的邏輯來看,后面上傳的正確的 Excel 文件在解析時都會拋錯。

5、詢問了當(dāng)時發(fā)現(xiàn)這個問題的同事,得知他第一次上傳的 Excel 文件是有問題的,后面想改正,再上傳時便出現(xiàn)了問題。到這里問題也算是找到了。

Arthas 原理探究

有了實際的使用之后,不免會想到,Arthas 是如何做到在程序運行時,動態(tài)監(jiān)測我們的代碼的呢?帶著這樣的問題,我們一起來看下 Java Agent 技術(shù)實現(xiàn)原理。

Java Agent 技術(shù)

Agent 是一個運行在目標(biāo) JVM 的特定程序,它的職責(zé)是負(fù)責(zé)從目標(biāo) JVM 中獲取數(shù)據(jù),然后將數(shù)據(jù)傳遞給外部進(jìn)程。加載 Agent 的時機可以是目標(biāo) JVM 啟動之時,也可以是在目標(biāo) JVM 運行時進(jìn)行加載,而在目標(biāo) JVM 運行時進(jìn)行 Agent 加載具備動態(tài)性。

基礎(chǔ)概念

JVMTI(JVM Tool Interface):是 JVM 暴露出來的一些供用戶擴(kuò)展的接口集合,JVMTI 是基于事件驅(qū)動的,JVM 每執(zhí)行到一定的邏輯就會調(diào)用一些事件的回調(diào)接口(如果有的話),這些接口可以供開發(fā)者去擴(kuò)展自己的邏輯。
JVMTIAgent(JVM Tool Interface):是一個動態(tài)庫,利用 JVMTI 暴露出來的一些接口幫助我們在程序啟動時或程序運行時 JVM Attach 機制,將 Agent 加載到目標(biāo) JVM 中。
JPLISAgent(Java Programming Language Instrumentation Services Agent):它的作用是初始化所有通過 Java Instrumentation API 編寫的 Agent,并且也承擔(dān)著通過 JVMTI 實現(xiàn) Java Instrumentation 中暴露 API 的責(zé)任。
VirtualMachine :提供了Attach 動作和 Detach 動作,允許我們通過 attach 方法,遠(yuǎn)程連接到 JVM 上,然后通過 loadAgent 方法向 JVM 注冊一個代理程序 agent ,在該 agent 的代理程序中會得到一個 Instrumentation 實例,該實例可以在 class 加載前改變 class 的字節(jié)碼,也可以在 class 加載后重新加載。
Instrumentation:可以在 class 加載前改變 class 的字節(jié)碼(premain),也可以在 class 加載后重新加載(agentmain)。

執(zhí)行過程

動手寫一個 Demo

通過 javassist,在運行時更改指定方法的代碼,在方法之前后添加自定義邏輯。

1、定義 Agent 類。當(dāng)前 Java 提供了兩種方式可以將代碼代碼注入到 JVM 中,這里我們的 Demo 選擇使用 agentmain 方法來實現(xiàn)。

premain:在啟動時通過 javaagent 命令,將代理注入到指定的 JVM 中。
agentmain:運行時通過 attach 工具激活指定代理。

  1. /** * AgentMain * * @author tomxin */public class AgentMain { public static void agentmain(String agentArgs, Instrumentation instrumentation) throws UnmodifiableClassException, ClassNotFoundException { instrumentation.addTransformer(new InterceptorTransformer(agentArgs), true); Class clazz = Class.forName(agentArgs.split(",")[1]); instrumentation.retransformClasses(clazz); }}/** * InterceptorTransformer * * @author tomxin */public class InterceptorTransformer implements ClassFileTransformer { private String agentArgs; public InterceptorTransformer(String agentArgs) { this.agentArgs = agentArgs; } @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { //javassist的包名是用點分割的,需要轉(zhuǎn)換下 if (className != null && className.indexOf("/") != -1) { className = className.replaceAll("/", "."); } try { //通過包名獲取類文件 CtClass cc = ClassPool.getDefault().get(className); //獲得指定方法名的方法 CtMethod m = cc.getDeclaredMethod(agentArgs.split(",")[2]); //在方法執(zhí)行前插入代碼 m.insertBefore("{ System.out.println(\"=========開始執(zhí)行=========\"); }"); m.insertAfter("{ System.out.println(\"=========結(jié)束執(zhí)行=========\"); }"); return cc.toBytecode(); } catch (Exception e) { } return null; }} 

2、使用 Maven 配置 MANIFEST.MF 文件,該文件能夠指定 Jar 包的 main 方法。

  1. <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.1</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> </manifest> <manifestEntries> <Agent-Class>com.tom.mdc.AgentMain</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build> 

3、定義 Attach 方法,通過 VirtualMachine.attach(#{pid}) 來指定要代理的類。

  1. import com.sun.tools.attach.VirtualMachine;import java.io.IOException;/** * AttachMain * * @author tomxin */public class AttachMain { public static void main(String[] args) { VirtualMachine virtualMachine = nulltry { virtualMachine = VirtualMachine.attach(args[0]); // 將打包好的Jar包,添加到指定的JVM進(jìn)程中。 virtualMachine.loadAgent("target/agent-demo-1.0-SNAPSHOT.jar",String.join(",", args)); } catch (Exception e) { if (virtualMachine != null) { try { virtualMachine.detach(); } catch (IOException ex) { ex.printStackTrace(); } } } }} 

4、定義測試的方法

  1. package com.tom.mdc;import java.lang.management.ManagementFactory;import java.util.Random;import java.util.concurrent.TimeUnit;/** * PrintParamTarget * * @author toxmxin */public class PrintParamTarget { public static void main(String[] args) { // 打印當(dāng)前進(jìn)程ID System.out.println(ManagementFactory.getRuntimeMXBean().getName()); Random random = new Random(); while (true) { int sleepTime = 5 + random.nextInt(5); running(sleepTime); } } private static void running(int sleepTime) { try { TimeUnit.SECONDS.sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("running sleep time " + sleepTime); }} 

 

責(zé)任編輯:梁菲 來源: 阿里云云棲號
相關(guān)推薦

2024-04-30 08:09:10

PulsarArthas消息隊列

2023-03-03 09:33:45

ArthasJava診斷工具

2021-06-01 09:29:43

ArthasJVM內(nèi)存

2021-04-19 17:25:08

Kubernetes組件網(wǎng)絡(luò)

2020-10-28 15:07:01

Arthas

2022-05-08 09:11:44

WiFi樹莓派GO

2024-09-10 09:31:07

開源項目Arthas

2010-08-04 14:28:01

Flex組件

2009-08-21 09:14:47

C# Excel CO

2024-08-14 14:20:00

2020-12-07 11:12:16

MySOLBinlogOtter

2022-01-26 19:42:05

MySQL亂碼排查

2021-11-14 05:00:56

排查Sdk方式

2021-06-01 07:55:42

DockerEOFk8s

2021-12-01 15:03:56

Java開發(fā)代碼

2024-11-22 09:40:18

Visual內(nèi)存泄漏內(nèi)存

2025-03-18 08:10:00

iodump開源I/O

2020-04-23 10:07:45

工具IDEA阿里巴巴

2024-11-21 09:30:38

內(nèi)存泄漏CPU

2024-12-02 09:10:15

Redis性能優(yōu)化
點贊
收藏

51CTO技術(shù)棧公眾號