目录
-
Java程序运行和类加载流程图
-
双亲委派流程图
-
双亲委派作用
-
打破双亲委派机制(重写`loadClass`方法)
环境说明
OpenJDK 版本:
jdk8-b119
阅读源码软件:Understand(个人推荐,挺好用)
画图软件:ProcessOn
IDE:IDEA
checkAndLoadMain
Java程序的运行流程
通过使用java
命令运行时,java
程序调用库函数创建虚拟机,并加载主类,执行main
方法后开始执行。
查看源码文件,找到java.h
,截取部分源码如下:
/*
* Initialize platform specific settings
*/
void InitLauncher(jboolean javaw);
/*
* For MacOSX and Windows/Unix compatibility we require these
* entry points, some of them may be stubbed out on Windows/Unixes.
*/
void PostJVMInit(JNIEnv *env, jstring mainClass, JavaVM *vm);
void ShowSplashScreen();
void RegisterThread();
/*
* this method performs additional platform specific processing and
* should return JNI_TRUE to indicate the argument has been consumed,
* otherwise returns JNI_FALSE to allow the calling logic to further
* process the option.
*/
jboolean ProcessPlatformOption(const char *arg);
/*
* This allows for finding classes from the VM's bootstrap class loader directly,
* FindClass uses the application class loader internally, this will cause
* unnecessary searching of the classpath for the required classes.
*
*/
typedef jclass (JNICALL FindClassFromBootLoader_t(JNIEnv *env,
const char *name));
jclass FindBootStrapClass(JNIEnv *env, const char *classname);
jobjectArray CreateApplicationArgs(JNIEnv *env, char **strv, int argc);
jobjectArray NewPlatformStringArray(JNIEnv *env, char **strv, int strc);
jclass GetLauncherHelperClass(JNIEnv *env);
int JNICALL JavaMain(void * args); /* entry point 程序入口
int JNICALL JavaMain(void * args);
根据注释,这行应该很关键,在java.c
文件中查看实现,截取部分源码如下:
int JNICALL
JavaMain(void * _args)
{
JavaMainArgs *args = (JavaMainArgs *)_args;
int argc = args->argc;
char **argv = args->argv;
int mode = args->mode;
char *what = args->what;
InvocationFunctions ifn = args->ifn;
JavaVM *vm = 0;
JNIEnv *env = 0;
jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start, end;
RegisterThread();
/* 初始化JVM虚拟机 */
/* Initialize the virtual machine */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) {
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1);
}
/* 省略部分代码 */
/*
* Get the application's main class.
*
* See bugid 5030265. The Main-Class name has already been parsed
* from the manifest, but not parsed properly for UTF-8 support.
* Hence the code here ignores the value previously extracted and
* uses the pre-existing code to reextract the value. This is
* possibly an end of release cycle expedient. However, it has
* also been discovered that passing some character sets through
* the environment has "strange" behavior on some variants of
* Windows. Hence, maybe the manifest parsing code local to the
* launcher should never be enhanced.
*
* Hence, future work should either:
* 1) Correct the local parsing code and verify that the
* Main-Class attribute gets properly passed through
* all environments,
* 2) Remove the vestages of maintaining main_class through
* the environment (and remove these comments).
*
* This method also correctly handles launching existing JavaFX
* applications that may or may not have a Main-Class manifest entry.
*/
mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
/*
* In some cases when launching an application that needs a helper, e.g., a
* JavaFX application with no main method, the mainClass will not be the
* applications own main class but rather a helper class. To keep things
* consistent in the UI we need to track and report the application main class.
*/
appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass, -1);
/*
* PostJVMInit uses the class name as the application name for GUI purposes,
* for example, on OSX this sets the application name in the menu bar for
* both SWT and JavaFX. So we'll pass the actual application class here
* instead of mainClass as that may be a launcher or helper class instead
* of the application class.
*/
PostJVMInit(env, appClass, vm);
/*
* The LoadMainClass not only loads the main class, it will also ensure
* that the main method's signature is correct, therefore further checking
* is not required. The main method is invoked here so that extraneous java
* stacks are not in the application stack trace.
*/
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* Build platform specific argument array */
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
/*
* The launcher's exit code (in the absence of calls to
* System.exit) will be non-zero if main threw an exception.
*/
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE();
}
可以看到,通过InitializeJVM(&vm, &env, &ifn)
初始化虚拟机后,调用mainClass = LoadMainClass(env, mode, what);
加载到主类,通过mainID = (*env)->GetStaticMethodID(env, mainClass, "main","([Ljava/lang/String;)V");
获取到mainId
后执行 (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
就可以执行Java主类的main
方法。
以后可以深入了解下虚拟机初始化流程。
类加载流程
加载主类方法static jclass LoadMainClass(JNIEnv *env, int mode, char *name)
/*
* Loads a class and verifies that the main class is present and it is ok to
* call it for more details refer to the java implementation.
*/
static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
jmethodID mid;
jstring str;
jobject result;
jlong start, end;
// 获取启动帮助类
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));
str = NewPlatformString(env, name);
result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str);
if (JLI_IsTraceLauncher()) {
end = CounterGet();
printf("%ld micro seconds to load main classn",
(long)(jint)Counter2Micros(end-start));
printf("----%s----n", JLDEBUG_ENV_ENTRY);
}
return (jclass)result;
}
查看jclass GetLauncherHelperClass(JNIEnv *env)
源码:
jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
// 查找启动类加载器并加载sun/launcher/LauncherHelper
NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper"));
}
return helperClass;
}
深入查看下FindBootStrapClass(JNIEnv *env, const char *classname)
方法:
/*
* The implementation for finding classes from the bootstrap
* class loader, refer to java.h
*/
static FindClassFromBootLoader_t *findBootClass = NULL;
jclass FindBootStrapClass(JNIEnv *env, const char *classname)
{
HMODULE hJvm;
if (findBootClass == NULL) {
hJvm = GetModuleHandle(JVM_DLL);
if (hJvm == NULL) return NULL;
/* need to use the demangled entry point */
findBootClass = (FindClassFromBootLoader_t *)GetProcAddress(hJvm,
"JVM_FindClassFromBootLoader");
if (findBootClass == NULL) {
JLI_ReportErrorMessage(DLL_ERROR4, "JVM_FindClassFromBootLoader");
return NULL;
}
}
return findBootClass(env, classname);
}
可以看到该方法是通过bootstrap class loader
启动类加载器去加载类,上面FindBootStrapClass(env,"sun/launcher/LauncherHelper")
的调用也就是通过启动类加载器去加载一个名为LauncherHelper
的类。
加载到这个LauncherHelper
类之后,执行
// 获取启动帮助类
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));
进入到Java方法,调用名为checkAndLoadMain
的方法。
在Java源码中找到这个类,看一下方法实现:
/**
* This method does the following:
* 1. gets the classname from a Jar's manifest, if necessary
* 2. loads the class using the System ClassLoader
* 3. ensures the availability and accessibility of the main method,
* using signatureDiagnostic method.
* a. does the class exist
* b. is there a main
* c. is the main public
* d. is the main static
* e. does the main take a String array for args
* 4. if no main method and if the class extends FX Application, then call
* on FXHelper to determine the main class to launch
* 5. and off we go......
*
* @param printToStderr if set, all output will be routed to stderr
* @param mode LaunchMode as determined by the arguments passed on the
* command line
* @param what either the jar file to launch or the main class when using
* LM_CLASS mode
* @return the application's main class
*/
public static Class<?> checkAndLoadMain(boolean printToStderr,
int mode,
String what) {
initOutput(printToStderr);
// get the class name
String cn = null;
switch (mode) {
case LM_CLASS:
cn = what;
break;
case LM_JAR:
cn = getMainClassFromJar(what);
break;
default:
// should never happen
throw new InternalError("" + mode + ": Unknown launch mode");
}
cn = cn.replace('/', '.');
Class<?> mainClass = null;
try {
mainClass = scloader.loadClass(cn);
} catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
if (System.getProperty("os.name", "").contains("OS X")
&& Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {
try {
// On Mac OS X since all names with diacretic symbols are given as decomposed it
// is possible that main class name comes incorrectly from the command line
// and we have to re-compose it
// 加载主类
mainClass = scloader.loadClass(Normalizer.normalize(cn, Normalizer.Form.NFC));
} catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {
abort(cnfe, "java.launcher.cls.error1", cn);
}
} else {
abort(cnfe, "java.launcher.cls.error1", cn);
}
}
// set to mainClass
appClass = mainClass;
/*
* Check if FXHelper can launch it using the FX launcher. In an FX app,
* the main class may or may not have a main method, so do this before
* validating the main class.
*/
if (mainClass.equals(FXHelper.class) ||
FXHelper.doesExtendFXApplication(mainClass)) {
// Will abort() if there are problems with the FX runtime
FXHelper.setFXLaunchParameters(what, mode);
return FXHelper.class;
}
validateMainClass(mainClass);
return mainClass;
}
加载主类的方法:mainClass = scloader.loadClass(Normalizer.normalize(cn, Normalizer.Form.NFC));
,查看一下成员变量:scloader
定义初始化:private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
源码:
/**
* Returns the system class loader for delegation. This is the default
* delegation parent for new <tt>ClassLoader</tt> instances, and is
* typically the class loader used to start the application.
*
* <p> This method is first invoked early in the runtime's startup
* sequence, at which point it creates the system class loader and sets it
* as the context class loader of the invoking <tt>Thread</tt>.
*
* <p> The default system class loader is an implementation-dependent
* instance of this class.
*
* <p> If the system property "<tt>java.system.class.loader</tt>" is defined
* when this method is first invoked then the value of that property is
* taken to be the name of a class that will be returned as the system
* class loader. The class is loaded using the default system class loader
* and must define a public constructor that takes a single parameter of
* type <tt>ClassLoader</tt> which is used as the delegation parent. An
* instance is then created using this constructor with the default system
* class loader as the parameter. The resulting class loader is defined
* to be the system class loader.
*
* <p> If a security manager is present, and the invoker's class loader is
* not <tt>null</tt> and the invoker's class loader is not the same as or
* an ancestor of the system class loader, then this method invokes the
* security manager's {@link
* SecurityManager#checkPermission(java.security.Permission)
* <tt>checkPermission</tt>} method with a {@link
* RuntimePermission#RuntimePermission(String)
* <tt>RuntimePermission("getClassLoader")</tt>} permission to verify
* access to the system class loader. If not, a
* <tt>SecurityException</tt> will be thrown. </p>
*
* @return The system <tt>ClassLoader</tt> for delegation, or
* <tt>null</tt> if none
*
* @throws SecurityException
* If a security manager exists and its <tt>checkPermission</tt>
* method doesn't allow access to the system class loader.
*
* @throws IllegalStateException
* If invoked recursively during the construction of the class
* loader specified by the "<tt>java.system.class.loader</tt>"
* property.
*
* @throws Error
* If the system property "<tt>java.system.class.loader</tt>"
* is defined but the named class could not be loaded, the
* provider class does not define the required constructor, or an
* exception is thrown by that constructor when it is invoked. The
* underlying cause of the error can be retrieved via the
* {@link Throwable#getCause()} method.
*
* @revised 1.4
*/
@CallerSensitive
public static ClassLoader getSystemClassLoader() {
// 初始化系统类加载
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
查看initSystemClassLoader
方法
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
// 获取启动器
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
查看sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
源码:
/**
* This class is used by the system to launch the main application.
Launcher */
public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher();
private static String bootClassPath =
System.getProperty("sun.boot.class.path");
public static Launcher getLauncher() {
return launcher;
}
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
// 获取扩展类加载器
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"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) {
throw new InternalError(
"Could not create application class loader", e);
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
// Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
if (s != null) {
SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
sm = new java.lang.SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
}
/*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader() {
return loader;
}
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
/**
* create an ExtClassLoader. The ExtClassLoader is created
* within a context that limits which files it can read
*/
public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();
try {
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext().
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
int len = dirs.length;
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
return new ExtClassLoader(dirs);
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
void addExtURL(URL url) {
super.addURL(url);
}
/*
* Creates a new ExtClassLoader for the specified directories.
*/
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}
private static File[] getExtDirs() {
String s = System.getProperty("java.ext.dirs");
File[] dirs;
if (s != null) {
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator);
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs;
}
}
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
// Note: on bugid 4256530
// Prior implementations of this doPrivileged() block supplied
// a rather restrictive ACC via a call to the private method
// AppClassLoader.getContext(). This proved overly restrictive
// when loading classes. Specifically it prevent
// accessClassInPackage.sun.* grants from being honored.
//
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}
/*
* Creates a new AppClassLoader
*/
AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory);
}
/**
* Override loadClass so we can checkPackageAccess.
*/
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
int i = name.lastIndexOf('.');
if (i != -1) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPackageAccess(name.substring(0, i));
}
}
return (super.loadClass(name, resolve));
}
}
}
可以看到Launcher()
构造方法初始化了扩展类加载器和应用类加载器,通过构造器构造父子关系:
private ClassLoader(Void unused, ClassLoader parent) {
// 通过属性parent构造父子关系
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
回到mainClass = scloader.loadClass(cn);
方法加载类。
跟踪到ClassLoader
类查看loadClass
方法:
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) {
long t0 = 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.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
双亲委派机制的关键代码:
// 首先检查当前类加载器是否能加载到name类
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
// 如果父 类加载器不为空
if (parent != null) {
// 调用父 类加载的loadclass加载类
c = parent.loadClass(name, false);
} else {
// 查找引导类加载器
c = findBootstrapClassOrNull(name);
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 加载类
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
总结
Java程序运行和类加载流程图
双亲委派流程图
双亲委派作用
-
沙箱安全机制:防止核心类库被篡改 -
避免类的重复加载:当父 类加载器已经加载了该类,则子 类加载器不会重复加载
打破双亲委派机制(重写loadClass
方法)
如果需要做版本隔离、环境隔离,则需要搭配双亲委派机制,比如Tomcat,可以运行多个版本的Spring Boot等类。
参考文档
-
Runtime-JVM的启动流程与main方法调用
原文始发于微信公众号(erpang coding):Java类加载机制
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/37340.html