一、GraalVM
1. 什么是GraalVM
2. GraalVM的两种运行模式
(1)JIT即时编译模式
(2)AOT提前编译模式
3. 应用场景
4. 参数优化和故障诊断
二、新一代的GC
1. 垃圾回收器的技术演进
2. Shenandoah GC
测试代码:
/*
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.sample;
import com.sun.management.OperatingSystemMXBean;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
//执行5轮预热,每次持续2秒
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
//输出毫秒单位
@OutputTimeUnit(TimeUnit.MILLISECONDS)
//统计方法执行的平均耗时
@BenchmarkMode(Mode.AverageTime)
//java -jar benchmarks.jar -rf json
@State(Scope.Benchmark)
public class MyBenchmark {
//每次测试对象大小 4KB和4MB
@Param({"4","4096"})
int perSize;
private void test(Blackhole blackhole){
//每次循环创建堆内存60%对象 JMX获取到Java运行中的实时数据
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
//获取堆内存大小
MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
//获取到剩余的堆内存大小
long heapSize = (long) ((heapMemoryUsage.getMax() - heapMemoryUsage.getUsed()) * 0.6);
//计算循环次数
long size = heapSize / (1024 * perSize);
for (int i = 0; i < 4; i++) {
List<byte[]> objects = new ArrayList<>((int)size);
for (int j = 0; j < size; j++) {
objects.add(new byte[1024 * perSize]);
}
blackhole.consume(objects);
}
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseSerialGC"})
public void serialGC(Blackhole blackhole){
test(blackhole);
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseParallelGC"})
public void parallelGC(Blackhole blackhole){
test(blackhole);
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g"})
public void g1(Blackhole blackhole){
test(blackhole);
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseShenandoahGC"})
public void shenandoahGC(Blackhole blackhole){
test(blackhole);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(MyBenchmark.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
测试结果:Shenandoah GC对小对象的GC停顿很短,但是大对象效果不佳。
3. ZGC
测试代码:
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseZGC","-XX:+UseLargePages"})
public void zGC(Blackhole blackhole){
test(blackhole);
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseZGC","-XX:+ZGenerational","-XX:+UseLargePages"})
public void zGCGenerational(Blackhole blackhole){
test(blackhole);
}
测试结果:
4. 实战案例
package com.itheima.jvmoptimize.fullgcdemo;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.SneakyThrows;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@RestController
@RequestMapping("/fullgc")
public class Demo2Controller {
private Cache cache = Caffeine.newBuilder().weakKeys().softValues().build();
private List<Object> objs = new ArrayList<>();
private static final int _1MB = 1024 * 1024;
//FULLGC测试
//-Xms8g -Xmx8g -Xss256k -XX:MaxMetaspaceSize=512m -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/test.hprof -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
//ps + po 50并发 260ms 100并发 474 200并发 930
//cms -XX:+UseParNewGC -XX:+UseConcMarkSweepGC 50并发 157ms 200并发 833
//g1 JDK11 并发200 248
@GetMapping("/1")
public void test() throws InterruptedException {
cache.put(RandomStringUtils.randomAlphabetic(8),new byte[10 * _1MB]);
}
}
三、揭秘Java工具
1. Java工具的核心:Java Agent技术
(1)静态加载模式
(2)动态加载模式
步骤①:创建Maven项目,添加maven-assembly-plugin插件,此插件可以打包出java agent的jar包
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestFile>src/main/resources/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
②:编写类和premain方法,premain方法中打印一行信息
public class AgentDemo {
/**
* 参数添加模式 启动java主程序时添加 -javaangent:agent路径
* @param agentArgs
* @param inst
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("java agent执行了...");
}
}
③编写MANIFEST.MF文件,此文件主要用于描述java agent的配置属性,比如使用哪一个类的premain方法
Manifest-Version: 1.0
Premain-Class: com.itheima.jvm.javaagent.AgentDemo
Agent-Class: com.itheima.jvm.javaagent.AgentDemo
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Can-Set-Native-Method-Prefix: true
④使用maven-assembly-plugin进行打包
⑤创建spring boot应用,并静态加载上一步打包完的java agent
步骤①:创建maven项目,添加maven-assembly-plugin插件,此插件可以打包出java agent的jar包
②编写类和agentmain方法, agentmain方法中打印一行信息
package com.itheima.jvm.javaagent.demo01;
import java.lang.instrument.Instrumentation;
public class AgentDemo {
/**
* 参数添加模式 启动java主程序时添加 -javaangent:agent路径
* @param agentArgs
* @param inst
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("java agent执行了...");
}
/**
* attach 挂载模式 java主程序运行之后,随时可以将agent挂载上去
*/
public static void agentmain(String agentArgs, Instrumentation inst) {
//打印线程名称
System.out.println(Thread.currentThread().getName());
System.out.println("attach模式执行了...");
}
}
③编写MANIFEST.MF文件,此文件主要用于描述java agent的配置属性,比如使用哪一个类的agentmain方法。
④使用maven-assembly-plugin进行打包。
⑤编写main方法,动态连接到运行中的java程序。
package com.itheima.jvm.javaagent.demo01;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
public class AttachMain {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
VirtualMachine vm = VirtualMachine.attach("24200");
vm.loadAgent("D:\\jvm-java-agent\\target\\itheima-jvm-java-agent-jar-with-dependencies.jar");
}
}
2. 实战案例1:简化版的Arthas
(1)查看内存使用情况
package com.itheima.jvm.javaagent.demo02;
import java.lang.instrument.Instrumentation;
import java.lang.management.*;
import java.util.List;
/**
* 1、查询所有进程
* 2、显示内存相关的信息
*/
public class AgentDemo {
/**
* 参数添加模式 启动java主程序时添加 -javaangent:agent路径
* @param agentArgs
* @param inst
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("java agent执行了...");
}
/**
* attach 挂载模式 java主程序运行之后,随时可以将agent挂载上去
*/
//-XX:+UseSerialGC -Xmx1g -Xms512m
public static void agentmain(String agentArgs, Instrumentation inst) {
//打印内存的使用情况
memory();
}
//获取内存信息
private static void memory(){
List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
System.out.println("堆内存:");
//获取堆内存
getMemoryInfo(memoryPoolMXBeans, MemoryType.HEAP);
//获取非堆内存
System.out.println("非堆内存:");
getMemoryInfo(memoryPoolMXBeans, MemoryType.NON_HEAP);
}
private static void getMemoryInfo(List<MemoryPoolMXBean> memoryPoolMXBeans, MemoryType heap) {
memoryPoolMXBeans.stream().filter(x -> x.getType().equals(heap))
.forEach(x -> {
StringBuilder sb = new StringBuilder();
sb
.append("name:")
.append(x.getName())
.append(" used:")
.append(x.getUsage().getUsed() / 1024 / 1024)
.append("m")
.append(" max:")
.append(x.getUsage().getMax() / 1024 / 1024)
.append("m")
.append(" committed:")
.append(x.getUsage().getCommitted() / 1024 / 1024)
.append("m");
System.out.println(sb);
});
}
public static void main(String[] args) {
memory();
}
}
(2)查看直接内存使用情况,生成堆内存快照
查看直接内存使用情况:
package com.itheima.jvm.javaagent.demo02;
import java.lang.instrument.Instrumentation;
import java.lang.management.*;
import java.util.List;
/**
* 1、查询所有进程
* 2、显示内存相关的信息
*/
public class AgentDemo {
/**
* 参数添加模式 启动java主程序时添加 -javaangent:agent路径
* @param agentArgs
* @param inst
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("java agent执行了...");
}
/**
* attach 挂载模式 java主程序运行之后,随时可以将agent挂载上去
*/
//-XX:+UseSerialGC -Xmx1g -Xms512m
public static void agentmain(String agentArgs, Instrumentation inst) {
//打印内存的使用情况
memory();
}
//获取内存信息
private static void memory(){
List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();
System.out.println("堆内存:");
//获取堆内存
getMemoryInfo(memoryPoolMXBeans, MemoryType.HEAP);
//获取非堆内存
System.out.println("非堆内存:");
getMemoryInfo(memoryPoolMXBeans, MemoryType.NON_HEAP);
//nio使用的直接内存
try{
@SuppressWarnings("rawtypes")
Class bufferPoolMXBeanClass = Class.forName("java.lang.management.BufferPoolMXBean");
@SuppressWarnings("unchecked")
List<BufferPoolMXBean> bufferPoolMXBeans = ManagementFactory.getPlatformMXBeans(bufferPoolMXBeanClass);
for (BufferPoolMXBean mbean : bufferPoolMXBeans) {
StringBuilder sb = new StringBuilder();
sb
.append("name:")
.append(mbean.getName())
.append(" used:")
.append(mbean.getMemoryUsed()/ 1024 / 1024)
.append("m")
.append(" max:")
.append(mbean.getTotalCapacity() / 1024 / 1024)
.append("m");
System.out.println(sb);
}
}catch (Exception e){
System.out.println(e);
}
}
private static void getMemoryInfo(List<MemoryPoolMXBean> memoryPoolMXBeans, MemoryType heap) {
memoryPoolMXBeans.stream().filter(x -> x.getType().equals(heap))
.forEach(x -> {
StringBuilder sb = new StringBuilder();
sb
.append("name:")
.append(x.getName())
.append(" used:")
.append(x.getUsage().getUsed() / 1024 / 1024)
.append("m")
.append(" max:")
.append(x.getUsage().getMax() / 1024 / 1024)
.append("m")
.append(" committed:")
.append(x.getUsage().getCommitted() / 1024 / 1024)
.append("m");
System.out.println(sb);
});
}
public static void main(String[] args) {
memory();
}
}
生成内存快照:
public static void heapDump(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm");
String filename = simpleDateFormat.format(new Date()) + ".hprof";
System.out.println("生成内存dump文件,文件名为:" + filename);
HotSpotDiagnosticMXBean hotSpotDiagnosticMXBean =
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class);
try {
hotSpotDiagnosticMXBean.dumpHeap(filename, true);
} catch (IOException e) {
e.printStackTrace();
}
}
(3)打印栈信息
package com.itheima.jvm.javaagent.demo03;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class ThreadCommand {
public static void printStackInfo(){
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] infos = threadMXBean.dumpAllThreads(threadMXBean.isObjectMonitorUsageSupported(),
threadMXBean.isSynchronizerUsageSupported());
// 打印线程信息
for (ThreadInfo info : infos) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("name:")
.append(info.getThreadName())
.append(" threadId:")
.append(info.getThreadId())
.append(" state:")
.append(info.getThreadState())
;
System.out.println(stringBuilder);
// 打印栈信息
StackTraceElement[] stackTrace = info.getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
System.out.println(stackTraceElement.toString());
}
System.out.println();
}
}
public static void main(String[] args) {
printStackInfo();
}
}
(4)打印类加载器
package com.itheima.jvm.javaagent.demo04;
import org.jd.core.v1.ClassFileToJavaSourceDecompiler;
import org.jd.core.v1.api.loader.Loader;
import org.jd.core.v1.api.loader.LoaderException;
import org.jd.core.v1.api.printer.Printer;
import java.lang.instrument.*;
import java.security.ProtectionDomain;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.stream.Collectors;
public class ClassCommand {
//获取所有类加载器
private static Set<ClassLoader> getAllClassLoader(Instrumentation inst){
HashSet<ClassLoader> classLoaders = new HashSet<>();
Class[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class clazz : allLoadedClasses) {
ClassLoader classLoader = clazz.getClassLoader();
classLoaders.add(classLoader);
}
return classLoaders;
}
public static void printAllClassLoader(Instrumentation inst){
Set<ClassLoader> allClassLoader = getAllClassLoader(inst);
String result = allClassLoader.stream().map(x -> {
if (x ==null) {
return "BootStrapClassLoader";
} else {
return x.getName();
}
}).filter(x -> x != null).distinct().sorted(String::compareTo).collect(Collectors.joining(","));
System.out.println(result);
}
}
(5)打印类的源码
pom添加依赖:
<dependency>
<groupId>org.jd</groupId>
<artifactId>jd-core</artifactId>
<version>1.1.3</version>
</dependency>
//获取类信息
public static void printClass(Instrumentation inst){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入类名:");
String next = scanner.next();
Class[] allLoadedClasses = inst.getAllLoadedClasses();
System.out.println("要查找的类名是:" + next);
//匹配类名
for (Class clazz : allLoadedClasses) {
if(clazz.getName().equals(next)){
System.out.println("找到了类,类加载器为:" + clazz.getClassLoader());
ClassFileTransformer transformer = new ClassFileTransformer() {
@Override
public byte[] transform(Module module, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
ClassFileToJavaSourceDecompiler classFileToJavaSourceDecompiler = new ClassFileToJavaSourceDecompiler();
Printer printer = new Printer() {
protected static final String TAB = " ";
protected static final String NEWLINE = "\n";
protected int indentationCount = 0;
protected StringBuilder sb = new StringBuilder();
@Override public String toString() { return sb.toString(); }
@Override public void start(int maxLineNumber, int majorVersion, int minorVersion) {}
@Override public void end() {
System.out.println(sb.toString());
}
@Override public void printText(String text) { sb.append(text); }
@Override public void printNumericConstant(String constant) { sb.append(constant); }
@Override public void printStringConstant(String constant, String ownerInternalName) { sb.append(constant); }
@Override public void printKeyword(String keyword) { sb.append(keyword); }
@Override public void printDeclaration(int type, String internalTypeName, String name, String descriptor) { sb.append(name); }
@Override public void printReference(int type, String internalTypeName, String name, String descriptor, String ownerInternalName) { sb.append(name); }
@Override public void indent() { this.indentationCount++; }
@Override public void unindent() { this.indentationCount--; }
@Override public void startLine(int lineNumber) { for (int i=0; i<indentationCount; i++) sb.append(TAB); }
@Override public void endLine() { sb.append(NEWLINE); }
@Override public void extraLine(int count) { while (count-- > 0) sb.append(NEWLINE); }
@Override public void startMarker(int type) {}
@Override public void endMarker(int type) {}
};
try {
classFileToJavaSourceDecompiler.decompile(new Loader() {
@Override
public boolean canLoad(String s) {
return false;
}
@Override
public byte[] load(String s) throws LoaderException {
return classfileBuffer;
}
},printer,className);
} catch (Exception e) {
e.printStackTrace();
}
//System.out.println(new String(classfileBuffer));
return ClassFileTransformer.super.transform(module, loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
}
};
inst.addTransformer(transformer,true);
try {
inst.retransformClasses(clazz);
} catch (UnmodifiableClassException e) {
e.printStackTrace();
}finally {
inst.removeTransformer(transformer);
}
}
}
}
(6)打印方法执行的参数和耗时
(1)ASM
package com.itheima.jvm.javaagent.demo05;
import org.objectweb.asm.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import static org.objectweb.asm.Opcodes.*;
public class ASMDemo {
public static byte[] classASM(byte[] bytes){
ClassWriter cw = new ClassWriter(0);
// cv forwards all events to cw
ClassVisitor cv = new ClassVisitor(ASM7, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
return new MyMethodVisitor(this.api,mv);
}
};
ClassReader cr = new ClassReader(bytes);
cr.accept(cv, 0);
return cw.toByteArray();
}
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
InputStream inputStream = ASMDemo.class.getResourceAsStream("/com/itheima/jvm/javaagent/demo05/ASMDemo.class");
byte[] b1 = inputStream.readAllBytes();
byte[] b2 = classASM(b1); // b2 represents the same class as b1
//创建类加载器
MyClassLoader myClassLoader = new MyClassLoader();
Class clazz = myClassLoader.defineClass("com.itheima.jvm.javaagent.demo05.ASMDemo", b2);
clazz.getDeclaredConstructor().newInstance();
}
}
class MyClassLoader extends ClassLoader {
public Class defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor(int api, MethodVisitor methodVisitor) {
super(api, methodVisitor);
}
@Override
public void visitCode() {
mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
mv.visitLdcInsn("开始执行");
mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);
super.visitCode();
}
@Override
public void visitInsn(int opcode) {
if(opcode == ARETURN || opcode == RETURN ) {
mv.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
mv.visitLdcInsn("结束执行");
mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);
}
super.visitInsn(opcode);
}
@Override
public void visitEnd() {
mv.visitMaxs(20,50);
super.visitEnd();
}
}
(2)Byte Buddy 字节码增强技术
3. 编写一个Advice通知描述如何去增强类:
package com.itheima.jvm.javaagent.demo05;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.matcher.ElementMatchers;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
public class ByteBuddyDemo {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Foo foo = new Foo();
MyClassLoader myClassLoader = new MyClassLoader();
Class<? extends Foo> newClazz = new ByteBuddy()
.subclass(Foo.class)
.method(ElementMatchers.any())
.intercept(Advice.to(MyAdvice.class))
.make()
.load(myClassLoader)
.getLoaded();
Foo foo1 = newClazz.getDeclaredConstructor().newInstance();
foo1.test();
}
}
class MyAdvice {
@Advice.OnMethodEnter
static void onEnter(){
System.out.println("方法进入");
}
@Advice.OnMethodExit
static void onExit(){
System.out.println("方法退出");
}
}
增强后的代码:
package com.itheima.jvm.javaagent.demo05;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.jd.core.v1.ClassFileToJavaSourceDecompiler;
import org.jd.core.v1.api.loader.Loader;
import org.jd.core.v1.api.loader.LoaderException;
import org.jd.core.v1.api.printer.Printer;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.Scanner;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
public class ClassEnhancerCommand {
//获取类信息
public static void enhanceClass(Instrumentation inst){
Scanner scanner = new Scanner(System.in);
System.out.println("请输入类名:");
String next = scanner.next();
Class[] allLoadedClasses = inst.getAllLoadedClasses();
System.out.println("要查找的类名是:" + next);
//匹配类名
for (Class clazz : allLoadedClasses) {
if(clazz.getName().equals(next)){
System.out.println("找到了类,类加载器为:" + clazz.getClassLoader());
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with( //new AgentBuilder.Listener.WithErrorsOnly(
new AgentBuilder.Listener.WithTransformationsOnly(
AgentBuilder.Listener.StreamWriting.toSystemOut()))
//.type(ElementMatchers.isAnnotatedWith(named("org.springframework.web.bind.annotation.RestController")))
.type(ElementMatchers.named(clazz.getName()))
.transform((builder, type, classLoader, module, protectionDomain) ->
builder.visit(Advice.to(MyAdvice.class).on(ElementMatchers.any()))
// builder .method(ElementMatchers.any())
// .intercept(MethodDelegation.to(MyInterceptor.class))
)
.installOn(inst);
}
}
}
}
package com.itheima.jvm.javaagent.demo07;
import net.bytebuddy.asm.Advice;
class MyAdvice {
@Advice.OnMethodEnter
static long enter(@Advice.AllArguments Object[] ary) {
if(ary != null) {
for(int i =0 ; i < ary.length ; i++){
System.out.println("Argument: " + i + " is " + ary[i]);
}
}
return System.nanoTime();
}
@Advice.OnMethodExit
static void exit(@Advice.Enter long value) {
System.out.println("耗时为:" + (System.nanoTime() - value) + "纳秒");
}
}
最后将整个简化版的arthas进行打包,在服务器上进行测试。使用maven-shade-plugin插件可以将所有依赖打入同一个jar包中并指定入口main方法
<!--打包成jar包使用-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>itheima-attach-agent</finalName>
<transformers>
<!--java -jar 默认启动的主类-->
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.itheima.jvm.javaagent.AttachMain</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
3. 实战案例2:APM系统的数据采集
Java Agent参数的获取
在Java Agent中如果需要传递参数到Byte Buddy,可以采用如下的方式:
1、绑定Key Value,Key是一个自定义注解,Value是参数的值。
2、自定义注解
3、通过注解注入
代码:
package com.itheima.javaagent;
import com.itheima.javaagent.command.ClassCommand;
import com.itheima.javaagent.command.MemoryCommand;
import com.itheima.javaagent.command.ThreadCommand;
import com.itheima.javaagent.enhancer.AgentParam;
import com.itheima.javaagent.enhancer.MyAdvice;
import com.itheima.javaagent.enhancer.TimingAdvice;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
import java.util.Scanner;
public class AgentMain {
//premain方法
public static void premain(String agentArgs, Instrumentation inst){
//使用bytebuddy增强类
new AgentBuilder.Default()
//禁止byte buddy处理时修改类名
.disableClassFormatChanges()
//处理时使用retransform增强
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
//打印出错误日志
.with(new AgentBuilder.Listener.WithTransformationsOnly(AgentBuilder.Listener.StreamWriting
.toSystemOut()))
//匹配哪些类
.type(ElementMatchers.isAnnotatedWith(ElementMatchers.named("org.springframework.web.bind.annotation.RestController")
.or(ElementMatchers.named("org.springframework.web.bind.annotation.Controller")))
)
//增强,使用MyAdvice通知,对所有方法都进行增强
.transform((builder, typeDescription, classLoader, module, protectionDomain) ->
builder.visit(Advice
.withCustomMapping()
.bind(AgentParam.class,agentArgs)
.to(TimingAdvice.class).on(ElementMatchers.any())))
.installOn(inst);
}
}
package com.itheima.javaagent.enhancer;
import net.bytebuddy.asm.Advice;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
//统计耗时,打印方法名、类名
public class TimingAdvice {
//方法进入时,返回开始时间
@Advice.OnMethodEnter
static long enter(){
return System.nanoTime();
}
//方法退出时候,统计方法执行耗时
@Advice.OnMethodExit
static void exit(@Advice.Enter long value,
@Advice.Origin("#t") String className,
@Advice.Origin("#m") String methodName,
@AgentParam("agent.log") String fileName){
String str = methodName + "@" + className + "耗时为: " + (System.nanoTime() - value) + "纳秒\n";
try {
FileUtils.writeStringToFile(new File(fileName),str, StandardCharsets.UTF_8,true);
} catch (IOException e) {
e.printStackTrace();
}
}
}