# Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG$@" export MAVEN_CMD_LINE_ARGS
The Java launcher, java, initiates the Java virtual machine. The virtual machine searches for and loads classes in this order:
Bootstrap classes - Classes that comprise the Java platform, including the classes in rt.jar and several other important jar files.
Extension classes - Classes that use the Java Extension mechanism. These are bundled as .jar files located in the extensions directory.
User classes - Classes defined by developers and third parties that do not take advantage of the extension mechanism.
How the Java Launcher Finds User Classes
User classes are classes which build on the Java platform. To find user classes, the launcher refers to the user class path – a list of directories, JAR archives, and ZIP archives which contain class files. A class file has a subpath name that reflects the class’s fully-qualified name. For example, if the class com.mypackage.MyClass is stored under /myclasses, then /myclasses must be in the user class path and the full path to the class file must be /myclasses/com/mypackage/MyClass.class. If the class is stored in an archive named myclasses.jar, then myclasses.jar must be in the user class path, and the class file must be stored in the archive as com/mypackage/MyClass.class. The user class path is specified as a string, with a colon (:) separating the class path entries on Solaris, and a semi-colon (;) separating entries on Microsoft Windows systems. The java launcher puts the user class path string in the java.class.path system property. The possible sources of this value are:
The default value, “.”, meaning that user class files are all the class files in the current directory (or under it, if in a package).
The value of the CLASSPATH environment variable, which overrides the default value.
The value of the -cp or -classpath command line option, which overrides both the default value and the CLASSPATH value.
The JAR archive specified by the -jar option, which overrides all other values. If this option is used, all user classes must come from the specified archive.
IDE 和 maven
IDE 会通过 maven 插件,解析项目模块,计算 classpath,最终通过 -classpath 影响启动,所以修改依赖的时候需要重新加载项目。
IDE 多模块的 classpath
每个模块的 classpath 是独立计算的,在不同模块启动 main 方法的的时候 -classpath 参数是不同的,例如有两个模块 AB,A 依赖 B 和第三方 C.jar , B 依赖第三方 D.jar
// A Klass provides: // 1: language level class object (method dictionary etc.) // 2: provide vm dispatch behavior for the object // Both functions are combined into one C++ class. // ... classKlass : public Metadata { // ... }
publicLauncher() { // Create the extension class loader ClassLoader extcl; try { extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { thrownewInternalError("Could not create extension class loader", e); }
// Now create the class loader to use to launch the application try { loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { thrownewInternalError("Could not create application class loader", e); }
// Also set the context class loader for the primordial thread. Thread.currentThread().setContextClassLoader(loader);
The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine’s built-in class loader, called the “bootstrap class loader”, does not itself have a parent but may serve as the parent of a ClassLoader instance.
// The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. privatefinal ClassLoader parent; // ... }
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { longt0= System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader }
if (c == null) { // If still not found, then invoke findClass in order // to find the class.
// ... c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } } // ...
/** * Finds the class with the specified <a href="#name">binary name</a>. * This method should be overridden by class loader implementations that * follow the delegation model for loading classes, and will be invoked by * the {@link #loadClass <tt>loadClass</tt>} method after checking the * parent class loader for the requested class. The default implementation * throws a <tt>ClassNotFoundException</tt>. */ protected Class<?> findClass(String name) throws ClassNotFoundException { thrownewClassNotFoundException(name); }
// relies on the fact that spoofing is impossible if a class has a name // of the form "java.*" if ((name != null) && name.startsWith("java.")) { thrownewSecurityException("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); } // ... }
Initialization of a class or interface consists of executing its class or interface initialization method (§2.9).
A class or interface C may be initialized only as a result of:
The execution of any one of the Java Virtual Machine instructions new, getstatic, putstatic, or invokestatic that references C (§new, §getstatic, §putstatic, §invokestatic). These instructions reference a class or interface directly or indirectly through either a field reference or a method reference.Upon execution of a new instruction, the referenced class is initialized if it has not been initialized already.Upon execution of a getstatic, putstatic, or invokestatic instruction, the class or interface that declared the resolved field or method is initialized if it has not been initialized already.
The first invocation of a java.lang.invoke.MethodHandle instance which was the result of method handle resolution (§5.4.3.5) for a method handle of kind 2 (REF_getStatic), 4 (REF_putStatic), 6 (REF_invokeStatic), or 8 (REF_newInvokeSpecial).This implies that the class of a bootstrap method is initialized when the bootstrap method is invoked for an invokedynamic instruction (§invokedynamic), as part of the continuing resolution of the call site specifier.
Invocation of certain reflective methods in the class library (§2.12), for example, in class Class or in package java.lang.reflect.
If C is a class, the initialization of one of its subclasses.
If C is an interface that declares a non-abstract, non-static method, the initialization of a class that implements C directly or indirectly.
If C is a class, its designation as the initial class at Java Virtual Machine startup (§5.2).
有且只有上述 6 种情况(且这个类没有被加载)会去加载一个类,简单来说
new 读取/修改静态变量(排除在常量池的)调用静态方法
动态语言支持 java.lang.invoke.MethodHandle 相关
class library 中的一些方法,例如 Class 和 ClassLoader 的部分方法、反射(java.lang.reflect)等
try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader }
public class ClassNotFoundException extends ReflectiveOperationException
Thrown when an application tries to load in a class through its string name using:
The forName method in class Class.
The findSystemClass method in class ClassLoader .
The loadClass method in class ClassLoader. but no definition for the class with the specified name could be found.
public class NoClassDefFoundError extends LinkageError
Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found. The searched-for class definition existed when the currently executing class was compiled, but the definition can no longer be found.
java.lang.NoClassDefFoundError 是一个 Error,发生在实例化一个类的时候(类已经找到了),一般情况是这个类依赖的某些定义不存在,一般会出现在 jar 包的版本不兼容上。
类的卸载
满足如下条件,则类会被卸载
该类的所有实例(包含子类的实例)都被回收
该类的 ClassLoader 不存在任何引用
该类的 Class 对象不存在任何引用(包括相关的 Method Field 等,无法通过反射访问)
/** * Launch the application. This method is the initial entry point that should be * called by a subclass {@code public static void main(String[] args)} method. * @param args the incoming arguments * @throws Exception if the application fails to launch */ protectedvoidlaunch(String[] args)throws Exception { JarFile.registerUrlProtocolHandler(); ClassLoaderclassLoader= createClassLoader(getClassPathArchives()); launch(args, getMainClass(), classLoader); }
/** * Create a classloader for the specified archives. * @param archives the archives * @return the classloader * @throws Exception if the classloader cannot be created */ protected ClassLoader createClassLoader(List<Archive> archives)throws Exception { List<URL> urls = newArrayList<URL>(archives.size()); for (Archive archive : archives) { urls.add(archive.getUrl()); } return createClassLoader(urls.toArray(newURL[urls.size()])); }
/** * Create a classloader for the specified URLs. * @param urls the URLs * @return the classloader * @throws Exception if the classloader cannot be created */ protected ClassLoader createClassLoader(URL[] urls)throws Exception { returnnewLaunchedURLClassLoader(urls, getClass().getClassLoader()); }
/** * Launch the application given the archive file and a fully configured classloader. * @param args the incoming arguments * @param mainClass the main class to run * @param classLoader the classloader * @throws Exception if the launch fails */ protectedvoidlaunch(String[] args, String mainClass, ClassLoader classLoader)throws Exception { Thread.currentThread().setContextClassLoader(classLoader); createMainMethodRunner(mainClass, args, classLoader).run(); } // ... }
因为是 Fat Jar 的模式(Jar in Jar),因此需要自己实现 LaunchedURLClassLoader 来定制类的查找逻辑。
Tomcat
Tomcat 可以同时加载多个 web 应用,并且可以热加载,支持动态加载或者卸载 web 应用。大概的实现思路
Tomcat 自身的代码会有一个 ClassLoader
Tomcat 为每个 Web 应用创建一个 org.apache.catalina.loader.WebappClassLoader 加载 Web 应用自己的类,且 parent 会指向 Tomcat 自身的类加载器
object.getClass().getProtectionDomain().getCodeSource() / clazz.getProtectionDomain().getCodeSource() 可以查看某个类加载的路径(如果两个 Jar 包都有相同的类,可以看到最终是加载了哪个 Jar 包的类),判断是否符合预期
// A Klass provides: // 1: language level class object (method dictionary etc.) // 2: provide vm dispatch behavior for the object // Both functions are combined into one C++ class. // ... classKlass : public Metadata { // ... }