Java源文件 ——>编译器 ——>字节码文件 (.class文件)——>JVM ——>机器码,每种平台的解释器是不同的,但是实现的虚拟机是相同的,当一个程序从开始运行,这使得虚拟机就开始实例化了,多个程序启动就会存在多个虚拟机实例。程序退出或者关闭,则虚拟机的实例消亡,多个虚拟机实例之间数据不能共享。
类加载的时机:
主动初始化的6种方式:
- 创建对象实例:new 对象的时候,会对类初始化,前提是这个类么有被初始化。
- 调用类的静态属性或为静态属性赋值。
- 调用类的静态方法
- 通过class文件反射创建对象
- 初始化一个类的子类:使用子类的时候先初始化父类
- Java虚拟机启动时被标记为启动类的类,比如main方法所在的类。
注意:
Java类的加载是动态的,它并不会一次性将所有的类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其它类,则在需要的时候才加载。这是为了节省内存开销。
不会进行初始化的情况:
- 在同一个类加载器下面只能初始化一次,如果已经初始化了便不会再次初始化
- 在编译的时候能确定下来的静态变量(编译常量),不会对类进行初始化。例如final写诗的静态变量。
类加载的过程:(负责将字节码文件载入到内存)
Bootstrap ClassLoader:
顶层的类加载器,负责加载$JAVA_HOME中的jre/linb/rt.jar 里所有的class,由C++实现,不属于ClassLoader的子类。
ExtClassLoader:
负责加载java平台的扩展功能的jar包,包括$JAVA_HOME中的jre/lib/*.jar 或者java.ext.dirs指定目录下的jar包。
AppClassLoader:
负责加载classpath中国指定的jar包及目录中的class。
-- JVM加载一个类的详细过程:
加载 ——> 链接 ——> 初始化
加载:查找并加载类的二进制数据,JVM先从磁盘中寻找该类的字节码文件,JVM从系统环境变量的 CLASSPATH里面找到字节码文件的搜索路径, .代表当前工作目录通过类的加载器从字节码文件中把 这个类的Class对象(类的户口信息)的信息加载到JVM中。
链接:
- 验证 文件格式,元数据,字节码,符号引用验证
- 准备 为类的静态变量分配内存,并将其初始化为默认值,如果当前类还有基类,则继续递归的加载其 基类
- 解析 把类中的符号引用转换为直接引用
初始化:为类的静态变量赋予正确的初始值
双亲委派模型:(要求除了顶层的启动类加载器之外,其余的类加载器都应该有自己的父类加载器)
其主要过程:
- 当前类加载器从自己已经加载的类中查询是否此类已经加载,如果已经加载则返回原来已经加载的类。
- 如果么有找到,就委托父类加载器去加载。父类加载器也会采用同样的策略,查看自己已经加载的类中是否包含这个类,有就返回,么有就委托父类去加载,直到委托到启动类加载器为止。如果父类加载器为空了,就代表使用启动类加载器作为父加载器去加载该类。(就是看到String类加载器为null的时候)。
- 如果启动类加载加载失败,就会使用扩展类加载器来尝试加载,继续失败则会使用AppclassLoader去加载,继续失败就会抛出一个ClassNotFoundException的异常。
其好处:
- 安全性 避免用户自己编写的类动态的提花Java的一些核心类。如果不采用双亲委派模型的方式进行类的加载,那我们就可以随时使用自定义的类来动态代替java核心的API中定义的类。
- 避免类的重复加载 JVM判定两个类是否是同一个类,不仅仅是根据类名是否相同进行判定的,还要判断加载该类的类加载器是否是同一个类加载器,相同的class文件被不同的类加载器加载得到的结果就是两个不同的类。