Java类加载机制源码分析
来源:互联网 发布:日本外汇储备数据 编辑:程序博客网 时间:2024/06/05 08:03
Java代码首先要编译成class文件字节码,在运行时通过JIT(即时编译器)编译成本地机器码,最后由ClassLoader将其加载解析成Class对象到内存中。通过ClassLoader的loadClass方法的源码加深对Java类加载机制的理解。
1. ClassLoader加载机制简述:
Java的类加载遵循父类优先的原则,也就是说ClassLoader是一个有层级的树形组合体系,并且一个ClassLoader要加载一个类,首先逐层向上检查是否有加载器加载过该类,如果有,将结果逐层返回到下级。如果没有,继续检查直到有一层ClassLoader返回没有加载并且它不应该加载,那么该层的下一层就可以加载该类。
PS:父类优先的方式也不是万能的,在JavaEE Web应用程序中,也会使用子女优先加载的方式;
1.1 JVM提供3层基本的类加载平台:
BootstrapClassLoader:加载JVM自身需要的类,注意在Hotspot JVM中它严格来说不是JVM类加载体系中的,它并不遵循上述机制,也不是下面ExtClassLoader的父类加载器;
ExtClassLoader:加载特定的类:System.getProperty("java.ext.dirs");也就是JRE/LIB/EXT目录下的类,加载的是sun公司的一些扩展包,它是AppClassLoader的父类加载器;
AppClassLoader:加载System.getProperty("java.class.path");就是classpath,看到这个你可能已经知道eclipse项目下.classpath的作用了,就是告诉AppClassLoader这些类由它加载;
继承自URLClassLoader的自定义类加载器,通过调用getSystemClassLoader获取自己的父加载器(AppClassLoader);
ClassLoader的类层次结构:
图中的AppClassLoader和ExtClassLoader是Launcher的内部类;
到现在,我们可以也可以看出Java的ClassLoader使用了职责链设计模式,父优先加载,一定程度上保证了程序安全(防止恶意代码替换JSE核心类)。
1.2 JVM加载Class文件到内存的方式:
一是隐式加载:继承或引用某个类时,有JVM负责加载;
二是显式加载:在代码中调用loadClass(),Class.forName,ClassLoader的findClass方法等,显式加载中也可能包含隐式加载;
2. ClassLoader的重要方法:
findClass:主要由URLClassLoader实现,根据URLClassPath去指定地方查找class文件;取得要加载class文件的字节流;
defineCLass:可以将字节流解析成Class对象,该Class对象并未进行resolve;
resolveClass:对Class对象进行Link,载入引用类(超类,接口字段,方法签名,方法中的本地变量);
loadClass:采用默认的加载逻辑根据类名加载一个类,返回Class对象,调用前面3个方法实现;
3. 加载class文件的过程:
3.1 加载字节码到内存:findClass和defineClass方法
protected Class<?> findClass(final String name) throws ClassNotFoundException { final Class<?> result; try { //获取特权,确保有权限可以读取到资源,这里 result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException { //将完整的类名转换成文件路径格式 String path = name.replace('.', '/').concat(".class"); //在指定的URLPath中获取对应的class文件资源 Resource res = ucp.getResource(path, false); if (res != null) { try { //获取成功将资源传入,最终获取未解析的Class对象 return defineClass(name, res); } catch (IOException e) { //不能成功读取文件内容 throw new ClassNotFoundException(name, e); } } else { //不能获取资源 return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { //因为要在特权操作中抛出ClassNotFoundException,使用了PrivilegedExceptionAction回调 //它会将异常包装,这里要解除包装 throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }
private Class<?> defineClass(String name, Resource res) throws IOException { long t0 = System.nanoTime(); int i = name.lastIndexOf('.'); URL url = res.getCodeSourceURL(); //首先加载包 if (i != -1) { String pkgname = name.substring(0, i); // Check if package already loaded. Manifest man = res.getManifest(); definePackageInternal(pkgname, man, url); } // Now read the class bytes and define the class //使用nio,获取字节缓冲区 java.nio.ByteBuffer bb = res.getByteBuffer(); if (bb != null) { // Use (direct) ByteBuffer: CodeSigner[] signers = res.getCodeSigners(); CodeSource cs = new CodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); return defineClass(name, bb, cs); } else { //获取不到缓冲区,直接InputStream获取字节数组 byte[] b = res.getBytes(); // must read certificates AFTER reading bytes. CodeSigner[] signers = res.getCodeSigners(); CodeSource cs = new CodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); return defineClass(name, b, 0, b.length, cs); } }
在这个方法中,我们可以看到对于class文件的读取策略,其中CodeSigner和CodeSource分别是代码签名和代码源,它们组合使用与前面提及的保护域机制(ProtectionDomain)当中,可见Java中类加载机制和安全模型是密不可分的。
3.2 验证和解析:defineClass方法和resovleClass方法:
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) throws ClassFormatError { //首先检查类名;阻止加载“java.”开头包内的类(应有BootStrapLoader加载); //确保同一个包内的Class拥有相同的证书 protectionDomain = preDefineClass(name, protectionDomain); //根据CodeSource获取一个URL的字符串表示 String source = defineClassSourceLocation(protectionDomain); //字节码验证;类准备(准备字段,方法,实现接口所必需的数据结构); Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); //通过证书为该类设置签名 postDefineClass(c, protectionDomain); return c; }
(3)解析:resolveClass方法,直接通过一个native方法实现
3.3 显式加载时的过程:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //该getClassLoadingLock获取同步锁,该锁用并发Map保存(享元模式) synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 这里体现了类加载机制中的父优先的查找机制 // 通过上滤直到parent为null // 这时再去BootstrapClassLoader中查找,在运行用户程序时,这一步一般都是null 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; } }
3.4 初始化Class对象:
4. 小结
- Java类加载机制源码分析
- 第八篇:JAVA类加载机制源码分析
- java类加载机制分析
- Tomcat WebappClassLoader 类加载机制源码分析
- JDK类加载机制源码分析及源码分析
- java-源码解读-java类加载机制
- Java源码编译机制、类加载机制、类执行机制
- java类加载机制简要分析
- Java类加载机制深度分析
- Java类加载机制深度分析
- Java类加载机制深度分析
- Java类加载机制深度分析
- Java虚拟机类加载机制+案例分析
- Java类加载机制实例分析
- Java类加载机制深度分析
- Java类加载二:类加载机制分析
- Android源码分析-资源加载机制
- Android源码分析-资源加载机制
- 滴滴快的笔试题:最大子矩阵
- Java Map数据结构与排序
- HDU 5195 DZY Loves Topological Sorting(优先队列)
- UVA - 10340 All in All
- 二叉树镜像--20150924
- Java类加载机制源码分析
- Python 学习(7)---网页访问异常处理
- hihoCoder 1233 Boxes(状态压缩)
- D-SIFT
- 详解pkg-config --cflags --libs glib-2.0的作用
- Linux中,getrlimit(),setrlimit(),getrusage()
- 一个长度为N的数组中包含正数 负数 0,请实现一个函数找出和为0的最长子数列
- centos6.7更换yum源
- 【ACM】HDOJ 1009 FatMouse' Trade