周志明先生所著的《深入理解Java虚拟机:JVM高级特性与最佳实践》(购买地址:亚马逊链接),对我学习Java、理解Java之道有非常大的帮助。至今已读过两遍,为了能够融会贯通,加深记忆(人老了记忆力差),便在Blog上记录一些认为该记的东西。
类加载的过程
加载
“加载”(Loading)阶段是“类加载”(Class Loading)过程的第一个阶段,在此阶段,虚拟机需要完成以下三件事情:
1、 通过一个类的全限定名来获取定义此类的二进制字节流。
2、 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
加载阶段即可以使用系统提供的类加载器在完成,也可以由用户自定义的类加载器来完成。加载阶段与连接阶段的部分内容(如一部分字节码文件格式验证动作)是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始。
验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
Java语言本身是相对安全的语言,使用Java编码是无法做到如访问数组边界以外的数据、将一个对象转型为它并未实现的类型等,如果这样做了,编译器将拒绝编译。但是,Class文件并不一定是由Java源码编译而来,可以使用任何途径,包括用十六进制编辑器(如UltraEdit)直接编写。如果直接编写了有害的“代码”(字节流),而虚拟机在加载该Class时不进行检查的话,就有可能危害到虚拟机或程序的安全。
不同的虚拟机,对类验证的实现可能有所不同,但大致都会完成下面四个阶段的验证:文件格式验证、元数据验证、字节码验证和符号引用验证。
1、文件格式验证,是要验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。如验证魔数是否0xCAFEBABE;主、次版本号是否正在当前虚拟机处理范围之内;常量池的常量中是否有不被支持的常量类型……该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区中,经过这个阶段的验证后,字节流才会进入内存的方法区中存储,所以后面的三个验证阶段都是基于方法区的存储结构进行的。
2、元数据验证,是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。可能包括的验证如:这个类是否有父类;这个类的父类是否继承了不允许被继承的类;如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法……
3、字节码验证,主要工作是进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为。如果一个类方法体的字节码没有通过字节码验证,那肯定是有问题的;但如果一个方法体通过了字节码验证,也不能说明其一定就是安全的。这里涉及了离散数学中一个很著名的问题“Halting Problem”:通俗一点的说法就是,通过程序去校验程序逻辑是无法做到绝对准确的——不能通过程序准确地检查出程序是否能在有限的时间之内结束运行。(通过我党的党内自查自纠是无法做到准确有效的反腐的……)
4、符号引用验证,发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在“解析阶段”中发生。验证符号引用中通过字符串描述的权限定名是否能找到对应的类;在指定类中是否存在符合方法字段的描述符及简单名称所描述的方法和字段;符号引用中的类、字段和方法的访问性(private、protected、public、default)是否可被当前类访问……
验证阶段对于虚拟机的类加载机制来说,不一定是必要的阶段。如果所运行的全部代码确认是安全的,可以使用-Xverify:none参数来关闭大部分的类验证措施,以缩短虚拟机类加载时间。
准备
准备阶段是正式为类变量(被static修饰的变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
public static int value=123;//在准备阶段value初始值为0 。在初始化阶段才会变为123 。
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
直接引用(Direct Reference):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,如果有了直接引用,那么引用的目标必定已经在内存中存在。
初始化
类初始化是类加载过程的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。
类加载器
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类是来源于同一个Class文件,只要加载它们的类加载器不同,那么这两个类就必定不相等。
/** * 类加载器与instanceof关键字演示 * @author 周志明 */ public class ClassLoaderTest { public static void main(String[] args) throws Exception{ //自定义一个类加载器 ClassLoader myLoader=new ClassLoader(){ @Override public Class<?> loadClass(String name) throws ClassNotFoundException{ try { String fileName=name.substring(name.lastIndexOf(".")+1)+".class"; InputStream is=getClass().getResourceAsStream(fileName); if (is==null) { return super.loadClass(name); } byte[] b=new byte[is.available()]; is.read(b); return defineClass(name, b, 0,b.length); } catch (IOException e) { e.printStackTrace(); throw new ClassNotFoundException(name); } } }; Object obj=myLoader.loadClass("ClassLoaderTest").newInstance(); System.out.println(obj.getClass()); System.out.println(obj instanceof ClassLoaderTest);//false } }
站在Java虚拟机的角度讲,只存在两种不同的类加载器:一种是启动类加载器,这个加载器在HotSpot中使用C++语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader。
启动类加载器(Bootstrap ClassLoader),负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机中。启动类加载器无法被Java程序直接引用。
扩展类加载器(Extension ClassLoader):由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序加载器(Application ClassLoader):由sun.misc.Launcher$AppClassLoader来实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径上所指定的类库,如果应用程序中没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器。
上图所展示的就是双亲委派模式。双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。其工作过程是:如果一个类加载器收到了类加载请求,它首先不会去自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,因此所有加载请求最终都应该传到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子类加载器才会尝试自己去加载。
Java.lang.ClassLoader中loadClass()方法的代码如下:
使用双亲委派模式来组织类加载器之间的关系,有个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar中,无论哪个类加载器要加载这个类,最终都是委派给启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
破坏双亲委派模式
双亲委派模式模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者们的类加载实现方式。在Java里大部分的类加载器都遵循这个模型,但也有例外。
如JNDI的代码由启动类加载器去加载,但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代码,但启动类加载器不可能“认识”这些代码啊!为了解决这个困境,Java设计团队只要引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局规范内都没有设置过,那么这个类加载器默认就是应用程序类加载器。JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JDBC,JCE, JBI等。
代码热替换、热部署等也“破坏”了双亲委派模式。
相关推荐
### 深入Java虚拟机JVM类加载学习笔记 #### 一、Classloader...通过以上内容的学习,我们可以更深入地理解Java虚拟机的类加载机制和垃圾回收机制,这对于优化Java应用程序的性能以及排查相关问题都是非常有帮助的。
类的加载过程主要由类加载器(ClassLoader)完成,类加载器负责将.class文件中的字节码读取到内存中,并转换为运行时数据结构。 #### 三、类加载器 类加载器主要分为以下几类: 1. **启动类加载器(Bootstrap ...
本文主要探讨JVM的类加载机制,包括类加载、连接、初始化等关键过程,以及类的主动使用和被动使用的情况。 首先,我们要理解**类加载**的作用。JVM的类加载器(ClassLoader)负责将编译后的`.class`文件加载到内存...
JVM中存在两类加载器:启动类加载器(用C++编写)和其他类加载器(用Java实现)。从开发者的角度来看,还有BootStrapClassLoader、SystemClassLoader、ClassPathClassLoader以及自定义的类加载器。Java的设计允许...
- **方法区**:也称为“永久代”,存储虚拟机加载的类信息、常量、静态变量等,是线程共享的区域。在Java 8之后,这部分被元空间(Metaspace)取代。 - **运行时常量池**:是方法区的一部分,存放编译器生成的各种...
1. 类加载器:Java的类加载机制是其安全体系的基础。类加载器负责将类的字节码加载到JVM中,不同的加载器有不同的权限范围。这种设计使得Java可以实现安全隔离,比如Web应用程序中的类加载策略,使得不同应用的类...
类加载器的学习是深入理解Java运行机制的关键,尤其对于系统级开发和框架设计而言更是如此。本文将主要围绕“黑马程序员------类加载器学习注意点”展开,探讨一些关键知识点。 首先,我们需要理解类加载器的基本...
总的来说,"深入理解Java虚拟机读书笔记之:第3章 安全(2)"主要涵盖了Java安全体系的核心概念,包括类加载器、权限模型、安全管理器以及相关工具的使用。理解这些内容对于任何希望构建安全、可靠的Java应用程序的...
此外,还涉及到类加载器的委托机制和类的可见性等问题。 综上所述,深入理解Java虚拟机的工作原理对于开发高效稳定的Java应用至关重要。通过学习JVM内部的具体实现细节,可以帮助开发者更好地编写代码,优化程序...
- **类装载器的体系结构**:类装载器负责加载Java类到JVM中,它由多个不同的类装载器组成,如启动类装载器、扩展类装载器和应用程序类装载器。这种层次化的结构有助于实现类隔离以及安全性控制。 - **Java Class...
* 启动类加载器(Bootstrap Class Loader):加载 Java 核心类。 * 扩展类加载器(Extension Class Loader):加载扩展类。 * 应用类加载器(Application Class Loader):加载应用类。 运行时栈帧的结构 Java 中...
在JVM的执行过程中,类加载器(Class Loader)负责将.class文件中的二进制数据读入内存中,将其转换为方法区内的运行时数据结构,并在堆中生成一个java.lang.Class对象,作为对方法区中数据的访问入口。类加载的过程...
4. **类加载机制** - 加载:找到并加载类文件到JVM中。 - 验证:确保字节码的安全性和正确性。 - 准备:为类的静态变量分配内存,并初始化为默认值。 - 解析:将符号引用转换为直接引用。 - 初始化:执行类的...
Java虚拟机(JVM)是Java程序运行的基础,它的核心组成部分之一就是类加载机制。类加载机制负责将Java源代码编译成的.class文件转换为内存中的数据结构,以便程序可以执行。本篇将深入探讨类加载机制的原理和过程。 ...
Java 类加载器是Java虚拟机(JVM)的关键组成部分,它们负责在运行时动态加载所需的类。类加载器基于三个核心机制:委托、可见性和单一性。 1. **委托机制**: - 当一个类加载器收到加载类的请求时,它首先不会...
1. **类加载机制**:JVM的类加载过程包括加载、验证、准备、解析和初始化五个阶段。类加载器(ClassLoader)负责从磁盘或网络中获取类文件,并将其转换为运行时的数据结构。 2. **运行时数据区**:JVM将内存划分为...
1. **类加载**:JVM有三个主要的类加载器——引导类加载器、扩展类加载器和应用程序类加载器。它们协同工作,将.class文件加载到内存中形成运行时数据区的类变量和方法区。此外,用户还可以自定义类加载器。 2. **...
类装载器分为两类:Bootstrap ClassLoader(引导类加载器)和User-defined ClassLoader(用户自定义类加载器)。Bootstrap ClassLoader是JVM的一部分,负责加载JDK核心类库,如rt.jar中的类。而User-defined ...
包括引导类加载器、扩展类加载器和应用程序类加载器等。 2. **运行时数据区**:包括堆、方法区、程序计数器、虚拟机栈和本地方法栈。其中,堆存储对象实例,方法区存储类信息,虚拟机栈处理方法调用,程序计数器...