ArthasArthas dump导出加载类

Posted 九师兄

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArthasArthas dump导出加载类相关的知识,希望对你有一定的参考价值。

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对象。

以上是关于ArthasArthas dump导出加载类的主要内容,如果未能解决你的问题,请参考以下文章

ArthasArthas retransform动态重新加载类

ArthasArthas classloader类加载器

ArthasArthas 类查找和反编译原理

ArthasArthas mc内存动态编译原理

内存dump文件导出与查看

mysqldump怎么加快导入导出的速度