1

We are using Java agent to enhance some methods in the application. The agent are dependent on other jars, and for convenience, so we packaged the agent and dependencies into a fatjar, and then load the dependent jars through a custom class loader. The program can startup normally but very slowly, and it takes a few minutes to startup completely. (I counted the loading time of each class (loadclass()in ClassLoader ) and found that the loading of some classes takes hundreds of milliseconds). Similarly, when I used spring boot (also fatjar with same dependencies), it would be much faster.

The version of JDK is 1.8.0_261.

The premain method is as follows:

public static void premain(String args, Instrumentation instrumentation) throws Exception {
    final AgentLanucher agentLauncher = new AgentLanucher();   //----1
    agentLauncher.launch();                                    //----2

    // add transformer
    instrumentation.addTransformer(new CustomAgentTransformer);
}

And the code of AgentLauncher is as follows:

import org.springframework.boot.loader.JarLauncher;
public class AgentLanucher extends JarLauncher {
    @Override
    protected void launch(String[] args) throws Exception{
        super.launch(args);
    }
}

When executing the code marked with (1,2), loading one class takes a long time (tens or hundreds of milliseconds). The code for counting time of loading one class is as follows:

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
    final long start = System.currentTimeMillis();
    final Class<?> clazz = super.loadClass(name, resolve);
    final long end = System.currentTimeMillis();
    System.out.println("Load [" + name + "] : " + (end -start));
    return clazz;
}

And I found that When the program run with JDK11, it run quickly. Then I changed the version back to JDK8, but moved the codes from the premain to a separate thread, it run quickly too. The codes is as follows:

public static void premain(String args, Instrumentation instrumentation) {
    CompletableFuture.runAsync(()->{
        final AgentLauncher launcher = new AgentLauncher();
        launcher.launch();
        instrumentation.addTransformer(new CustomAgentTransformer());
    });
}

There is another attempt, I blocked the main thread until the sub-thread completed. It run slowly. The codes is as follows:

public static void premain(String args, Instrumentation instrumentation) {
    final CompletableFuture<Void> initTask = CompletableFuture.runAsync(() -> {
        final AgentLauncher launcher = new AgentLauncher();
        launcher.launch();
        instrumentation.addTransformer(new CustomAgentTransformer());
    });
    System.out.println("Waiting.....");
    initTask.join();
}

I am confused about the issue, Thanks for any help.

knittl
  • 246,190
  • 53
  • 318
  • 364
hammer
  • 11
  • 2
  • 2
    What kind of agent? Is it fast without the agent? What is the agent instrumenting and how is it instrumenting it? Have you used a profiler already to find out where time is being spent? – knittl Dec 01 '21 at 11:37
  • Thanks for your reminding. I revised the question. The application startup quickly without agent. But what makes me wonder is that when executing the code before instrumentation, the class will take a long time to load. – hammer Dec 01 '21 at 12:32
  • With modular java one could deploy an application without fat jar (and perhaps your custom classloader), I would try that if noone comes up with a better cause for your performance. – Joop Eggen Dec 02 '21 at 10:51
  • 1
    It’s always helpful to include version information… – Holger Dec 02 '21 at 12:12
  • Thanks for your suggestions. I have add the version information of JDK and more description about the issue. And for some reason, I have to run the code with jdk8 and modularity in jdk cannot be used. Thanks. – hammer Dec 03 '21 at 02:10
  • i've seen this problem in older versions of java as well. generally, i just skip the fat jar completely and use the maven dependency plugin to dump all the classes from the dependent jars into the final jar. in most cases, this solves the problem (unless you have weird overlapping files, like for services discovery, i.e. ServiceLoader). – jtahlborn Dec 03 '21 at 02:25

1 Answers1

3

In OpenJDK 8, JIT compiler is initialized after JVM TI agents. This means, premain method always runs in the interpreted (slow) mode.

This is the bug JDK-7018422.

The order of initialization has been changed with the Module System, thus making premain eligible for JIT compilation in modern JDKs.

apangin
  • 92,924
  • 10
  • 193
  • 247