JVM类加载机制

JVM类结构类加载类执行

JVM类加载机制_第1张图片
image.png

JVM类加载机制_第2张图片
image.png

类加载的五个过程:加载、验证、准备、解析、初始化。

  • 加载: 根据全限定名来获取定义类的二进制字节流,然后将该字节流所代表的静态结构转化为方法区的运行时数据结构,最后在生成一个代表该类的Class对象,作为方法区这些数据的访问入口.
  • 验证:主要时为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全.包含四个阶段的验证过程:
  1. 文件格式验证:保证输入的字节流能够正确地解析并存储在方法区之内,格式上符合描述一个java类型信息的要求
  2. 元数据验证:字节码语义信息的验证,以保证描述的信息符合java语言规范.验证点有:这个类是否有父类等.
  3. 字节码验证:主要是进行数据流和控制流分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为.
  4. 符号引用验证:对符号引用转化为直接引用过程的验证.
  • 准备:为类变量分配内存并设置变量的初始值, 这些内存在方法区进行分配.
  • 解析:将虚拟机常量池中的符号引用转化为直接引用的过程.解析主要是针对类或接口、字段、类方法、类接口方法四类.
  • 初始化:执行静态变量的赋值操作以及静态代码块,完成初识化.初始化过程保证了父类中定义的初始化优先于子类的初始化.但接口不需要执行父类的初始化.

类初始化阶段解析

https://blog.csdn.net/ns_code/article/details/17881581

类变量初始化顺序

https://blog.csdn.net/jianggujin/article/details/52228983

类class文件解析

https://blog.csdn.net/ns_code/article/details/17675609

JVM类加载时机

  • 何时开始类的初始化
    什么情况下需要开始类加载过程的第一个阶段:"加载"。虚拟机规范中并没强行约束,这点可以交给虚拟机的的具体实现自由把握,但是对于初始化阶段虚拟机规范是严格规定了如下几种情况,如果类未初始化会对类进行初始化。
  1. 创建类的实例
  2. 访问类的静态变量(除常量【被final修辞的静态变量】原因:常量一种特殊的变量,因为编译器把他们当作值(value)而不是域(field)来对待。如果你的代码中用到了常变量(constant variable),编译器并不会生成字节码来从对象中载入域的值,而是直接把这个值插入到字节码中。这是一种很有用的优化,但是如果你需要改变final域的值那么每一块用到那个域的代码都需要重新编译。
  3. 访问类的静态方法
  4. 反射如(Class.forName("my.xyz.Test"))
  5. 当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化
  6. 虚拟机启动时,定义了main()方法的那个类先初始化
    以上情况称为称对一个类进行“主动引用”,除此种情况之外,均有且仅有这几种情况不会触发类的初始化,称为“被动引用”
    接口的加载过程与类的加载过程稍有不同。接口中不能使用static{}块。当一个接口在初始化时,并不要求其父接口全部都完成了初始化,只有真正在使用到父接口时(例如引用接口中定义的常量)才会初始化。
  • 被动引用例子
  1. 子类调用父类的静态变量,子类不会被初始化。只有父类被初始化。。对于静态字段,只有直接定义这个字段的类才会被初始化.
  2. 通过数组定义来引用类,不会触发类的初始化
    SubClass[] sca = new SubClass[10];// 被动引用2
  3. 访问类的常量,不会初始化类

具体可参考 https://www.cnblogs.com/javaee6/p/3714716.html

JVM 加载 class 文件的原理机制

JVM 中类的装载是由类加载器(ClassLoader) 和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。
由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的 .class 文件中的数据读入到内存中,通常是创建一个字节数组读入 .class 文件,然后产生与所加载类对应的 Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化,包括:

  1. 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
  2. 如果类中存在初始化语句,就依次执行这些初始化语句。
    类的加载是由类加载器完成的,类加载器包括:启动类加载器(BootStrap)、扩展加载器(Extension)、应用程序加载器(Application)和用户自定义类加载器(java.lang.ClassLoader的子类)。从JDK 1.2开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类加载器的说明:
  • Bootstrap:启动类加载器,一般用本地代码实现,负责加载JVM基础核心类库。加载存放在/lib目录中的类库(如rt.jar);
  • Extension ClassLoader:扩展加载器, 负责加载/lib/ext目录中的 ,或被java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap;
  • Application ClassLoader:应用程序加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。

缺点:

  • 双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被调用代码调用的API。但是,如果基础类又要调用用户的代码时,双亲委派模型无法满足要求。 因为Bootstrap加载器无法找到永不代码类。

为了解决这个困境,Java设计团队只好引入了一个不太优雅的设计:线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。了有线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI,JDBC,JCE,JAXB和JBI等。 Dubbo的SPI也是采用这种机制实现。

双亲委派模型

除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器.顺序依次是:

  • Bootstrap ClassLoader: 启动类加载器,加载java_home/lib中的类
  • Extension ClassLoader: 扩展类加载器,加载java_home/lib/ext目录下的类库
  • Application ClassLoader: 应用程序类加载器,加载用户类路径上指定类库.

双亲委派模型的工作原理是:如果一个类加载器受到了类加载请求,它首先不会自己去尝试加载这个类,而把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求时,加载器才尝试自己加载.这种方式保证了Oject类(JDK 核心类)在各个加载器加载环境中都是同一个类.

分派:静态分派与动态分派。

多态性特征的一些最基本的体现. 静态类型是编译期可知的,动态类型是在运行时可知.Human h =new Man(); Human是静态类型,Man时动态类型.

所有依赖于静态类型定位方法执行版本的分派动作称作静态分派,最典型的应用是方法重载.静态分派发生在编译阶段

动态分派是根据动态类型来确定执行的版本,所以只有到运行时才能确定具体的执行方法版本.典型的代表时重写.其过程如下:

  1. 首先找到操作数栈栈顶的第一个元素所执向对象的实际类型,记做C.
  2. 如果在类型C中找到和常量中的描述符和简单名称都相符的方法,则进行范围权限校验.如果通过则返回该方法的直接引用,否则抛出IllegalAccessError异常.
  3. 否则按照继承关系从下往上一次对C的各个父类进行第2步的搜索和验证过程.
  4. 如果始终没有找到就抛出AbstractMethodError异常. 方法的接受者和方法的参数统称方法宗量,根据分配基于多少中宗量可以分为单分派和多分派.java是静态多分派,动态分派属于单分派.

动态分派的实现: 动态分派时非常频繁的动作,而且动态分派的方法版本选择过程需要运行时在类的方法元数据中搜索合适的目标方法,因此出于性能的考虑,在方法区中建立一个虚方法表,用来保存各个方法的实际入口地址.如果某个方法的子类中没有被重写,那么子类的虚方法表里面的地址入口和父类相同方法的地址入口是一致的.都是指向父类的实现入口,如果子类中重写了这个方法,子类方法表中的地址将会被替换为指向子类实现版本的入口地址.虚方法表在类加载的连接阶段进行初始化.

Student s= new Student(),在内存中做了那些事情

  1. 加载Student.class 文件进内存
  2. 在栈内存为s开辟空间
  3. 在堆内存为Student对象开辟空间
  4. 学生对象的成员变量进行显示初始化
  5. 通过构造方法对学生对象变量赋值
  6. 学生对象初始完毕,把对象地址赋值给s变量

相关资料: http://blog.csdn.net/wisgood/article/details/16818243

Java对象,类存在形式

Java双亲委派模型及破坏

https://blog.csdn.net/zhangcanyan/article/details/78993959

双亲委派模型的式作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完全这个加载请求时,子加载器才会尝试自己去加载。

双亲委派模型的破坏
双亲委派模型的第一次“被破坏”其实发生在双亲委派模型出现之前--即JDK1.2发布之前。由于双亲委派模型是在JDK1.2之后才被引入的,而类加载器和抽象类java.lang.ClassLoader则是JDK1.0时候就已经存在,面对已经存在 的用户自定义类加载器的实现代码,Java设计者引入双亲委派模型时不得不做出一些妥协。为了向前兼容,JDK1.2之后的java.lang.ClassLoader添加了一个新的proceted方法findClass(),在此之前,用户去继承java.lang.ClassLoader的唯一目的就是重写loadClass()方法,因为虚拟在进行类加载的时候会调用加载器的私有方法loadClassInternal(),而这个方法的唯一逻辑就是去调用自己的loadClass()。JDK1.2之后已不再提倡用户再去覆盖loadClass()方法,应当把自己的类加载逻辑写到findClass()方法中,在loadClass()方法的逻辑里,如果父类加载器加载失败,则会调用自己的findClass()方法来完成加载,这样就可以保证新写出来的类加载器是符合双亲委派模型的。
双亲委派模型的第二次“被破坏”是这个模型自身的缺陷所导致的,双亲委派模型很好地解决了各个类加载器的基础类统一问题(越基础的类由越上层的加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被调用代码调用的API。但是,如果基础类又要调用用户的代码,那该怎么办呢。
这并非是不可能的事情,一个典型的例子便是JNDI服务,它的代码由启动类加载器去加载(在JDK1.3时放进rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用独立厂商实现部部署在应用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代码,但启动类加载器不可能“认识”之些代码,该怎么办?
为了解决这个困境,Java设计团队只好引入了一个不太优雅的设计:线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。了有线程上下文类加载器,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型,但这也是无可奈何的事情。Java中所有涉及SPI的加载动作基本上都采用这种方式,例如JNDI,JDBC,JCE,JAXB和JBI等。
双亲委派模型的第三次“被破坏”是由于用户对程序的动态性的追求导致的,例如OSGi的出现。在OSGi环境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为网状结构。

你可能感兴趣的:(JVM类加载机制)