类的生命周期
类的生命周期分为7个阶段:加载、验证、准备、解析、初始化、使用、卸载。其中 验证、准备、解析 三个阶段称之为连接。由于动态绑定机制,可能解析会在初始化之后进行,其他都是顺序开始,交叉进行。
类加载的过程
类的加载过程全过程包括:加载、验证、准备、解析、初始化五个过程。
-
加载
在加载阶段,虚拟机要完成三个事情:
1)通过类的全限定类名获取定义此类的二进制字节流。
2)将字节流代表的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成此类的java.lang.Class对象,作为方法区这个类的数据访问入口。 -
验证
保证Class文件的字节流信息符合《JVM规范》。
1.文件格式验证:
是否以魔数CAFEBABE开头。
主、此 版本号是否在接收范围。
… … … … …
2.元数据验证(数据类型验证)
进行语义分析,保证其描述信息符合规范。
是否有父类(除了Object都有父类)
是否 父类继承了不能继承的类
3.字节码验证(方法体验证)
4.符号引用验证(引用的东西是否存在之类) -
准备
这一阶段正式为类中定义的变量(静态变量)分配内存并设置类变量初始值(这里的初始值是java数据类型的默认值(final 定义例外),不是用户设定的,用户设定的要在初始化阶段时设置)。逻辑上来讲存放在方法区。public static int value = 123; 准备阶段赋值为0 public static final int value = 123; 准备阶段赋值为123
-
解析
将常量池里的符号引用替换为直接引用:就是将JVM里的规范的符号替换为指针。
类或接口的解析、字段的解析、方法的解析、接口方法的解析、 -
初始化
真正执行类中编写的java代码,主导权交给应用程序。
初始化的阶段就是执行类构造器方法的过程。
类加载器
在JVM层面,只有两种不同的类加载器,一是C++所实现的,是虚拟机的一部分的,启动类加载器(Bootstrap ClassLoader)另一种是java实现的独立存在虚拟机外的,并继承自抽象类java.lang.ClassLoader。
通俗来讲:比较两个类是否相等只有在两个类由同一个类加载器加载的前提下才有意义,不同类加载器加载必然不同,这里的相等包括 equals()方法,isInstance( )方法等。
双亲委派模型
java一直保持着三层类加载器、双亲委派的类加载架构。
双亲委派模型指的是:当需要加载类时,会优先委派当前所在的类的加载器的父加载器去加载这个类。所有加载请求传递到顶层的启动类加载器,如果父加载器无法加载到这个类时,再尝试在子加载器中加载这个类。
优点在于:保证Java 核心类被篡改,并且避免类的重复加载。
图片来源
双亲委派模型的破坏:
- 引入双亲委派模型时,为了兼容旧代码,只能加了个findClass( )方法,引导用户在自定义类加载器时重写这个方法而不是核心方法loadClass().
- 模型自身的缺陷导致,当基础类型要调用用户代码时,基础类由启动类加载器加载而其无法加载用户类。因此引入线程上下文加载器(Thread Context ClassLoader)。若未设置默认为应用程序类加载器。由此可以父类加载器调用子加载器。
- 用户追求程序动态性。OSGI实现模块化热部署。每个类都有自己的类加载器。
自定义类加载器
继承java.lang.ClassLoader,重写它的findClass方法。在其中调用loadClass方法。
findClass()和loadClass()
findClass()用于写类加载逻辑、loadClass()方法的逻辑里如果父类加载器加载失败则会调用自己的findClass()方法完成加载,保证了双亲委派规则。
如果不想打破双亲委派模型,那么只需要重写findClass方法,如果想打破双亲委派模型,那么就重写整个loadClass方法
New 一个对象发生了什么?
要看类有没加载,如何没有加载默认通过双亲委派模型进行类的加载,然后
进行创建对象
1、在堆区分配对象需要的内存
分配的内存包括本类和父类的所有实例变量,但不包括任何静态变量
2、对所有实例变量赋默认值
将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值
3、执行实例初始化代码
初始化顺序是先初始化父类再初始化子类,初始化时先执行实例代码块然后是构造方法
4、类似于Son s = new Son()形式的引用,首先在栈区定义Son类型引用变量s,然后将堆区对象的地址赋值给它。
参考:https://www.cnblogs.com/JackPn/p/9386182.html
书籍:深入理解java虚拟机