本文主要是介绍JavaClassLoader源码分析(中),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
ClassLoader的属性
说明:ClassLoader的属性与其内部类是穿插着创建的,这里写在一起了
private static native void registerNatives();//将ClassLoader类中所有native修饰的方法与C 语言描述的方法对应上相当于做了一次映射//也侧面反映了一次编写可以处处运行的一种道理,同时相当于基于java与其他语言做了一个适配static {registerNatives();}// The parent class loader for delegation// Note: VM hardcoded the offset of this field, thus all new fields// must be added *after* it.// 这个属性被JVM硬编码到类的第一个位置,标识委托的父类加载器// 这么做的原因可能是为了方便初始化ClassLoader对象的时候先初始化自己,然后自己再初始化自己拥有的属性,保障加载顺序private final ClassLoader parent;/*** Encapsulates the set of parallel capable loader types.*///这个内部类提供了并行加载类的特性private static class ParallelLoaders {private ParallelLoaders() {}// the set of parallel capable loader typesprivate static final Set<Class<? extends ClassLoader>> loaderTypes =Collections.newSetFromMap(new WeakHashMap<Class<? extends ClassLoader>, Boolean>());static {synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); }}/*** Registers the given class loader type as parallel capabale.* Returns {@code true} is successfully registered; {@code false} if* loader's super class is not registered.*/static boolean register(Class<? extends ClassLoader> c) {synchronized (loaderTypes) {if (loaderTypes.contains(c.getSuperclass())) {// register the class loader as parallel capable// if and only if all of its super classes are.// Note: given current classloading sequence, if// the immediate super class is parallel capable,// all the super classes higher up must be too.loaderTypes.add(c);return true;} else {return false;}}}/*** Returns {@code true} if the given class loader type is* registered as parallel capable.*/static boolean isRegistered(Class<? extends ClassLoader> c) {synchronized (loaderTypes) {return loaderTypes.contains(c);}}}// Maps class name to the corresponding lock object when the current// class loader is parallel capable.// Note: VM also uses this field to decide if the current class loader// is parallel capable and the appropriate lock object for class loading.//并行加载的时候为了保障不会重复加载使用下面的属性标识加载指定类的时候进行//锁定private final ConcurrentHashMap<String, Object> parallelLockMap;//这个属性跟TSL有关,也跟JDK版本有关private final Map <String, Certificate[]> package2certs;// Shared among all packages with unsigned classes// 所有的类都有指定的证书private static final Certificate[] nocerts = new Certificate[0];// The classes loaded by this class loader. The only purpose of this table// is to keep the classes from being GC'ed until the loader is GC'ed.////当前类加载加载的类向量集合,如果类加载器被回收的话这里面的类也会被回收private final Vector<Class<?>> classes = new Vector<>();// The "default" domain. Set as the default ProtectionDomain on newly// created classes.// 默认的类的访问级别private final ProtectionDomain defaultDomain =new ProtectionDomain(new CodeSource(null, (Certificate[]) null),null, this, null);// The initiating protection domains for all classes loaded by this loader//访问级别集合private final Set<ProtectionDomain> domains;// Invoked by the VM to record every loaded class with this loader.//这里没有被显示调用void addClass(Class c) {classes.addElement(c);}// The packages defined in this class loader. Each package name is mapped// to its corresponding Package object.// @GuardedBy("itself")// 这里其实对java类中的包结构做了一次抽象,封装了Package对象,这个对象也是被类加载器所加载的,private final HashMap<String, Package> packages = new HashMap<>();// The class loader for the system// @GuardedBy("ClassLoader.class")//这里定义这个属性作为系统类加载器private static ClassLoader scl;// Set to true once the system class loader has been set// @GuardedBy("ClassLoader.class")//当系统类加载器初始化之后设置初始化状态private static boolean sclSet;// All native library names we've loaded.//已经加载的本地lib列表private static Vector<String> loadedLibraryNames = new Vector<>();// Native libraries belonging to system classes.// 本地加载lib列表 属于系统类的private static Vector<NativeLibrary> systemNativeLibraries= new Vector<>();// Native libraries associated with the class loader.//与当前类加载器相关的本地libprivate Vector<NativeLibrary> nativeLibraries = new Vector<>();// native libraries being loaded/unloaded.//已经加载或者没加载的本地libprivate static Stack<NativeLibrary> nativeLibraryContext = new Stack<>();// The paths searched for libraries//搜索的lib目录private static String usr_paths[];private static String sys_paths[];// -- Assertion management --//定义一个对象作为断言锁,后续会讲到final Object assertionLock;// The default toggle for assertion checking.// @GuardedBy("assertionLock")//默认断言状态private boolean defaultAssertionStatus = false;// Maps String packageName to Boolean package default assertion status Note// that the default package is placed under a null map key. If this field// is null then we are delegating assertion status queries to the VM, i.e.,// none of this ClassLoader's assertion status modification methods have// been invoked.// @GuardedBy("assertionLock")//加载包的时候进行断言加锁private Map<String, Boolean> packageAssertionStatus = null;// Maps String fullyQualifiedClassName to Boolean assertionStatus If this// field is null then we are delegating assertion status queries to the VM,// i.e., none of this ClassLoader's assertion status modification methods// have been invoked.// @GuardedBy("assertionLock")//加载包的时候进行断言加锁,相当于验证是否加载了包,Map<String, Boolean> classAssertionStatus = null;
ClassLoader的构造方法
private ClassLoader(Void unused, ClassLoader 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;}//这里的构造方法主要操作有两个//1.设置当前类加载的父类加载器//2.根据是否注册了并行类加载容器初始化对应的容器}/*** Creates a new class loader using the specified parent class loader for* delegation.** <p> If there is a security manager, its {@link* SecurityManager#checkCreateClassLoader()* <tt>checkCreateClassLoader</tt>} method is invoked. This may result in* a security exception. </p>** @param parent* The parent class loader** @throws SecurityException* If a security manager exists and its* <tt>checkCreateClassLoader</tt> method doesn't allow creation* of a new class loader.** @since 1.2*/protected ClassLoader(ClassLoader parent) {this(checkCreateClassLoader(), parent);}/*** Creates a new class loader using the <tt>ClassLoader</tt> returned by* the method {@link #getSystemClassLoader()* <tt>getSystemClassLoader()</tt>} as the parent class loader.** <p> If there is a security manager, its {@link* SecurityManager#checkCreateClassLoader()* <tt>checkCreateClassLoader</tt>} method is invoked. This may result in* a security exception. </p>** @throws SecurityException* If a security manager exists and its* <tt>checkCreateClassLoader</tt> method doesn't allow creation* of a new class loader.*/protected ClassLoader() {this(checkCreateClassLoader(), getSystemClassLoader());}
ClassLoader执行类加载的过程
/*** Loads the class with the specified <a href="#name">binary name</a>.* This method searches for classes in the same manner as the {@link* #loadClass(String, boolean)} method. It is invoked by the Java virtual* machine to resolve class references. Invoking this method is equivalent* to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,* false)</tt>}. </p>** @param name* The <a href="#name">binary name</a> of the class** @return The resulting <tt>Class</tt> object** @throws ClassNotFoundException* If the class was not found*///定义一个重载方法,这里的加载其实也就是创建Class实例的意思public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}/*** Loads the class with the specified <a href="#name">binary name</a>. The* default implementation of this method searches for classes in the* following order:** <p><ol>** <li><p> Invoke {@link #findLoadedClass(String)} to check if the class* has already been loaded. </p></li>** <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method* on the parent class loader. If the parent is <tt>null</tt> the class* loader built-in to the virtual machine is used, instead. </p></li>** <li><p> Invoke the {@link #findClass(String)} method to find the* class. </p></li>** </ol>** <p> If the class was found using the above steps, and the* <tt>resolve</tt> flag is true, this method will then invoke the {@link* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.** <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link* #findClass(String)}, rather than this method. </p>** <p> Unless overridden, this method synchronizes on the result of* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method* during the entire class loading process.** @param name* The <a href="#name">binary name</a> of the class** @param resolve* If <tt>true</tt> then resolve the class** @return The resulting <tt>Class</tt> object** @throws ClassNotFoundException* If the class could not be found*///看方法的修饰符,protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{//先根据方法名判断是否已经有类加载器在加载这个类了,//这里有两层含义1.synchronized标明这里的代码区是有并发安全问题的//意思是不能有多个线程同时执行类加载,否则会导致出现同一个类被加载多次//2.第二层含义就是下面的方法,下面的方法是classLoader提供的并行加载机制,并行加载的时候也需要//保证同一个类不能被加载多次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();//这里如果还么有找到自己才进行类加载,也就是说双亲委派模式下没有找到类,就自己加载,自己找类//这个方法在ClassLoader中是没有实现的,所以如果父类没有找到子类必须通过这个方法找类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();}}//resolve 这里如果是true,进行类的链接(验证,准备,解析)if (resolve) {resolveClass(c);}return c;}}/*** Returns the lock object for class loading operations.* For backward compatibility, the default implementation of this method* behaves as follows. If this ClassLoader object is registered as* parallel capable, the method returns a dedicated object associated* with the specified class name. Otherwise, the method returns this* ClassLoader object. </p>** @param className* The name of the to-be-loaded class** @return the lock for class loading operations** @throws NullPointerException* If registered as parallel capable and <tt>className</tt> is null** @see #loadClass(String, boolean)** @since 1.7*///这里使用并发安全容器类实现类加载过程的锁机制,保证线程安全protected Object getClassLoadingLock(String className) {Object lock = this;if (parallelLockMap != null) {Object newLock = new Object();lock = parallelLockMap.putIfAbsent(className, newLock);if (lock == null) {lock = newLock;}}return lock;}
ClassLoader查找类的过程
说明:这里查找类的过程是由JVM底层实现的,比较能看懂,不做解析
// return null if not foundprivate native Class findBootstrapClass(String name);/*** Returns the class with the given <a href="#name">binary name</a> if this* loader has been recorded by the Java virtual machine as an initiating* loader of a class with that <a href="#name">binary name</a>. Otherwise* <tt>null</tt> is returned. </p>** @param name* The <a href="#name">binary name</a> of the class** @return The <tt>Class</tt> object, or <tt>null</tt> if the class has* not been loaded** @since 1.1*/protected final Class<?> findLoadedClass(String name) {if (!checkName(name))return null;return findLoadedClass0(name);}private native final Class findLoadedClass0(String name);/*** Links the specified class. This (misleadingly named) method may be* used by a class loader to link a class. If the class <tt>c</tt> has* already been linked, then this method simply returns. Otherwise, the* class is linked as described in the "Execution" chapter of* <cite>The Java™ Language Specification</cite>.* </p>** @param c* The class to link** @throws NullPointerException* If <tt>c</tt> is <tt>null</tt>.** @see #defineClass(String, byte[], int, int)*/protected final void resolveClass(Class<?> c) {resolveClass0(c);}private native void resolveClass0(Class c);
ClassLoader定义包的过程
说明:定义类之前先定义包,这里的包的意思跟命名空间类似,可以搜一下java中的命名空间
/*** Defines a package by name in this <tt>ClassLoader</tt>. This allows* class loaders to define the packages for their classes. Packages must* be created before the class is defined, and package names must be* unique within a class loader and cannot be redefined or changed once* created. </p>** @param name* The package name** @param specTitle* The specification title** @param specVersion* The specification version** @param specVendor* The specification vendor** @param implTitle* The implementation title** @param implVersion* The implementation version** @param implVendor* The implementation vendor** @param sealBase* If not <tt>null</tt>, then this package is sealed with* respect to the given code source {@link java.net.URL* <tt>URL</tt>} object. Otherwise, the package is not sealed.** @return The newly defined <tt>Package</tt> object** @throws IllegalArgumentException* If package name duplicates an existing package either in this* class loader or one of its ancestors** @since 1.2*/protected Package definePackage(String name, String specTitle,String specVersion, String specVendor,String implTitle, String implVersion,String implVendor, URL sealBase)throws IllegalArgumentException{synchronized (packages) {Package pkg = getPackage(name);if (pkg != null) {throw new IllegalArgumentException(name);}pkg = new Package(name, specTitle, specVersion, specVendor,implTitle, implVersion, implVendor,sealBase, this);packages.put(name, pkg);return pkg;}}/*** Returns a <tt>Package</tt> that has been defined by this class loader* or any of its ancestors. </p>** @param name* The package name** @return The <tt>Package</tt> corresponding to the given name, or* <tt>null</tt> if not found** @since 1.2*/protected Package getPackage(String name) {Package pkg;synchronized (packages) {pkg = packages.get(name);}if (pkg == null) {if (parent != null) {pkg = parent.getPackage(name);} else {pkg = Package.getSystemPackage(name);}if (pkg != null) {synchronized (packages) {Package pkg2 = packages.get(name);if (pkg2 == null) {packages.put(name, pkg);} else {pkg = pkg2;}}}}return pkg;}/*** Returns all of the <tt>Packages</tt> defined by this class loader and* its ancestors. </p>** @return The array of <tt>Package</tt> objects defined by this* <tt>ClassLoader</tt>** @since 1.2*/protected Package[] getPackages() {Map<String, Package> map;synchronized (packages) {map = new HashMap<>(packages);}Package[] pkgs;if (parent != null) {pkgs = parent.getPackages();} else {pkgs = Package.getSystemPackages();}if (pkgs != null) {for (int i = 0; i < pkgs.length; i ) {String pkgName = pkgs[i].getName();if (map.get(pkgName) == null) {map.put(pkgName, pkgs[i]);}}}return map.values().toArray(new Package[map.size()]);}
ClassLoader定义/创建类的过程
说明:这里的一些方法是类加载的一些核心内容
/*** Converts an array of bytes into an instance of class <tt>Class</tt>.* Before the <tt>Class</tt> can be used it must be resolved. This method* is deprecated in favor of the version that takes a <a* href="#name">binary name</a> as its first argument, and is more secure.** @param b* The bytes that make up the class data. The bytes in positions* <tt>off</tt> through <tt>off len-1</tt> should have the format* of a valid class file as defined by* <cite>The Java™ Virtual Machine Specification</cite>.** @param off* The start offset in <tt>b</tt> of the class data** @param len* The length of the class data** @return The <tt>Class</tt> object that was created from the specified* class data** @throws ClassFormatError* If the data did not contain a valid class** @throws IndexOutOfBoundsException* If either <tt>off</tt> or <tt>len</tt> is negative, or if* <tt>off len</tt> is greater than <tt>b.length</tt>.** @throws SecurityException* If an attempt is made to add this class to a package that* contains classes that were signed by a different set of* certificates than this class, or if an attempt is made* to define a class in a package with a fully-qualified name* that starts with "{@code java.}".** @see #loadClass(String, boolean)* @see #resolveClass(Class)** @deprecated Replaced by {@link #defineClass(String, byte[], int, int)* defineClass(String, byte[], int, int)}*///注意这里的方法就是类加载过程中链接阶段要做的事情@Deprecatedprotected final Class<?> defineClass(byte[] b, int off, int len)throws ClassFormatError{return defineClass(null, b, off, len, null);}/*** Converts an array of bytes into an instance of class <tt>Class</tt>.* Before the <tt>Class</tt> can be used it must be resolved.** <p> This method assigns a default {@link java.security.ProtectionDomain* <tt>ProtectionDomain</tt>} to the newly defined class. The* <tt>ProtectionDomain</tt> is effectively granted the same set of* permissions returned when {@link* java.security.Policy#getPermissions(java.security.CodeSource)* <tt>Policy.getPolicy().getPermissions(new CodeSource(null, null))</tt>}* is invoked. The default domain is created on the first invocation of* {@link #defineClass(String, byte[], int, int) <tt>defineClass</tt>},* and re-used on subsequent invocations.** <p> To assign a specific <tt>ProtectionDomain</tt> to the class, use* the {@link #defineClass(String, byte[], int, int,* java.security.ProtectionDomain) <tt>defineClass</tt>} method that takes a* <tt>ProtectionDomain</tt> as one of its arguments. </p>** @param name* The expected <a href="#name">binary name</a> of the class, or* <tt>null</tt> if not known** @param b* The bytes that make up the class data. The bytes in positions* <tt>off</tt> through <tt>off len-1</tt> should have the format* of a valid class file as defined by* <cite>The Java™ Virtual Machine Specification</cite>.** @param off* The start offset in <tt>b</tt> of the class data** @param len* The length of the class data** @return The <tt>Class</tt> object that was created from the specified* class data.** @throws ClassFormatError* If the data did not contain a valid class** @throws IndexOutOfBoundsException* If either <tt>off</tt> or <tt>len</tt> is negative, or if* <tt>off len</tt> is greater than <tt>b.length</tt>.** @throws SecurityException* If an attempt is made to add this class to a package that* contains classes that were signed by a different set of* certificates than this class (which is unsigned), or if* <tt>name</tt> begins with "<tt>java.</tt>".** @see #loadClass(String, boolean)* @see #resolveClass(Class)* @see java.security.CodeSource* @see java.security.SecureClassLoader** @since 1.1*/protected final Class<?> defineClass(String name, byte[] b, int off, int len)throws ClassFormatError{return defineClass(name, b, off, len, null);}/*** Converts an array of bytes into an instance of class <tt>Class</tt>,* with an optional <tt>ProtectionDomain</tt>. If the domain is* <tt>null</tt>, then a default domain will be assigned to the class as* specified in the documentation for {@link #defineClass(String, byte[],* int, int)}. Before the class can be used it must be resolved.** <p> The first class defined in a package determines the exact set of* certificates that all subsequent classes defined in that package must* contain. The set of certificates for a class is obtained from the* {@link java.security.CodeSource <tt>CodeSource</tt>} within the* <tt>ProtectionDomain</tt> of the class. Any classes added to that* package must contain the same set of certificates or a* <tt>SecurityException</tt> will be thrown. Note that if* <tt>name</tt> is <tt>null</tt>, this check is not performed.* You should always pass in the <a href="#name">binary name</a> of the* class you are defining as well as the bytes. This ensures that the* class you are defining is indeed the class you think it is.** <p> The specified <tt>name</tt> cannot begin with "<tt>java.</tt>", since* all classes in the "<tt>java.*</tt> packages can only be defined by the* bootstrap class loader. If <tt>name</tt> is not <tt>null</tt>, it* must be equal to the <a href="#name">binary name</a> of the class* specified by the byte array "<tt>b</tt>", otherwise a {@link* <tt>NoClassDefFoundError</tt>} will be thrown. </p>** @param name* The expected <a href="#name">binary name</a> of the class, or* <tt>null</tt> if not known** @param b* The bytes that make up the class data. The bytes in positions* <tt>off</tt> through <tt>off len-1</tt> should have the format* of a valid class file as defined by* <cite>The Java™ Virtual Machine Specification</cite>.** @param off* The start offset in <tt>b</tt> of the class data** @param len* The length of the class data** @param protectionDomain* The ProtectionDomain of the class** @return The <tt>Class</tt> object created from the data,* and optional <tt>ProtectionDomain</tt>.** @throws ClassFormatError* If the data did not contain a valid class** @throws NoClassDefFoundError* If <tt>name</tt> is not equal to the <a href="#name">binary* name</a> of the class specified by <tt>b</tt>** @throws IndexOutOfBoundsException* If either <tt>off</tt> or <tt>len</tt> is negative, or if* <tt>off len</tt> is greater than <tt>b.length</tt>.** @throws SecurityException* If an attempt is made to add this class to a package that* contains classes that were signed by a different set of* certificates than this class, or if <tt>name</tt> begins with* "<tt>java.</tt>".*/protected final Class<?> defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)throws ClassFormatError{//1.链接过程中的第一步1.1验证protectionDomain = preDefineClass(name, protectionDomain);Class c = null;//1.链接过程中的第二步1.2准备,这里就是加载class字节码了String source = defineClassSourceLocation(protectionDomain);//1.链接过程中的第三步1.3解析,这里就是通过native方法进行具体的解析了try {c = defineClass1(name, b, off, len, protectionDomain, source);} catch (ClassFormatError cfe) {c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,source);}//对类进行签名postDefineClass(c, protectionDomain);return c;}/* Determine protection domain, and check that:- not define java.* class,- signer of this class matches signers for the rest of the classes inpackage.*/private ProtectionDomain preDefineClass(String name,ProtectionDomain pd){if (!checkName(name))throw new NoClassDefFoundError("IllegalName: " name);if ((name != null) && name.startsWith("java.")) {throw new SecurityException("Prohibited package name: " name.substring(0, name.lastIndexOf('.')));}if (pd == null) {pd = defaultDomain;}if (name != null) checkCerts(name, pd.getCodeSource());return pd;}private String defineClassSourceLocation(ProtectionDomain pd){CodeSource cs = pd.getCodeSource();String source = null;if (cs != null && cs.getLocation() != null) {source = cs.getLocation().toString();}return source;}private Class defineTransformedClass(String name, byte[] b, int off, int len,ProtectionDomain pd,ClassFormatError cfe, String source)throws ClassFormatError{// Class format error - try to transform the bytecode and// define the class again//ClassFileTransformer[] transformers =ClassFileTransformer.getTransformers();Class c = null;if (transformers != null) {for (ClassFileTransformer transformer : transformers) {try {// Transform byte code using transformerbyte[] tb = transformer.transform(b, off, len);c = defineClass1(name, tb, 0, tb.length,pd, source);break;} catch (ClassFormatError cfe2) {// If ClassFormatError occurs, try next transformer}}}// Rethrow original ClassFormatError if unable to transform// bytecode to well-formed//if (c == null)throw cfe;return c;}private void postDefineClass(Class c, ProtectionDomain pd){if (pd.getCodeSource() != null) {Certificate certs[] = pd.getCodeSource().getCertificates();if (certs != null)setSigners(c, certs);}}
ClassLoader总结
- ClassLoader展现了双亲委派模式的实现机制
- ClassLoader实现了大概的类加载过程(加载和链接阶段)
- ClassLoader在加载过程中涉及到了安全管理,类签名,lib扫描,包定义,类的访问权限,并发加载的安全性
- ClassLoader源码总共分为下面5大部分,由于是偏底层的类,所以代码会紧凑点,不好阅读,但是比较稳定
1.Class 的定义和加载
2.Hierarchy 类的层次结构
3.Resource 定义和加载
4.Native library access (lib包访问)
5.Package 定义
上面只是根据ClassLoader源码做了一些简单的解析,更具体的可以看下面的链接:https://blog.csdn.net/m0_38075425/article/details/81627349https://www.cnblogs.com/czwbig/p/11127222.html
架构设计@工程设计@服务稳定性之路
这篇关于JavaClassLoader源码分析(中)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!