In the previous article, Heigi analyzed Maven dependency conflicts into two categories:

  • The project has the same dependent application, there are multiple versions, each version is the same class, may have differences.
  • The project depends on different applications. There are classes with the same package name and class name.

In the second case, which is often the case, the local/test environment works fine, but the tests just don’t work when it goes live.

This is all about JVM class loading. The local/test environment loads the right classes, but the production process loads the wrong classes.

There are two main reasons:

  • The same class is only loaded once by the loader
  • Classes are loaded in different order in different environments

The same class is only loaded once by the loader

JVM class loading has a caching mechanism, and each class load is checked first to see if the class is loaded by the current class loader. If it is not loaded, it is handed to the parent class loader first. If the parent class loader fails to load, it is handed to the current class loader.

After the current class loader is loaded, it will be cached.

The core source code for class loading is in ClassLoader#loadClass:

ClassLoader#findLoadedClass0 is called, which is a native method that will eventually find the cache for the key value based on the class name plus the classloader.

Each class loader is responsible for a different load range:

  • BootstrapClassLoaderBootstrap class loading loads the most core class libraries, such as$JAVA_HOME/jre/lib/
  • ExtClassLoaderThe extension class loader takes care of the loading$JAVA_HOME/jre/lib/extSome extension classes under
  • AppClassLoaderThe application class loader will loadclasspathClass specified.

The classes on which the application is running will be recorded by the AppClassLoader. Classes with the same name will not be loaded the next time they are encountered.

Voice-over: Use caching to speed up queries

Classes are loaded in different order in different environments

Java can specify the location of dependent classes using the -classpath parameter.

The loading order of a class can be specified as follows:

java -classpath a.jar:b.jar:c.jar xx.xx.Main
Copy the code

In this case, the class load first looks for the relevant class in A.jar, and if it doesn’t find it, it moves on. So this way you can specify which jar package with the same name to use.

However, this approach is a bit cumbersome, if you rely on 100 JAR packages, you need to write all of them.

So production environments can splice jars together using shell commands:

LIB_DIR=lib
LIB_JARS=`ls $LIB_DIR|grep .jar|awk '{print "'$LIB_DIR'/"$0}'|tr "\n" ":"`
Copy the code

Java also supports wildcard notation:

java -classpath './*' xx.xx.Main
Copy the code

The loading order of this method will be affected by the loading order of the underlying system files.

Reproduce dependency conflicts

Suppose we now apply the dependency as follows:

Application A depends on B and C, and there is org.example.App in B and C with the same package and the same name. The code is as follows:

If you specify jar packages to start the application sequentially:

Jar: b-1.0-snapshot. jar: c-1.0-snapshot.jar org.example.classaCopy the code

The following output is displayed:

Change the order B,C:

The order in which the class loader’s classes are found will be from front to back, as specified by classpath.

If starting with wildcard:

java -classpath './*' org.example.ClassA
Copy the code

In this case, the JVM will load the class as the Schrodinger class, and it will not be possible to determine which JAR the loaded class came from before running it.

Use verbose:class to print loading classes

Verbose :class = verbose:class = verbose:class = verbose: verbose:class = verbose: verbose:class = verbose: verbose:class = verbose: verbose:class = verbose: verbose:class

java -verbose:class -classpath './*' xx.xx.Main
Copy the code

The following output is displayed:

This way you can see which Jar package the loaded class came from.

However, this approach requires rebooting the application, which is not very elegant for production systems.

Arthas found the source class

The Arthas sc command can be used to find information about loaded classes.

The sc command is short for search-class. This command can Search for classes that have been loaded into the JVM. The supported parameters are shown in the following table.

After the program is started, arthas is launched to enter application A.

Run the following command:

sc -d org.example.App
Copy the code

The following output is displayed:

Code-source displays the C from which the current lookup class org.example.App comes.

In addition, we can jad command decompiled class, online view source code.

conclusion

This article mainly explains why there are multiple classes with the same name in the application. It then introduces two quick ways to find the source of running application dependencies.

When the source of the conflicting classes is located, we can display the order in which the classpath JAR packages are specified, specifying the order in which the classes are loaded. But this is only a temporary fix. The problem of conflict in nature needs to be eliminated at a deeper level.

Welcome to pay attention to my public account: procedures to get daily dry goods push. If you are interested in my topics, you can also follow my blog: studyidea.cn