【Arthas】Arthas dump导出加载类

x33g5p2x  于2022-04-06 转载在 其他  
字(6.5k)|赞(0)|评价(0)|浏览(337)

1.概述

转载:Arthas dump导出加载类

2.开篇

Arthas提供dump命令来导出已加载类的 byte code 到特定目录,可以考虑用来排查线上代码是否是最新的,这个命令的原理和Arthas的jad命令基本一致的,唯一的差别就是dump会到导出原始字节码,jad命令返回反编译的字节码。

dump命令的关键点在于Instrumentation的addTransformer方法,通过指定ClassFileTransformer来导出指定类的字节码。

Instrumentation
public interface Instrumentation {
    //添加一个ClassFileTransformer
    //之后类加载时都会经过这个ClassFileTransformer转换
    void addTransformer(ClassFileTransformer transformer, boolean canRetransform);

    void addTransformer(ClassFileTransformer transformer);
    //移除ClassFileTransformer
    boolean removeTransformer(ClassFileTransformer transformer);

    boolean isRetransformClassesSupported();
    //将一些已经加载过的类重新拿出来经过注册好的ClassFileTransformer转换
    //retransformation可以修改方法体,但是不能变更方法签名、增加和删除方法/类的成员属性
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;

    boolean isRedefineClassesSupported();

    //重新定义某个类
    void redefineClasses(ClassDefinition... definitions)
        throws  ClassNotFoundException, UnmodifiableClassException;

    boolean isModifiableClass(Class<?> theClass);

    @SuppressWarnings("rawtypes")
    Class[] getAllLoadedClasses();

    @SuppressWarnings("rawtypes")
    Class[] getInitiatedClasses(ClassLoader loader);

    long getObjectSize(Object objectToSize);

    void appendToBootstrapClassLoaderSearch(JarFile jarfile);

    void appendToSystemClassLoaderSearch(JarFile jarfile);

    boolean isNativeMethodPrefixSupported();

    void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);
}

Instrumention支持的功能都在java.lang.instrument.Instrumentation接口中体现。

我们通过addTransformer方法注册了一个ClassFileTransformer,后面类加载的时候都会经过这个Transformer处理。对于已加载过的类,可以调用retransformClasses来重新触发这个Transformer的转换。
ClassFileTransformer可以判断是否需要修改类定义并根据自己的代码规则修改类定义然后返回给JVM。利用这个Transformer类,我们可以很好的实现虚拟机层面的AOP。

3.dump流程分析

public class DumpClassCommand extends AnnotatedCommand {

    public void process(CommandProcess process) {
        RowAffect effect = new RowAffect();
        try {
            Instrumentation inst = process.session().getInstrumentation();
            // 查找待导出的类
            Set<Class<?>> matchedClasses = SearchUtils.searchClass(inst, classPattern, isRegEx, code);
            if (matchedClasses == null || matchedClasses.isEmpty()) {
                status = processNoMatch(process);
            } else if (matchedClasses.size() > limit) {
                status = processMatches(process, matchedClasses);
            } else {
                // 执行类的导出
                status = processMatch(process, effect, inst, matchedClasses);
            }
            process.appendResult(new RowAffectModel(effect));
            CommandUtils.end(process, status);
        } catch (Throwable e){
        }
    }

    private ExitStatus processMatch(CommandProcess process, RowAffect effect, Instrumentation inst, Set<Class<?>> matchedClasses) {
        try {
            // 执行dump操作
            Map<Class<?>, File> classFiles = dump(inst, matchedClasses);
            // 处理dump后的结果
            List<DumpClassVO> dumpedClasses = new ArrayList<DumpClassVO>(classFiles.size());
            for (Map.Entry<Class<?>, File> entry : classFiles.entrySet()) {
                Class<?> clazz = entry.getKey();
                File file = entry.getValue();
                DumpClassVO dumpClassVO = new DumpClassVO();
                dumpClassVO.setLocation(file.getCanonicalPath());
                ClassUtils.fillSimpleClassVO(clazz, dumpClassVO);
                dumpedClasses.add(dumpClassVO);
            }
            process.appendResult(new DumpClassModel().setDumpedClasses(dumpedClasses));

            effect.rCnt(classFiles.keySet().size());
            return ExitStatus.success();
        } catch (Throwable t) {
        }
    }

    
    private Map<Class<?>, File> dump(Instrumentation inst, Set<Class<?>> classes) throws UnmodifiableClassException {
        // 通过ClassDumpTransformer
        ClassDumpTransformer transformer = null;
        if (directory != null) {
            transformer = new ClassDumpTransformer(classes, new File(directory));
        } else {
            transformer = new ClassDumpTransformer(classes);
        }
        // 执行类的导出
        InstrumentationUtils.retransformClasses(inst, transformer, classes);
        return transformer.getDumpResult();
    }
}

dump的核心流程在于查找对应的class类并执行dump动作。
dump内部是通过Instrument来进行触发ClassDumpTransformer动作,内部执行字节码的保存。

class ClassDumpTransformer implements ClassFileTransformer {

    private Set<Class<?>> classesToEnhance;
    private Map<Class<?>, File> dumpResult;
    private File arthasLogHome;

    private File directory;

    public ClassDumpTransformer(Set<Class<?>> classesToEnhance) {
        this(classesToEnhance, null);
    }

    public ClassDumpTransformer(Set<Class<?>> classesToEnhance, File directory) {
        this.classesToEnhance = classesToEnhance;
        this.dumpResult = new HashMap<Class<?>, File>();
        this.arthasLogHome = new File(LogUtil.loggingDir());
        this.directory = directory;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer)
            throws IllegalClassFormatException {
        // 如果是待导出的类执行dumpClassIfNecessary操作
        if (classesToEnhance.contains(classBeingRedefined)) {
            dumpClassIfNecessary(classBeingRedefined, classfileBuffer);
        }
        return null;
    }

    private void dumpClassIfNecessary(Class<?> clazz, byte[] data) {
        String className = clazz.getName();
        ClassLoader classLoader = clazz.getClassLoader();
        String classDumpDir = "classdump";

        // 创建类所在的包路径
        File dumpDir = null;
        if (directory != null) {
            dumpDir = directory;
        } else {
            dumpDir = new File(arthasLogHome, classDumpDir);
        }

        String fileName;
        if (classLoader != null) {
            fileName = classLoader.getClass().getName() + "-" + Integer.toHexString(classLoader.hashCode()) +
                    File.separator + className.replace(".", File.separator) + ".class";
        } else {
            fileName = className.replace(".", File.separator) + ".class";
        }

        File dumpClassFile = new File(dumpDir, fileName);

        // 将类字节码写入文件
        try {
            FileUtils.writeByteArrayToFile(dumpClassFile, data);
            dumpResult.put(clazz, dumpClassFile);
        } catch (IOException e) {
        }
    }
}
ClassDumpTransformer的重写了transform方法,内部执行dumpClassIfNecessary保存字节码。


public class InstrumentationUtils {

    public static void retransformClasses(Instrumentation inst, ClassFileTransformer transformer,
            Set<Class<?>> classes) {
        try {
            inst.addTransformer(transformer, true);

            for (Class<?> clazz : classes) {
                if (ClassUtils.isLambdaClass(clazz)) {
                    logger.info(
                            "ignore lambda class: {}, because jdk do not support retransform lambda class: https://github.com/alibaba/arthas/issues/1512.",
                            clazz.getName());
                    continue;
                }
                try {
                    // 执行retransformClasses动作触发类
                    inst.retransformClasses(clazz);
                } catch (Throwable e) {
                    String errorMsg = "retransformClasses class error, name: " + clazz.getName();
                    logger.error(errorMsg, e);
                }
            }
        } finally {
            inst.removeTransformer(transformer);
        }
    }

    public static void trigerRetransformClasses(Instrumentation inst, Collection<String> classes) {
        for (Class<?> clazz : inst.getAllLoadedClasses()) {
            if (classes.contains(clazz.getName())) {
                try {
                    inst.retransformClasses(clazz);
                } catch (Throwable e) {
                    String errorMsg = "retransformClasses class error, name: " + clazz.getName();
                    logger.error(errorMsg, e);
                }
            }
        }
    }
}

retransformClasses先通过addTransformer来添加ClassDumpTransformer对象。

retransformClasses再通过retransformClasses来触发指定类的重新加载,重新加载过程中会触发ClassDumpTransformer的逻辑来实现字节码dump过程。

retransformClasses最后通过removeTransformer移除ClassDumpTransformer对象。

相关文章