Arthas之類操作
1. classLoader
查詢當前JVM中存在的classloader
classloader
name numberOfInstances loadedCountTotal
BootstrapClassLoader 1 2340
com.taobao.arthas.agent.ArthasClassloader 1 1345
sun.misc.Launcher$AppClassLoader 1 145
sun.misc.Launcher$ExtClassLoader 1 52
sun.reflect.DelegatingClassLoader 17 17
com.system.framework.CoutomerClassLoader 2 2
// author:herbert qq:464884492 date:20220330
查詢當前JVM中存在的classloader,注意我們自定義實現的 com.system.framework.CoutomerClassLoader
classloader -l
name loadedCount hash parent
BootstrapClassLoader 2340 null null
com.system.framework.CoutomerClassLoader@6a6824be 1 6a6824be sun.misc.Launcher$AppClassLoader@7cd84586
com.system.framework.CoutomerClassLoader@4aa8f0b4 1 4aa8f0b4 sun.misc.Launcher$AppClassLoader@7cd84586
com.taobao.arthas.agent.ArthasClassloader@5cd3ae63 1345 5cd3ae63 sun.misc.Launcher$ExtClassLoader@7e6cbb7a
sun.misc.Launcher$AppClassLoader@7cd84586 145 7cd84586 sun.misc.Launcher$ExtClassLoader@7e6cbb7a
sun.misc.Launcher$ExtClassLoader@7e6cbb7a 52 7e6cbb7a null
// author:herbert qq:464884492 date:20220330
查詢當前JVM中classloder之間繼承關係,注意我們自定義實現的 com.system.framework.CoutomerClassLoader
classloader -t
+-BootstrapClassLoader
+-sun.misc.Launcher$ExtClassLoader@7e6cbb7a
+-com.taobao.arthas.agent.ArthasClassloader@5cd3ae63
+-sun.misc.Launcher$AppClassLoader@7cd84586
+-com.system.framework.CoutomerClassLoader@6a6824be
+-com.system.framework.CoutomerClassLoader@4aa8f0b4
查詢指定的classLoader加載了哪些資源
classloader -c 7e6cbb7a
classloader --classLoaderClass sun.misc.Launcher$ExtClassLoade
file:/C:/Program%20Files/Java/jre1.8.0_221/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/Java/jre1.8.0_221/lib/ext/cldrdata.jar
...
查詢具體資源路徑
classloader -c 7cd84586 -r com/system/framework/FrameworkApplicationTests.class
file:/D:/projects/javaprj/framework/target/test-classes/com/system/framework/FrameworkApplicationTests.class
查詢指定classloader已經加載的類
classloader -c 4aa8f0b4 -a
hash:1252585652, com.system.framework.CoutomerClassLoader@4aa8f0b4
com.system.framework.EncryptClass
2. Class從哪裏加載
通過命令sc查找,重點關注 code-source class-loade classLoaderHash
查找當前環境中加載類中包含EncryptClass的類,code-source爲空,表示該class是運行時從代碼加載,沒有具體路徑
sc -d *EncryptClass*
class-info com.system.framework.EncryptClass
code-source
name com.system.framework.EncryptClass
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name EncryptClass
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-com.system.framework.CoutomerClassLoader@6a6824be
+-sun.misc.Launcher$AppClassLoader@7cd84586
+-sun.misc.Launcher$ExtClassLoader@7e6cbb7a
classLoaderHash 6a6824be
// author:herbert qq:464884492 date:20220330
code-source 顯示具體class加載來源,如果是從jar包中加載會顯示具體jar路徑
sc -d *CoutomerClassLoader*
class-info com.system.framework.CoutomerClassLoader
code-source /D:/projects/javaprj/framework/target/test-classes/
name com.system.framework.CoutomerClassLoader
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name CoutomerClassLoader
modifier public
annotation
interfaces
super-class +-java.lang.ClassLoader
+-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@7cd84586
+-sun.misc.Launcher$ExtClassLoader@7e6cbb7a
classLoaderHash 7cd84586
// author:herbert qq:464884492 date:20220330
3. 同名不同版本jar生效版本
根據jar存放位置,推算出對應的classloader,比如查找當前環境加載哪個版本的log4j
classloader -classLoaderClass sun.misc.Launcher$AppClassLoader | grep log4j
file:/D:/maven/org/springframework/boot/spring-boot-starter-log4j2/2.0.5.RELEASE/spring-boot-starter-log4j2-2.0.5.RELEASE.jar
file:/D:/maven/org/apache/logging/log4j/log4j-slf4j-impl/2.10.0/log4j-slf4j-impl-2.10.0.jar
file:/D:/maven/org/apache/logging/log4j/log4j-jul/2.10.0/log4j-jul-2.10.0.jar
file:/D:/maven/org/apache/logging/log4j/log4j-api/2.16.0/log4j-api-2.16.0.jar
file:/D:/maven/org/apache/logging/log4j/log4j-core/2.16.0/log4j-core-2.16.0.jar
從上可知道,當前環境使用的是log4j-api-2.10.0.jar
根據jar中包含的類名,使用sc命令查找
sc -d *LogManager
class-info org.apache.logging.log4j.LogManager
code-source /D:/maven/org/apache/logging/log4j/log4j-api/2.16.0/log4j-api-2.16.0.jar
name org.apache.logging.log4j.LogManager
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name LogManager
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@7cd84586
+-sun.misc.Launcher$ExtClassLoader@7e6cbb7a
classLoaderHash 7cd84586
// author:herbert qq:464884492 date:20220330
從 code-source 中可以知道當前加載的文件具體路徑
針對日誌框架可以通過logger命名查看codeSource來了解jar來源
logger
name root
class org.apache.logging.log4j.core.config.LoggerConfig
classLoader sun.misc.Launcher$AppClassLoader@7cd84586
classLoaderHash 7cd84586
level INFO
config XmlConfiguration[location=D:\projects\javaprj\framework\target\classes\log4j2.xml]
additivity true
codeSource file:/D:/maven/org/apache/logging/log4j/log4j-core/2.16.0/log4j-core-2.16.0.jar
appenders name Console
class org.apache.logging.log4j.core.appender.ConsoleAppender
classLoader sun.misc.Launcher$AppClassLoader@7cd84586
classLoaderHash 7cd84586
target SYSTEM_OUT
// author:herbert qq:464884492 date:20220330
4. 反編譯得到源碼
從上邊classloader列表可以知道,當前JVM中實例化了兩個自定義的類加載器CoutomerClassLoader。他們分別加載類 com.system.framework.EncryptClass。當前這個class已經加密,在我們源代碼環境是沒有這個類源文件。我們自定義加載器,加載代碼如下
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
byte[] classByte = Base64Utils.decodeFromString(ArtahsDemoClassLoader.encrypt);
if ("com.system.framework.EncryptClass".equals(name)) {
return defineClass(name, classByte, 0, classByte.length);
}
return super.loadClass(name);
}
控制檯顯示源代碼
jad -c 3e2e18f2 --source-only com.system.framework.EncryptClass --lineNumber false
控制檯不顯示,直接輸出文件
jad -c 3e2e18f2 --source-only com.system.framework.EncryptClass --lineNumber false > D:\\EncryptClass.java
得到反編譯代碼如下
// author:herbert qq:464884492 date:20220330
/*
* Decompiled with CFR.
*/
package com.system.framework;
public class EncryptClass {
String note;
public EncryptClass() {
}
public EncryptClass(String note) {
this.note = note;
}
public void print() {
System.out.println("源文件初始輸出==>" + this.note);
}
public void setNote(String note) {
this.note = note;
}
public String getNote() {
return this.note;
}
}
從代碼可知,當前控制檯會打印對應的信息爲源文件初始輸出==>note
。主程序代碼每5秒輸出一次,輸出信息如下
...
======第190次輸出======
源文件初始輸出==>testRefect--1
源文件初始輸出==>testRefect--2
======第191次輸出======
源文件初始輸出==>testRefect--1
源文件初始輸出==>testRefect--2
...
5. 內存編譯得到字節碼
把反編譯文件中的 print方法做如下改動
public void print() {
System.out.println("反編譯後重新加載==>" + this.note);
}
執行內存編譯編譯命令,這裏需要注意下,當前運行環境一定是jdk環境而不是jre環境
mc -c 3e2e18f2 -d D:\\ D:\\EncryptClass.java
Memory compiler output:
D:\com\system\framework\EncryptClass.class
Affect(row-cnt:1) cost in 738 ms.
出現如上信息,表示編譯成功
6. 加載改動後的Class到JVM
得到我們修改後的class,需要重新裝載到JVM以替換之前的class字節碼
retransform -c 3e2e18f2 D:\\com\\system\\framework\\EncryptClass.class
成功以後,回到控制檯。可以看到輸出信息已經改變
======第73次輸出======
源文件初始輸出==>testRefect--1
源文件初始輸出==>testRefect--2
======第74次輸出======
反編譯後重新加載==>testRefect--1
反編譯後重新加載==>testRefect--2
從結果來看,最初猜測,指定classloder,只會影響當前classloader加載的類。可是另一個classloader加載類輸出也改變了。說明同一個類的字節碼在jvm一定只存在一份
查看已經重新加載過的類
retransform -l
Id ClassName TransformCount LoaderHash LoaderClassName
1 com.system.framework.EncryptClass 1 3e2e18f2 null
還原重新加載前的字節信息,一定依次執行如下兩條命令。classPattern 支持通配符
retransform --deleteAll
retransform --classPattern com.system.framework.EncryptClass
======第260次輸出======
反編譯後重新加載==>testRefect--1
反編譯後重新加載==>testRefect--2
======第261次輸出======
源文件初始輸出==>testRefect--1
源文件初始輸出==>testRefect--2
7. 總結
歡迎感興趣的朋友關注我的訂閱號“小院不小”,或點擊下方二維碼關注。我將多年開發中遇到的難點,以及一些有意思的功能,體會都會一一發布到我的訂閱號中