JVM是Java的核心和基础,是介于Java编译器和操作平台之间的虚拟处理器,基于底层操作系统和硬件平台使用软件方法实现的抽象计算机,可以在JVM上面执行编译后的Java字节码程序。Java源文件经过Java编译器编译成JVM可执行的字节码文件,通过JVM将字节码翻译(解释)成不同平台机器码,通过特定平台运行。
参考链接
JVM的生命周期
JVM的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
JVM实例的运行:main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,Java程序也可以标明自己创建的线程是守护线程。
JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用java.lang.Runtime类或者java.lang.System.exit()来退出。
JVM运行内存模型
JVM类加载机制
JVM把文件.class字节码加载到内存,对数据进行校验,准备和解析,并初始化,最终形成可以被虚拟机直接使用的java类型。加载、验证、准备、解析、初始化 的执行过程,就是类的加载过程。
类加载机制指将.class文件中的二进制数据读入到JVM内存中,并对数据进行校验,解析和初始化。最终每一个类都会在方法区保存一份它的元数据,在堆中创建一个与之对应的Class对象。
类的加载阶段
加载阶段是“类加载机制”中的一个阶段,这个阶段通常也被称作“装载”。
主要完成以下功能:
通过“类全名”来获取定义此类的二进制字节流
将字节流所代表的静态存储结构转换为方法区的运行时数据结构----模板
在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区模板数据的访问入口
类的验证阶段
验证是链接阶段的第一步,主要目的是确保class文件中字节流包含的信息符合当前虚拟机要求,并且不会危害虚拟机自身安全。
验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
类的准备阶段
准备阶段正式为类变量分配内存并设置类变量初始值,这些内存都将在方法区中进行分配。
这个阶段中有两个容易产生混淆的知识点:
这时候进行内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。
这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量定义为: public static int value = 123;那么变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何java方法,把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会被执行。上面所说的“通常情况”下初始值是零值,那相对于一些特殊的情况,如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,建于上面类变量value定义为:public static final int value = 123;编译时java才将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value设置为123。
类的解析阶段
解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。
符号引用:一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
直接引用:直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
解析的动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。分别对应编译后常量池内的CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info四种常量类型。
1.类、接口的解析
2.字段解析
3.类方法解析
4.接口方法解析
类的初始化阶段
类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
类加载的时机
使用new实例化对象时、读取或者设置一个类的静态字段或方法时
反射调用时,例如 Class.forName("com.xxx.MyTest")
初始化一个类的子类,会首先初始化子类的父类
java虚拟机启动时标明的启动类
JDK8 之后,接口中存在default方法,这个接口的实现类初始化时,接口会其之前进行初始化
参考链接
Java类加载器有哪些?
类加载器的任务是根据一个类的全限定名,来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例
java中有一下四种类加载器:
启动类加载器(Bootstrap ClassLoader)-----c++编写,加载java核心库 java.*
■负责加载放在
扩展类加载器(Extension ClassLoader)-----java编写,加载扩展库
■由sun.misc.Launcher$AppClassLoader实现。它负责
应用程序类加载器(Application ClassLoader)---------java编写,加载程序所在的目录
■这个类由sun.misc.Launcher$AppClassLoader实现。是ClassLoader中getSystemClassLoader()方法的返回值。它负责用户路径(ClassPath)所指定的类库。用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。
自定义类加载器(User ClassLoader)
■用户自己定义的类加载器。
类装载器的内部机制----双亲委派机制
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
双亲委派机制的作用
保证安全性:防止重复加载同一个class。通过委托去向上面问,加载过了就不用再加载一遍。保证数据安全。
保证唯一性:保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象。不同加载器加载同一个.class也不是同一个Class对象,这样保证了Class执行安全。
解释:如果没有双亲委派模型而是由各个类加载器自行加载,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,多个类加载器都去加载这个类到内存中,系统中将会出现多个不同的Object类,那么类之间的比较结果及类的唯一性将无法保证,因为Object都各不相同那么程序运行启动就会出错,也保证了JVM能够正常的安全运行。