目录
虚拟机的类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
Java类加载器
Java类加载器(Java Classloader)负责动态地将Java类加载到Java虚拟机的内存空间内, 是Java运行时环境(Java Runtime Environment)的一部分,JVM默认有3个类加载器,每个类加载器负责加载特定位置的Java类:
启动类加载器
1、Bootstrap ClassLoader(引导类或启动类加载器):该类加载器通常由C++语言实现,不继承任何Java类,负责加载System.getProperty("sun.boot.class.path")所指定核心Java库,也可以通过java -Xbootclasspath指定其搜索路径;
public class Test { public static void main(String[] args) { System.out.println(System.getProperty("sun.boot.class.path")); } }
结果:
D:\Program_Files\Java\jdk1.8.0_131\jre\lib\resources.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\rt.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\sunrsasign.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\jsse.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\jce.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\charsets.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\jfr.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\classes
扩展类加载器
2、ExtClassLoader(扩展类加载器):该类加载器由sun.misc.Launcher$ExtClassLoader类实现,负责加载System.getProperty("java.ext.dirs")所指定的Java的扩展库,也可以通过java -Djava.ext.dirs指定其搜索路径,例如:java -Djava.ext.dirs=d:\classes HelloWorld;注意:如果将自己开发的 jar 文件放在System.getProperty("java.ext.dirs")所指定的目录中,也会被 ExtClassLoader类加载器加载;
public class Test { public static void main(String[] args) { System.out.println(System.getProperty("java.ext.dirs")); } }
结果:
D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext; C:\Windows\Sun\Java\lib\ext
应用程序类加载器
3、AppClassLoader(系统类或应用程序类加载器):该类加载器由sun.misc.Launcher$AppClassLoader类实现,负责加载System.getProperty("java.class.path")或CLASSPATH环境变量所指定的Java类,也可以加上-cp来覆盖原有的classpath设置,例如: java -cp ./classes HelloWorld;说明:默认情况下自定义类都由该类加载器加载。如果应用程序中没有自定义的类加载器,一般情况下这就是程序中默认的类加载器。
public class Test { public static void main(String[] args) { System.out.println(System.getProperty("java.class.path")); } }
结果:
D:\Program_Files\Java\jdk1.8.0_131\jre\lib\resources.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\rt.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\jsse.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\jce.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\charsets.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\jfr.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-32.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar; D:\Program_Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar; D:\workspace\test\bin ****自命名类所存在的路径
双亲委派模型
Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织: 除了顶层的启动类加载器以外,其他的类加载器都应当有自己的父类加载器。
public class Test { public static void main(String[] args) { ClassLoader classLoader =Test.class.getClassLoader(); System.out.println(classLoader.getClass().getName());//控制台输出结果sun.misc.Launcher$AppClassLoader classLoader=classLoader.getParent(); System.out.println(classLoader.getClass().getName());//控制台输出结果sun.misc.Launcher$ExtClassLoader classLoader=classLoader.getParent(); //(引导类加载器):该类加载器通常由C++语言实现,不继承任何Java类 java找不到 System.out.println(classLoader);//控制台输出结果为null System.out.println(classLoader.getClass().getName());//出现异常 } }
运行结果:
从上面的结果可以看出,并没有获取到ExtClassLoader的父ClassLoader,因为Bootstrap ClassLoader(启动类加载器)是用C语言实现的,getParent()找不到一个确定的返回父ClassLoader的方式,于是就返回null。
启动类(引导类)加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类(引导类)加载器,那直接使用null代替即可。
源码
sun.misc.Launcher源码:openjdk\jdk\src\share\classes\sun\misc目录:代码如下:
package sun.misc; import java.io.File; import java.io.IOException; import java.io.FilePermission; import java.net.URL; import java.net.URLClassLoader; import java.net.MalformedURLException; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.util.HashSet; import java.util.StringTokenizer; import java.util.Set; import java.util.Vector; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.security.AccessControlContext; import java.security.PermissionCollection; import java.security.Permissions; import java.security.Permission; import java.security.ProtectionDomain; import java.security.CodeSource; import sun.security.util.SecurityConstants; import sun.net.www.ParseUtil; /** * 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); SharedSecrets.getJavaNetAccess(). getURLClassPath(this).initLookupCache(this); } 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; } private static URL[] getExtURLs(File[] dirs) throws IOException { Vector<URL> urls = new Vector<URL>(); for (int i = 0; i < dirs.length; i++) { String[] files = dirs[i].list(); if (files != null) { for (int j = 0; j < files.length; j++) { if (!files[j].equals("meta-index")) { File f = new File(dirs[i], files[j]); urls.add(getFileURL(f)); } } } } URL[] ua = new URL[urls.size()]; urls.copyInto(ua); return ua; } /* * Searches the installed extension directories for the specified * library name. For each extension directory, we first look for * the native library in the subdirectory whose name is the value * of the system property <code>os.arch</code>. Failing that, we * look in the extension directory itself. */ public String findLibrary(String name) { name = System.mapLibraryName(name); URL[] urls = super.getURLs(); File prevDir = null; for (int i = 0; i < urls.length; i++) { // Get the ext directory from the URL File dir = new File(urls[i].getPath()).getParentFile(); if (dir != null && !dir.equals(prevDir)) { // Look in architecture-specific subdirectory first // Read from the saved system properties to avoid deadlock String arch = VM.getSavedProperty("os.arch"); if (arch != null) { File file = new File(new File(dir, arch), name); if (file.exists()) { return file.getAbsolutePath(); } } // Then check the extension directory File file = new File(dir, name); if (file.exists()) { return file.getAbsolutePath(); } } prevDir = dir; } return null; } private static AccessControlContext getContext(File[] dirs) throws IOException { PathPermissions perms = new PathPermissions(dirs); ProtectionDomain domain = new ProtectionDomain( new CodeSource(perms.getCodeBase(), (java.security.cert.Certificate[]) null), perms); AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] { domain }); return acc; } } /** * 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); } }); } final URLClassPath ucp; /* * Creates a new AppClassLoader */ AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this); ucp.initLookupCache(this); } /** * 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)); } } if (ucp.knownToNotExist(name)) { // The class of the given name is not found in the parent // class loader as well as its local URLClassPath. // Check if this class has already been defined dynamically; // if so, return the loaded class; otherwise, skip the parent // delegation and findClass. Class<?> c = findLoadedClass(name); if (c != null) { if (resolve) { resolveClass(c); } return c; } throw new ClassNotFoundException(name); } return (super.loadClass(name, resolve)); } /** * allow any classes loaded from classpath to exit the VM. */ protected PermissionCollection getPermissions(CodeSource codesource) { PermissionCollection perms = super.getPermissions(codesource); perms.add(new RuntimePermission("exitVM")); return perms; } /** * This class loader supports dynamic additions to the class path * at runtime. * * @see java.lang.instrument.Instrumentation#appendToSystemClassPathSearch */ private void appendToClassPathForInstrumentation(String path) { assert(Thread.holdsLock(this)); // addURL is a no-op if path already contains the URL super.addURL( getFileURL(new File(path)) ); } /** * create a context that can read any directories (recursively) * mentioned in the class path. In the case of a jar, it has to * be the directory containing the jar, not just the jar, as jar * files might refer to other jar files. */ private static AccessControlContext getContext(File[] cp) throws java.net.MalformedURLException { PathPermissions perms = new PathPermissions(cp); ProtectionDomain domain = new ProtectionDomain(new CodeSource(perms.getCodeBase(), (java.security.cert.Certificate[]) null), perms); AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] { domain }); return acc; } } private static class BootClassPathHolder { static final URLClassPath bcp; static { URL[] urls; if (bootClassPath != null) { urls = AccessController.doPrivileged( new PrivilegedAction<URL[]>() { public URL[] run() { File[] classPath = getClassPath(bootClassPath); int len = classPath.length; Set<File> seenDirs = new HashSet<File>(); for (int i = 0; i < len; i++) { File curEntry = classPath[i]; // Negative test used to properly handle // nonexistent jars on boot class path if (!curEntry.isDirectory()) { curEntry = curEntry.getParentFile(); } if (curEntry != null && seenDirs.add(curEntry)) { MetaIndex.registerDirectory(curEntry); } } return pathToURLs(classPath); } } ); } else { urls = new URL[0]; } bcp = new URLClassPath(urls, factory); bcp.initLookupCache(null); } } public static URLClassPath getBootstrapClassPath() { return BootClassPathHolder.bcp; } private static URL[] pathToURLs(File[] path) { URL[] urls = new URL[path.length]; for (int i = 0; i < path.length; i++) { urls[i] = getFileURL(path[i]); } // DEBUG //for (int i = 0; i < urls.length; i++) { // System.out.println("urls[" + i + "] = " + '"' + urls[i] + '"'); //} return urls; } private static File[] getClassPath(String cp) { File[] path; if (cp != null) { int count = 0, maxCount = 1; int pos = 0, lastPos = 0; // Count the number of separators first while ((pos = cp.indexOf(File.pathSeparator, lastPos)) != -1) { maxCount++; lastPos = pos + 1; } path = new File[maxCount]; lastPos = pos = 0; // Now scan for each path component while ((pos = cp.indexOf(File.pathSeparator, lastPos)) != -1) { if (pos - lastPos > 0) { path[count++] = new File(cp.substring(lastPos, pos)); } else { // empty path component translates to "." path[count++] = new File("."); } lastPos = pos + 1; } // Make sure we include the last path component if (lastPos < cp.length()) { path[count++] = new File(cp.substring(lastPos)); } else { path[count++] = new File("."); } // Trim array to correct size if (count != maxCount) { File[] tmp = new File[count]; System.arraycopy(path, 0, tmp, 0, count); path = tmp; } } else { path = new File[0]; } // DEBUG //for (int i = 0; i < path.length; i++) { // System.out.println("path[" + i + "] = " + '"' + path[i] + '"'); //} return path; } private static URLStreamHandler fileHandler; static URL getFileURL(File file) { try { file = file.getCanonicalFile(); } catch (IOException e) {} try { return ParseUtil.fileToEncodedURL(file); } catch (MalformedURLException e) { // Should never happen since we specify the protocol... throw new InternalError(e); } } /* * The stream handler factory for loading system protocol handlers. */ private static class Factory implements URLStreamHandlerFactory { private static String PREFIX = "sun.net.www.protocol"; public URLStreamHandler createURLStreamHandler(String protocol) { String name = PREFIX + "." + protocol + ".Handler"; try { Class<?> c = Class.forName(name); return (URLStreamHandler)c.newInstance(); } catch (ReflectiveOperationException e) { throw new InternalError("could not load " + protocol + "system protocol handler", e); } } } } class PathPermissions extends PermissionCollection { // use serialVersionUID from JDK 1.2.2 for interoperability private static final long serialVersionUID = 8133287259134945693L; private File path[]; private Permissions perms; URL codeBase; PathPermissions(File path[]) { this.path = path; this.perms = null; this.codeBase = null; } URL getCodeBase() { return codeBase; } public void add(java.security.Permission permission) { throw new SecurityException("attempt to add a permission"); } private synchronized void init() { if (perms != null) return; perms = new Permissions(); // this is needed to be able to create the classloader itself! perms.add(SecurityConstants.CREATE_CLASSLOADER_PERMISSION); // add permission to read any "java.*" property perms.add(new java.util.PropertyPermission("java.*", SecurityConstants.PROPERTY_READ_ACTION)); AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { for (int i=0; i < path.length; i++) { File f = path[i]; String path; try { path = f.getCanonicalPath(); } catch (IOException ioe) { path = f.getAbsolutePath(); } if (i == 0) { codeBase = Launcher.getFileURL(new File(path)); } if (f.isDirectory()) { if (path.endsWith(File.separator)) { perms.add(new FilePermission(path+"-", SecurityConstants.FILE_READ_ACTION)); } else { perms.add(new FilePermission( path + File.separator+"-", SecurityConstants.FILE_READ_ACTION)); } } else { int endIndex = path.lastIndexOf(File.separatorChar); if (endIndex != -1) { path = path.substring(0, endIndex+1) + "-"; perms.add(new FilePermission(path, SecurityConstants.FILE_READ_ACTION)); } else { // XXX? } } } return null; } }); } public boolean implies(java.security.Permission permission) { if (perms == null) init(); return perms.implies(permission); } public java.util.Enumeration<Permission> elements() { if (perms == null) init(); synchronized (perms) { return perms.elements(); } } public String toString() { if (perms == null) init(); return perms.toString(); } }
jdk的扩展类加载器ExtClassLoader和系统类加载器AppClassLoader就是定义在Launcher类里边的。
类加载器实例化过程:
通过java命令执行Java程序时,首先初始化JVM,产生Bootstrap ClassLoader(启动类加载器)——>Bootstrap ClassLoader自动加载Extended ClassLoader(扩展类加载器),并将其父ClassLoader设为Bootstrap Loader——>Bootstrap ClassLoader自动加载AppClass ClassLoader(系统类加载器),并将其父ClassLoader设为Extended ClassLoader
类加载器运行原理
一个类加载器接到加载某个类的任务时,不会自己去尝试加载这个类,而是
- 先将加载任务委托给父类加载器去完成, 每一个层次的类加载器都是如此,因此加载任务最终传递到最顶层的启动类加载器,
- 然后自上而下从最顶层类加载器开始,每级类加载器在其搜索范围内尝试完成加载任务,如果某级类加载器完成类加载任务,就成功返回,类加载结束,
- 否则交给下级类加载器尝试完成加载任务,以此类推,
- 当所有父类加载器都无法完成类加载任务时就会回退到最初的类装载器时,如果该加载器也不能完成类的装载,则抛出ClassNotFoundException异常,
这种加载类的机制称为双亲委派机制。
ClassLoader抽象类中loadClass(String name, boolean resolve)方法实现了双亲委派机制,代码如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { //检查是否被加载过 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) { //如果父类加载失败,则使用自己的findClass方法进行加载 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; } }
双亲委派机制有效避免了类被重复加载!
自定义类加载器
无法改变JDK自带类加载器的搜索路径,因此如果在程序运行时从特定搜索路径加载类,就要自定义类加载器,其定义步骤如下:
1、自定义一个继承自ClassLoader抽象类的Java类;
2、重写ClassLoader抽象类中findClass方法;
代码如下:
import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class OtherClassLoader extends ClassLoader{ private String classPath; public OtherClassLoader(String classPath) { this.classPath=classPath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { classPath = classPath+"\\"+name.replace(".", "\\")+".class"; InputStream inputStream = new FileInputStream(classPath); ByteArrayOutputStream outputStream= new ByteArrayOutputStream(); byte [] car=new byte[1024]; int length=-1; while ((length=inputStream.read(car))!=-1) { outputStream.write(car, 0, length);//将字节码转化为Class对象 } byte[] classFile = outputStream.toByteArray(); return defineClass(name, classFile,0, length);//获取class文件内容 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }