本节目录
一.Java虚拟机概述
定义:Java虚拟机(JVM)是Java程序运行的标准环境。可以把Java虚拟机看作一个抽象的计算机,它有各种指令集和各种运行时的数据区域。虽然是叫做Java虚拟机,但是在它之上运行的语言不仅有Java,还包括Kotlin、Groovy等JVM系语言。
1.Java虚拟机家族
HotSpot VM
是Oracle JDK和openJDK中自带的虚拟机,是最主流和使用范围最广的Java虚拟机。
J9 VM
由IBM开发的虚拟机。
Zing VM
以HotSpot VM为基础进行改进的虚拟机。
2.Java虚拟机执行流程
Java虚拟机执行流程分为两大部分,分别是编译时环境和运行时环境,当一个Java程序经过Java编译器编译之后,会产生一个Class文件,这个Class文件会由Java虚拟机来进行处理。
Java虚拟机只与特定的二进制文件Class有关,也就是说无论任何语言,只要能够生成Class文件,就可以被Java虚拟机识别并执行。
二.Java虚拟机结构
Java虚拟机结构
Java虚拟机结构包括:运行时数据区域、执行引擎、本地库接口和本地方法库,而类加载子系统并不属于Java虚拟机的内部结构。
1.Class文件格式
Java文件被编译后生成了Class文件,而这种二进制文件不依赖于特定的硬件和操作系统。每一个Class文件都对应着唯一的类或者接口的定义信息,但是类或者接口并不一定定义在文件中。Class文件格式如下:
Class文件格式
Class文件的基本数据类型如下所示:
u1:1字节,无符号类型
u2:2字节,无符号类型
u4:4字节,无符号类型
u8:8字节,无符号类型
2.类的生命周期
定义:一个Java文件被加载到Java虚拟机内存中到从内存中卸载的过程称为类的生命周期。
生命周期阶段:
(1) 加载:查找并由类加载子系统来加载Class文件。
(2) 链接:包括验证、准备和解析。
验证:确保被导入类型的正确性
准备:为类的静态字段分配字段,并用默认值初始化这些字段
解析虚拟机将常量池内的符号引用替换为直接引用
(3) 初始化:将类变量初始化为正确的初始值。
3.类加载子系统
定义:类加载子系统通过多种类加载器来查找和加载Class文件到Java虚拟机中,Java虚拟机中有两种类加载器:系统加载器和自定义加载器。
系统加载器:
Bootstrap ClassLoader(引导类加载器)
底层用C/C++代码实现的加载器,用于加载指定的核心类库。它用来加载以下目录中的类库:$JAVA_HOME/jre/lib目录和-Xbootclasspath参数指定目录。
Extensions ClassLoader(扩展类加载器)
用于加载Java的扩展类,提供除了系统类之外的额外功能。它用来加载以下目录中的类库:$JAVA_HOME/jre/lib/ext目录和java.ext.dir指定目录。
Application ClassLoader(应用程序类加载器)
它用来加载以下目录中的类库:当前应用程序的ClassPath目录和系统属性java.class.path指定目录。
自定义加载器:自定义加载器是通过继承java.lang.ClassLoader类的方式来实现自己的类加载器的。
4.运行时数据区域
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为不同的数据区域,这些区域分别为程序计数器、Java虚拟机栈、本地方法栈、Java堆和方法区。
1.程序计数器
为了保证程序能够连续的执行下去,处理器必须具有某些手段来确定下一条指令的地址,而程序技术器就是起到这种作用。程序计数器是Java虚拟机规范中唯一没有任何规定OutOfMemoryError情况的数据区域。
2.Java虚拟机栈
每一条Java虚拟机线程都有一个线程私有的Java虚拟机栈,它的生命周期与线程相同,与线程是同时创建的。Java虚拟机栈存储线程中Java方法调用状态,包括局部变量、参数、返回值以及运算的中间结果等。
3.本地方法栈
Java虚拟机实现可能要用到C Stacks来支持Native语言,这个C Stacks就是本地方法栈。它与Java虚拟机栈类似,只不过本地方法栈是用来支持Native方法的。
4.Java堆
Java堆是被所有线程共享的运行时内存区域。Java堆用来存放对象实例,几乎所有的对象实例都在这里分配内存。Java堆存储的对象被垃圾收集器管理。Java容量可以实固定的,也可以是动态扩展的。Java堆所使用的内存在物理上不需要连续,只要在逻辑上连续即可。
5.方法区
方法区是被所有线程共享的运行时内存区域,用来存储已经被Java虚拟机加载的类的结构信息。方法区是Java堆的逻辑组成部分,它一样在物理上不需要连续。
6.运行时常量池
运行时常量池是方法区的一部分,它用来存放编译时期生成的字面量和符号引用,这些内容会在类加载后存放在方法区的运行时常量池中。
三.对象的创建
当我们创建对象时一般是通过new指令来完成一个对象的创建的,当虚拟机收到一个new指令时,它会做如下操作:
1.判断对象对应的类是否加载、链接和初始化
2.为对象分配内存
3.处理并发安全问题
4.初始化分配到内存空间
5.设置对象的对象头
6.执行init方法进行初始化
四.对象的堆布局内存
对象创建完毕,并且已经在Java堆中分配了内存,而对象在堆内存的布局分为三个区域,分别是对象头、实例数据和对齐补充:
对象头
对象头包括两部分信息,分别是Mark World和元数据指针。Mark World用于存储对象运行时的数据,而元数据指针用于指向方法区中的目标类的元数据,通过元数据可以确定对象的具体类型。
实例数据
用于存储对象中的各种类型的字段信息。
对齐填充
对齐填充不一定存在,只是起到了占位符的作用。
五.垃圾标记算法
1.垃圾收集器
垃圾收集器,通常是被称作GC,其主要任务有两个,一个是内存的划分和分配,另一个是对垃圾进行回收。在对垃圾进行回收之前,GC要先标出垃圾,目前主流的有两种垃圾标记算法:引用计数算法和根搜索算法。
2.Java中的引用
Java将引用分为强引用、软引用、弱引用和虚引用。
强引用
当新建一个对象时就会创建一个具有强引用的对象,如果一个对象具有强引用,垃圾收集器就绝不会回收它。
软引用
如果一个对象只有软引用,则当内存不够时,会回收这些对象的内存。Java提供SoftTrference类来实现软引用。
弱引用
弱引用比起软引用具有更短的生命周期,垃圾收集器一旦发现了只具有弱引用的对象,不管当前内存是否足够,都会回收它。
虚引用
虚引用不会决定对象的生命周期,如果一个对象仅持虚引用,就相当于没有任何引用一样,在任何时候都可能被垃圾收集器回收。
3.引用计数算法
基本思想:每个对象都有一个引用计数器,当对象在某处被引用的时候,它的引用计数器就+1,引用失效的时候就-1,当引用计数器中的值变为0,则该对象就不能被使用,也就变成了垃圾。
缺点: 引用计数算法没有解决对象之间相互循环引用的问题,举例来说:
public class Test{
public static void main(String[] args){
Object obj1 = new Object();
Object obj2 = new Object();
obj1.instance = obj2; //引用
obj2.instance = obj1;
obj1 = null; //此时已经无效,可以回收
obj2 = null;
System.gc(); //打印gc日志
}
}
4.根搜索算法
基本思想: 选定一些对象作为GC Roots,并组成根对象集合,然后以这些GC Roots的对象作为起始点,向下搜索,如果目标对象到GC Roots是连接着的,这些目标就称为可达的,如果目标对象不可达则说明目标对象是可以被回收的对象。
在Java中,可以作为GC Roots的对象主要有以下几种:
1)Java栈中引用的对象。
2)本地方法栈中JNI引用的对象。
3)方法区中运行时常量池引用的对象。
六.Java对象在虚拟机中的生命周期
创建阶段(Created)
创建阶段的具体步骤为:
1)为对象分配存储空间。
2)构造对象。
3)从超类到子类对static成员进行初始化。
4)递归调用超类的构造方法。
5)调用子类的构造方法。
应用阶段(In Use)
在创建阶段之后,对象的状态就切换到了应用阶段。这个阶段的对象至少具有一个强引用,或者显示的使用软引用、弱引用或者虚弱引用。
不可见阶段(Invisible)
此时在程序中找不到对象的任何强引用,但是该对象仍可能被特殊的强引用GC Roots持有着(可达)。
不可达阶段(Unreachable)
在程序中找不到对象的人和强引用,并且垃圾收集器发现对象不可达。
收集阶段(Collected)
垃圾收集器已经准备好要对该对象的内存空间重新进行分配,这个时候如果该对象重写了finalize方法,则会调用该方法。
终结阶段(Finalized)
在对象执行完fnialize方法后仍然处于不可达状态时,或者对象没有重写finalize方法,则对象会进入终结阶段,并等待垃圾收集器回收该对象空间。
对象空间重写分配阶段(Deallocated)
垃圾收集器对对象的内存空间进行回收或者再分配,此时该对象会彻底消失。
七.垃圾收集算法
1.标记-清除算法
基本概念:垃圾清除算法将垃圾收集分为两个阶段:标记阶段(标记出可回收的对象)和清除阶段(回收被标记对象)。
缺点: 首先是标记和清除的效率都不高,还有就是容易产生大量不连续的内存碎片,碎片太多可能会导致后续没有足够的连续内存分配给较大的对象。
2.复制算法
基本概念: 它把内存空间划分为两个相等的区域,每次只使用其中一个区域。在垃圾收集时,遍历当前使用的区域,把存活的对象复制到另一个区域中,最后将当前使用区域的可回收对象进行回收。
缺点: 系统的使用内存减半,同时复制算法的效率和存活对象的数目有很大的关系,如果存活对象很少,则复制算法的效率就会很高。复制算法广泛用于新生代中。
3.标记-压缩算法
基本概念:首先标记内存中可以回收的对象,然后将所有存活的对象压缩到内存的一端,使它门紧凑的排列在一起,然后对边界以外的内存进行回收。标记-压缩算法被广泛应用于老年代中。
4.分代收集算法
分代的概念:分代收集算法会结合不同的收集算法来处理不同的空间。Java堆区的空间划分在Java虚拟机中,各种对象的生命周期会有着较大的差别,因此应该对不同的生命周期的对象采取不同的收集策略,根据生命周期的长短将它们分别放到不同的区域,并在不同的区域中采取不同的收集算法。Java堆区基于分代的概念,分为新生代和老年代。
分代收集: 根据Java堆区的空间划分,垃圾收集的类型分为两种:Minor Collection(新生代垃圾收集)和Full Collection(老年代收集)。