天天看點

JVM Profiler 啟動過程分析

開篇

 先來調侃一句,原來獨角獸Uber的程式員寫的代碼也是看得懂的,而且還是比較容易看得懂的,是以有時候在設計模式和代碼結構清晰以及可讀性方面我還是更傾向于後者,甯可重複或浪費一部分代碼也要整個代碼的可讀性更強一些。

 整個JVM Profiler的啟動過程比較簡單也非常清晰,當我們通過下面指令啟動Profiler以後,會按照以下步驟進行:

java -javaagent:target/jvm-profiler-0.0.8.jar=
reporter=com.uber.profiling.reporters.ConsoleOutputReporter,
tag=mytag,metricInterval=5000,
durationProfiling=com.uber.profiling.examples.HelloWorldApplication.publicSleepMethod,
argumentProfiling=com.uber.profiling.examples.HelloWorldApplication.publicSleepMethod.1,
sampleInterval=100 
-cp target/jvm-profiler-0.0.8.jar com.uber.profiling.examples.HelloWorldApplication
           
  • 1、解析參數:以啟動指令行傳入的參數作為基準,以指令參數指定配置檔案中的參數進行覆寫。核心點在于建構需要攔截的方法和方法參數等。
  • 2、攔截方法:針對需要攔截的方法在原有的方法體當中織入前置和後置耗時統計代碼。
  • 3、建立所有采集器Profiler:建立包括IO,Memory,cpu,方法體耗時等Profiler采集器。
  • 4、建立Reporter對象:根據參數指定建立指定的Reporter對象,用于上報采集資料。
  • 5、啟動采集器Profiler:根據周期性和非周期兩種來啟動采集器,其中周期性通過ScheduledExecutorService來實作的。
  • 6、增加shutdown鈎子函數:用于停止的時候做一些額外操作。

源碼介紹

  • 通過agentmain和premain兩種固定的函數建構入口
  • Arguments.parseArgs()解析指令行參數
  • arguments.runConfigProvider()解析配置檔案的參數
  • agentImpl.run()進入JVM Profiler的啟動過程
public final class Agent {

    private static AgentImpl agentImpl = new AgentImpl();

    private Agent() {
    }

    // Java SE6開始,提供了在應用程式的VM啟動後在動态添加代理的方式,即agentmain方式
    public static void agentmain(final String args, final Instrumentation instrumentation) {
        premain(args, instrumentation);
    }

    // premain是Java SE5開始就提供的代理方式,其必須在指令行指定代理jar,并且代理類必須在main方法前啟動。
    public static void premain(final String args, final Instrumentation instrumentation) {
        System.out.println("Java Agent " + AgentImpl.VERSION + " premain args: " + args);

        // 解析參數
        Arguments arguments = Arguments.parseArgs(args);

        // 解析參數中指定的配資檔案内的配置資訊,覆寫傳參帶入的參數
        arguments.runConfigProvider();

        agentImpl.run(arguments, instrumentation, null);
    }
}
           

參數解析過程

  • 參數解析過程用于解析指令行傳入的參數
  • 指令行參數是按照a=b的格式傳入的,解析分割生成Map對象
  • 根據Map對象建構參數對象Arguments進行具體參數解析過程
public static Arguments parseArgs(String args) {
        if (args == null) {
            return new Arguments(new HashMap<>());
        }

        args = args.trim();
        if (args.isEmpty()) {
            return new Arguments(new HashMap<>());
        }

        // 解析參數,按照a=b,c=d,e=f的格式進行參數傳入
        Map<String, List<String>> map = new HashMap<>();
        for (String argPair : args.split(",")) {
            String[] strs = argPair.split("=");
            if (strs.length != 2) {
                throw new IllegalArgumentException("Arguments for the agent should be like: key1=value1,key2=value2");
            }

            String key = strs[0].trim();
            if (key.isEmpty()) {
                throw new IllegalArgumentException("Argument key should not be empty");
            }

            List<String> list = map.get(key);
            if (list == null) {
                list = new ArrayList<>();
                map.put(key, list);
            }
            list.add(strs[1].trim());
        }

        return new Arguments(map);
    }


    private Arguments(Map<String, List<String>> parsedArgs) {
        // parsedArgs是kv的map形式
        updateArguments(parsedArgs);
    }
           

參數具體解析過程

  • 指令行傳入的參數解析成Map對象
  • 根據Map對象的key擷取對應的參數值指派給對應的變量
  • 攔截方法體會建構ClassAndMethod,攔截方法體參數會建構ClassMethodArgument
public final static String ARG_NOOP = "noop";
    public final static String ARG_REPORTER = "reporter";
    public final static String ARG_CONFIG_PROVIDER = "configProvider";
    public final static String ARG_CONFIG_FILE = "configFile";
    public final static String ARG_METRIC_INTERVAL = "metricInterval";
    public final static String ARG_SAMPLE_INTERVAL = "sampleInterval";
    public final static String ARG_TAG = "tag";
    public final static String ARG_CLUSTER = "cluster";
    public final static String ARG_APP_ID_VARIABLE = "appIdVariable";
    public final static String ARG_APP_ID_REGEX = "appIdRegex";
    public final static String ARG_DURATION_PROFILING = "durationProfiling";
    public final static String ARG_ARGUMENT_PROFILING = "argumentProfiling";
    public final static String ARG_BROKER_LIST = "brokerList";
    public final static String ARG_SYNC_MODE = "syncMode";
    public final static String ARG_TOPIC_PREFIX = "topicPrefix";
    public final static String ARG_OUTPUT_DIR = "outputDir";
    public final static String ARG_IO_PROFILING = "ioProfiling";
    public static final long MIN_INTERVAL_MILLIS = 50;


    public void updateArguments(Map<String, List<String>> parsedArgs) {
        // 儲存原始的參數資訊,也就是kv的map對象
        rawArgValues.putAll(parsedArgs);

        // 解析參數 ARG_NOOP
        String argValue = getArgumentSingleValue(parsedArgs, ARG_NOOP);
        if (needToUpdateArg(argValue)) {
            noop = Boolean.parseBoolean(argValue);
            logger.info("Got argument value for noop: " + noop);
        }

        // 解析參數 ARG_REPORTER
        argValue = getArgumentSingleValue(parsedArgs, ARG_REPORTER);
        if (needToUpdateArg(argValue)) {
            reporterConstructor = ReflectionUtils.getConstructor(argValue, Reporter.class);
            logger.info("Got argument value for reporter: " + argValue);
        }

        // 解析參數 ARG_CONFIG_PROVIDER
        argValue = getArgumentSingleValue(parsedArgs, ARG_CONFIG_PROVIDER);
        if (needToUpdateArg(argValue)) {
            configProviderConstructor = ReflectionUtils.getConstructor(argValue, ConfigProvider.class);
            logger.info("Got argument value for configProvider: " + argValue);
        }

        // 解析參數 ARG_CONFIG_FILE,應該可以覆寫通過傳參數帶入的參數,系統提供YAML格式的解析器
        argValue = getArgumentSingleValue(parsedArgs, ARG_CONFIG_FILE);
        if (needToUpdateArg(argValue)) {
            configFile = argValue;
            logger.info("Got argument value for configFile: " + configFile);
        }

        // 解析參數 ARG_METRIC_INTERVAL
        argValue = getArgumentSingleValue(parsedArgs, ARG_METRIC_INTERVAL);
        if (needToUpdateArg(argValue)) {
            metricInterval = Long.parseLong(argValue);
            logger.info("Got argument value for metricInterval: " + metricInterval);
        }

        if (metricInterval < MIN_INTERVAL_MILLIS) {
            throw new RuntimeException("Metric interval too short, must be at least " + Arguments.MIN_INTERVAL_MILLIS);
        }

        // 解析參數 ARG_SAMPLE_INTERVAL
        argValue = getArgumentSingleValue(parsedArgs, ARG_SAMPLE_INTERVAL);
        if (needToUpdateArg(argValue)) {
            sampleInterval = Long.parseLong(argValue);
            logger.info("Got argument value for sampleInterval: " + sampleInterval);
        }

        if (sampleInterval != 0 && sampleInterval < MIN_INTERVAL_MILLIS) {
            throw new RuntimeException("Sample interval too short, must be 0 (disable sampling) or at least " + Arguments.MIN_INTERVAL_MILLIS);
        }

        // 解析參數 ARG_TAG
        argValue = getArgumentSingleValue(parsedArgs, ARG_TAG);
        if (needToUpdateArg(argValue)) {
            tag = argValue;
            logger.info("Got argument value for tag: " + tag);
        }

        // 解析參數 ARG_CLUSTER
        argValue = getArgumentSingleValue(parsedArgs, ARG_CLUSTER);
        if (needToUpdateArg(argValue)) {
            cluster = argValue;
            logger.info("Got argument value for cluster: " + cluster);
        }

        // 解析參數 ARG_APP_ID_VARIABLE
        argValue = getArgumentSingleValue(parsedArgs, ARG_APP_ID_VARIABLE);
        if (needToUpdateArg(argValue)) {
            appIdVariable = argValue;
            logger.info("Got argument value for appIdVariable: " + appIdVariable);
        }

        // 解析參數 ARG_APP_ID_REGEX
        argValue = getArgumentSingleValue(parsedArgs, ARG_APP_ID_REGEX);
        if (needToUpdateArg(argValue)) {
            appIdRegex = argValue;
            logger.info("Got argument value for appIdRegex: " + appIdRegex);
        }

        // 解析參數 ARG_DURATION_PROFILING
        List<String> argValues = getArgumentMultiValues(parsedArgs, ARG_DURATION_PROFILING);
        if (!argValues.isEmpty()) {
            durationProfiling.clear();
            for (String str : argValues) {
                int index = str.lastIndexOf(".");
                if (index <= 0 || index + 1 >= str.length()) {
                    throw new IllegalArgumentException("Invalid argument value: " + str);
                }
                // 以最後一個.作為分割符,前半部分代表class,後半部分代表方法名
                // 以com.uber.profiling.examples.HelloWorldApplication.publicSleepMethod為例
                String className = str.substring(0, index);
                String methodName = str.substring(index + 1, str.length());
                // 建立類名和方法名的對象
                ClassAndMethod classAndMethod = new ClassAndMethod(className, methodName);
                // durationProfiling儲存所有需要監控的ClassAndMethod(包括類名和方法名)
                durationProfiling.add(classAndMethod);
                logger.info("Got argument value for durationProfiling: " + classAndMethod);
            }
        }

        // 解析參數 ARG_ARGUMENT_PROFILING
        argValues = getArgumentMultiValues(parsedArgs, ARG_ARGUMENT_PROFILING);
        if (!argValues.isEmpty()) {
            argumentProfiling.clear();
            for (String str : argValues) {
                // 以最後一個.作為分割符,前半部分代表類和方法,後半部分代表參數下标
                int index = str.lastIndexOf(".");
                if (index <= 0 || index + 1 >= str.length()) {
                    throw new IllegalArgumentException("Invalid argument value: " + str);
                }
                // 前半部分擷取類和方法名,後半部分代表下标
                String classMethodName = str.substring(0, index);
                int argumentIndex = Integer.parseInt(str.substring(index + 1, str.length()));

                // 分割類名和方法名
                index = classMethodName.lastIndexOf(".");
                if (index <= 0 || index + 1 >= classMethodName.length()) {
                    throw new IllegalArgumentException("Invalid argument value: " + str);
                }
                // 擷取類名和方法名
                String className = classMethodName.substring(0, index);
                String methodName = str.substring(index + 1, classMethodName.length());
                // 建構ClassMethodArgument對象,包括類名,方法名,參數下标。
                ClassMethodArgument classMethodArgument = new ClassMethodArgument(className, methodName, argumentIndex);
                // argumentProfiling用來儲存ClassMethodArgument對象
                argumentProfiling.add(classMethodArgument);
                logger.info("Got argument value for argumentProfiling: " + classMethodArgument);
            }
        }

        // 解析參數 ARG_BROKER_LIST
        argValue = getArgumentSingleValue(parsedArgs, ARG_BROKER_LIST);
        if (needToUpdateArg(argValue)) {
            brokerList = argValue;
            logger.info("Got argument value for brokerList: " + brokerList);
        }

        // 解析參數 ARG_SYNC_MODE
        argValue = getArgumentSingleValue(parsedArgs, ARG_SYNC_MODE);
        if (needToUpdateArg(argValue)) {
            syncMode = Boolean.parseBoolean(argValue);
            logger.info("Got argument value for syncMode: " + syncMode);
        }

        // 解析參數 ARG_TOPIC_PREFIX
        argValue = getArgumentSingleValue(parsedArgs, ARG_TOPIC_PREFIX);
        if (needToUpdateArg(argValue)) {
            topicPrefix = argValue;
            logger.info("Got argument value for topicPrefix: " + topicPrefix);
        }

        // 解析參數 ARG_OUTPUT_DIR
        argValue = getArgumentSingleValue(parsedArgs, ARG_OUTPUT_DIR);
        if (needToUpdateArg(argValue)) {
            outputDir = argValue;
            logger.info("Got argument value for outputDir: " + outputDir);
        }

        // 解析參數 ARG_IO_PROFILING
        argValue = getArgumentSingleValue(parsedArgs, ARG_IO_PROFILING);
        if (needToUpdateArg(argValue)) {
            ioProfiling = Boolean.parseBoolean(argValue);
            logger.info("Got argument value for ioProfiling: " + ioProfiling);
        }
    }
           

配置檔案參數解析過程

  • 解析配置檔案對應的參數,然後覆寫指令行解析的參數
public void runConfigProvider() {
        try {
            // 擷取指定的配置提供者,也就是配置解析的自定義函數
            ConfigProvider configProvider = getConfigProvider();
            if (configProvider != null) {
                // 擷取配置提供者傳回的參數配置資訊
                Map<String, Map<String, List<String>>> extraConfig = configProvider.getConfig();

                // Get root level config (use empty string as key in the config map)
                Map<String, List<String>> rootConfig = extraConfig.get("");
                if (rootConfig != null) {
                    updateArguments(rootConfig);
                    logger.info("Updated arguments based on config: " + JsonUtils.serialize(rootConfig));
                }

                // Get tag level config (use tag value to find config values in the config map)
                if (getTag() != null && !getTag().isEmpty()) {
                    Map<String, List<String>> overrideConfig = extraConfig.get(getTag());
                    if (overrideConfig != null) {
                        updateArguments(overrideConfig);
                        logger.info("Updated arguments based on config override: " + JsonUtils.serialize(overrideConfig));
                    }
                }
            }
        } catch (Throwable ex) {
            logger.warn("Failed to update arguments with config provider", ex);
        }
    }
           

啟動采集過程

  • 負責對建立方法體攔截的對象JavaAgentFileTransformer并添加到instrumentation當中。
  • 負責建立Profiler對象
  • 啟動所有的Profiler對象
  • 建立鈎子函數負責處理shutdown的後續操作
public void run(Arguments arguments, Instrumentation instrumentation, Collection<AutoCloseable> objectsToCloseOnShutdown) {
        if (arguments.isNoop()) {
            logger.info("Agent noop is true, do not run anything");
            return;
        }

        // 擷取監控上報方法的對象
        Reporter reporter = arguments.getReporter();

        String processUuid = UUID.randomUUID().toString();

        String appId = null;

        // 擷取監控對象的appId變量
        String appIdVariable = arguments.getAppIdVariable();
        if (appIdVariable != null && !appIdVariable.isEmpty()) {
            appId = System.getenv(appIdVariable);
        }
        
        if (appId == null || appId.isEmpty()) {
            appId = SparkUtils.probeAppId(arguments.getAppIdRegex());
        }

        // 将監控的方法名和監控方法的參數下标作為參數建立JavaAgentFileTransformer對象後通過instrumentation.addTransformer綁定到instrumentation。
        if (!arguments.getDurationProfiling().isEmpty()
                || !arguments.getArgumentProfiling().isEmpty()) {
            // 建構JavaAgentFileTransformer對象
            instrumentation.addTransformer(new JavaAgentFileTransformer(arguments.getDurationProfiling(), arguments.getArgumentProfiling()));
        }

        // 建立了所有Profiler對象
        List<Profiler> profilers = createProfilers(reporter, arguments, processUuid, appId);

        // 啟動所有Profile對象,内部分為一次性和周期性兩種
        ProfilerGroup profilerGroup = startProfilers(profilers);

        // 後置的鈎子函數
        Thread shutdownHook = new Thread(new ShutdownHookRunner(profilerGroup.getPeriodicProfilers(), Arrays.asList(reporter), objectsToCloseOnShutdown));
        Runtime.getRuntime().addShutdownHook(shutdownHook);
    }
           

建立采集器過程

  • 建立了各種采集資料的采集器對象
private List<Profiler> createProfilers(Reporter reporter, Arguments arguments, String processUuid, String appId) {
        String tag = arguments.getTag();
        String cluster = arguments.getCluster();
        long metricInterval = arguments.getMetricInterval();

        List<Profiler> profilers = new ArrayList<>();

        // 建立cpu采集器
        CpuAndMemoryProfiler cpuAndMemoryProfiler = new CpuAndMemoryProfiler(reporter);
        cpuAndMemoryProfiler.setTag(tag);
        cpuAndMemoryProfiler.setCluster(cluster);
        cpuAndMemoryProfiler.setIntervalMillis(metricInterval);
        cpuAndMemoryProfiler.setProcessUuid(processUuid);
        cpuAndMemoryProfiler.setAppId(appId);

        profilers.add(cpuAndMemoryProfiler);

        // 建立程序采集器
        ProcessInfoProfiler processInfoProfiler = new ProcessInfoProfiler(reporter);
        processInfoProfiler.setTag(tag);
        processInfoProfiler.setCluster(cluster);
        processInfoProfiler.setProcessUuid(processUuid);
        processInfoProfiler.setAppId(appId);

        profilers.add(processInfoProfiler);

        // 建立接口耗時統計采集器MethodDurationProfiler
        if (!arguments.getDurationProfiling().isEmpty()) {
            // 采集結果存儲資料結構
            ClassAndMethodLongMetricBuffer classAndMethodMetricBuffer = new ClassAndMethodLongMetricBuffer();

            MethodDurationProfiler methodDurationProfiler = new MethodDurationProfiler(classAndMethodMetricBuffer, reporter);
            methodDurationProfiler.setTag(tag);
            methodDurationProfiler.setCluster(cluster);
            methodDurationProfiler.setIntervalMillis(metricInterval);
            methodDurationProfiler.setProcessUuid(processUuid);
            methodDurationProfiler.setAppId(appId);

            MethodDurationCollector methodDurationCollector = new MethodDurationCollector(classAndMethodMetricBuffer);
            // 設定methodDurationCollector收集器
            MethodProfilerStaticProxy.setCollector(methodDurationCollector);

            profilers.add(methodDurationProfiler);
        }

        // 建立接口參數采集器MethodArgumentProfiler
        if (!arguments.getArgumentProfiling().isEmpty()) {
            ClassMethodArgumentMetricBuffer classAndMethodArgumentBuffer = new ClassMethodArgumentMetricBuffer();

            MethodArgumentProfiler methodArgumentProfiler = new MethodArgumentProfiler(classAndMethodArgumentBuffer, reporter);
            methodArgumentProfiler.setTag(tag);
            methodArgumentProfiler.setCluster(cluster);
            methodArgumentProfiler.setIntervalMillis(metricInterval);
            methodArgumentProfiler.setProcessUuid(processUuid);
            methodArgumentProfiler.setAppId(appId);

            MethodArgumentCollector methodArgumentCollector = new MethodArgumentCollector(classAndMethodArgumentBuffer);
            // 設定methodArgumentCollector統計資料收集器
            MethodProfilerStaticProxy.setArgumentCollector(methodArgumentCollector);

            profilers.add(methodArgumentProfiler);
        }

        // 建立堆棧資訊包括線程資訊的采集器
        if (arguments.getSampleInterval() > 0) {
            StacktraceMetricBuffer stacktraceMetricBuffer = new StacktraceMetricBuffer();

            StacktraceCollectorProfiler stacktraceCollectorProfiler = new StacktraceCollectorProfiler(stacktraceMetricBuffer, AgentThreadFactory.NAME_PREFIX);
            stacktraceCollectorProfiler.setIntervalMillis(arguments.getSampleInterval());
                    
            StacktraceReporterProfiler stacktraceReporterProfiler = new StacktraceReporterProfiler(stacktraceMetricBuffer, reporter);
            stacktraceReporterProfiler.setTag(tag);
            stacktraceReporterProfiler.setCluster(cluster);
            stacktraceReporterProfiler.setIntervalMillis(metricInterval);
            stacktraceReporterProfiler.setProcessUuid(processUuid);
            stacktraceReporterProfiler.setAppId(appId);

            profilers.add(stacktraceCollectorProfiler);
            profilers.add(stacktraceReporterProfiler);
        }

        // 建立IO采集器
        if (arguments.isIoProfiling()) {
            IOProfiler ioProfiler = new IOProfiler(reporter);
            ioProfiler.setTag(tag);
            ioProfiler.setCluster(cluster);
            ioProfiler.setIntervalMillis(metricInterval);
            ioProfiler.setProcessUuid(processUuid);
            ioProfiler.setAppId(appId);

            profilers.add(ioProfiler);
        }
        
        return profilers;
    }
           
  • 負責啟動建立的所有Profiler對象
  • 一次性采集的Profiler對象直接啟動一次
  • 周期性的Profiler對象直接通過ScheduledExecutorService服務進行排程
public ProfilerGroup startProfilers(Collection<Profiler> profilers) {
        if (started) {
            logger.warn("Profilers already started, do not start it again");
            return new ProfilerGroup(new ArrayList<>(), new ArrayList<>());
        }

        List<Profiler> oneTimeProfilers = new ArrayList<>();
        List<Profiler> periodicProfilers = new ArrayList<>();

        for (Profiler profiler : profilers) {
            if (profiler.getIntervalMillis() == 0) {
                oneTimeProfilers.add(profiler);
            } else if (profiler.getIntervalMillis() > 0) {
                periodicProfilers.add(profiler);
            } else {
                logger.log(String.format("Ignored profiler %s due to its invalid interval %s", profiler, profiler.getIntervalMillis()));
            }
        }

        // 啟動一次性采集程式
        for (Profiler profiler : oneTimeProfilers) {
            try {
                profiler.profile();
                logger.info("Finished one time profiler: " + profiler);
            } catch (Throwable ex) {
                logger.warn("Failed to run one time profiler: " + profiler, ex);
            }
        }
        // 先單次執行采集程式
        for (Profiler profiler : periodicProfilers) {
            try {
                profiler.profile();
                logger.info("Ran periodic profiler (first run): " + profiler);
            } catch (Throwable ex) {
                logger.warn("Failed to run periodic profiler (first run): " + profiler, ex);
            }
        }
        // 周期性的執行需要排程的采集程式
        scheduleProfilers(periodicProfilers);
        started = true;

        return new ProfilerGroup(oneTimeProfilers, periodicProfilers);
    }
           

周期性排程過程

  • 周期性任務排程直接将Profiler封裝成Thread對象
  • 周期性任務通過ScheduledExecutorService進行排程
// 對于周期性的采集器需要周期性的進行排程執行
    private void scheduleProfilers(Collection<Profiler> profilers) {
        int threadPoolSize = Math.min(profilers.size(), MAX_THREAD_POOL_SIZE);
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(threadPoolSize, new AgentThreadFactory());

        // 對于周期性的采集器需要周期性的取執行
        for (Profiler profiler : profilers) {
            if (profiler.getIntervalMillis() < Arguments.MIN_INTERVAL_MILLIS) {
                throw new RuntimeException("Interval too short for profiler: " + profiler + ", must be at least " + Arguments.MIN_INTERVAL_MILLIS);
            }
            
            ProfilerRunner worker = new ProfilerRunner(profiler);
            scheduledExecutorService.scheduleAtFixedRate(worker, 0, profiler.getIntervalMillis(), TimeUnit.MILLISECONDS);
            logger.info(String.format("Scheduled profiler %s with interval %s millis", profiler, profiler.getIntervalMillis()));
        }
    }


public class ProfilerRunner implements Runnable {
    private static final AgentLogger logger = AgentLogger.getLogger(ProfilerRunner.class.getName());

    private static final int MAX_ERROR_COUNT_TO_LOG = 100;
    
    private final Profiler profiler;
    private final AtomicLong errorCounter = new AtomicLong(0);

    public ProfilerRunner(Profiler profiler) {
        this.profiler = profiler;
    }

    @Override
    public void run() {
        try {
            profiler.profile();
        } catch (Throwable e) {
            long errorCountValue = errorCounter.incrementAndGet();
            if (errorCountValue <= MAX_ERROR_COUNT_TO_LOG) {
                logger.warn("Failed to run profile: " + profiler, e);
            } else {
                e.printStackTrace();
            }
        }
    }
}
           

maven打包配置

  • Javassist有額外的打包要求,特别的地方就在下面這個地方。
<plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.6</version>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Agent-Class>com.uber.profiling.Agent</Agent-Class>
                            <Premain-Class>com.uber.profiling.Agent</Premain-Class>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
           

繼續閱讀