JVM知识总结
Java的跨平台性
Java的跨平台性,能够实现跨平台的是Java程序,而不是JVM,JVM是用C/C++开发,是编译之后的机器码,无法实现跨平台,不同的平台下需要安装不同的JVM
Java程序编译之后会生成字节码文件,即.class文件,JVM就是负责将字节码文件翻译成特定平台下的机器码然后运行,在不同的平台上安装对应的JVM,就可以来运行字节码文件,通过这一“中间层”,在不同平台上运行Java程序,实现“一次编译,到处运行”的目的。
JVM概念
JVM即Java Virtual Machine,Java虚拟机
JVM是Java的核心和基础,在Java编译器和OS平台之间的虚拟的处理器,是一种利用软件方法来实现的抽象的计算机的下层的操作系统和硬件平台,在其上面执行java的字节码程序
JVM有之间的完善的硬件架构,如处理器,堆栈,寄存器,还有相应的指定系统,使用JVM可以实现开发和操作系统的无关,实现跨平台性。
JDK/JVM/JRE是什么关系
JRE(Java Runtime Environment ,Java的运行环境),也就是Java平台,所有的Java程序都是要在JRE 下才能运行,
JDK(Java Development Kit,Java开发工具包),程序开发过程中用来编译、调试Java程序用的Java开发工具包,JDK的工具包也是Java程序,也是需要在JRE上运行,为了保证JDK的独立性和完整性,在JDK安装过程中,JRE也是安装的一部分,所以在JDK安装的过程中有一个jre的目录,主要存放的是JRE的文件
JVM(Java Virtual Machine,Java虚拟机)是JRE的一部分,是英国虚拟出来的计算机,通过实际的计算机来仿真模拟各种计算机的功能
JVM生命周期
启动和消亡
JVM负责运行Java程序,启动一个Java程序时,一个虚拟机也就诞生了,当程序关闭时,JVM实例也就随之消亡
JVM运行的起点:
JVM实际是通过调用某个初始类的main()方法来运行一个java程序,而这个main()方法必须是共有的(public)、静态的(static)、返回值为void,并且能够接受一个字符串数组作为参数,任何拥有这样main方法作为java程序运行的起点
JVM两种线程:
守护线程和非守护线程
只要有Java程序在继续运行,即非守护线程在运行,守护线程依赖于非守护线程,当程序运行结束退出之后,守护线程也会退出,继而JVM虚拟机实例也会自动退出
JVM工作过程
JVM的运行过程涉及到三个子系统:
类装载子系统(Class Loader SubSystem)
运行时数据区(Runtime Data Areas)
执行引擎(Execution Engine)
类加载子系统
作用是将字节码文件加载到JVM中,在类第一次被使用时,是需要初始化类文件
装载:功能就是来加载类,使用到了三个类加载器,分别是Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader
链接:
验证:字节码验证器将验证生成字节码是否正确,如果验证失败,将得到验证错误
准备:对于所有的静态变量,进行分配内存并给定默认值,tmp分配内存,并给定默认值null
解析:将所有的符号内存引用替换为方法区的原始引用
初始化:静态变量将被赋予原始值,静态代码块将被执行 tmp=10
运行时数据区域
方法区:类级别数据、静态变量的应用,线程共享
堆区:对象及其实例变量和数据的存储位置,线程共享
栈区:程序运行过程中会使用栈区,线程私有
本地方法栈:保存本地方法的信息,调用JNI,线程私有
程序计数器:线程私有
执行引擎
将分配给运行时数据区域的字节码将由执行引擎来执行,执行引擎读取字节码并逐个进行执行
类加载器
负责将字节码加载到内存中
各加载器的职责:
Bootstrap ClassLoader:启动类加载器
负责加载
J
A
V
A
H
O
M
E
的
j
r
e
/
l
i
b
/
r
t
.
j
a
r
里
面
所
有
的
c
l
a
s
s
E
x
t
e
n
s
i
o
n
C
l
a
s
s
L
o
a
d
e
r
:
扩
展
类
加
载
器
负
责
加
载
j
a
v
a
平
台
中
扩
展
功
能
的
一
些
j
a
r
包
,
包
括
JAVA_HOME的jre/lib/rt.jar里面所有的class Extension ClassLoader:扩展类加载器 负责加载java平台中扩展功能的一些jar包,包括
JAVAHOME的jre/lib/rt.jar里面所有的classExtensionClassLoader:扩展类加载器负责加载java平台中扩展功能的一些jar包,包括JAVA_HOME中除jre/lib/*.jar或者-DJava.ext.dirs=XXX指定路径下的jar包
Application ClassLoader:应用类加载器
负责加载classpath中指定的jar和目录中的class
双亲委派模型
通过双亲委派模型来加载类的过程如下:
1、当前类加载器会从自己已经在家的类中查询是否此类已经加载,如果已经加载则返回原来已经加载的类
2、如果没有找到,就会委托父类加载器去加载,父类加载器采用同样的策略,查看自己已经加载的类中是否包含这个类,有则返回,没有的话就委托给父类去加载,直到委屈给启动类加载器为止,因为启动类加载器父类为空,到启动类加载器就不会再往上进行委托
3、如果启动类加载器加载失败,就会使用扩展类尝试加载,继续失败的话则会使用应用类加载器来加载,继续失败就会抛出一个异常:ClassNotFoundException
首先查找自身已加载的类->委托父类->尝试在记载器指定路径下加载
双亲委派的好处
1、安全性
避免用户自己编写的类动态替换java的一些核心类
如果不采用双亲委派模型的加载方式进行类的加载工作,那我们就可以随时使用自定义的类来动态替换Java核心API中定义的类。例如:如果黑客将“病毒代码”植入到自定义的String类当中,随后类加载器将自定义的String类加载到JVM上,那么此时就会对JVM产生意想不到的“病毒攻击”。而双亲委派的这种加载方式就可以避免这种情况,因为String类已经在启动时就被引导类加载器进行了加载。
2、避免类的重复加载
因为JVM判定两个类是否是同一个类,不仅仅根据类名是否相同进行判定,还需要判断加载该类的类加载器是否是同一个类加载器,相同的class文件被不同的类加载器加载得到的结果就是两个不同的类。
类加载的详细过程
JVM将字节码文件加载,分为以下几步:
加载、连接过程(验证、准备、解析)、初始化
1、验证:文件格式、元数据、字节码、符号引用验证
2、准备:为类的静态变量分配内存,并将其初始化为默认值(基本类型初始化为0,String类型等初始化默认值为null)
3、解析:把类中的符号引用转化为直接引用
初始化:将类的静态变量赋予初始化,对静态代码块做相应的执行
类在同一个JVM实例中只被加载一次,而类的使用可以多次使用
创建型的设计模式:在系统中(JVM实例)只存在一个对象实例
public class Single2 {
private static Single2 s = null;
private Single2() { }
static {
s = new Single2();
}
public static Single2 getInstance() {
return s;
}
}
如何保证当前的类只被实例化一次,当前的这个类是可以保证只创建一个实例的
当前的单例实现上将当前要实例的对象s设置为私有的,通过一个public方法getInstance来获取当前对象的实例,而且当前的s对象和方法getInstance都是定义为静态的
当第一次调用Single2.getInstance()方法时,该类在当前JVM中不存在,尝试来加载当前类Single2时,就进行类的加载过程,在准备阶段完成静态变量s的分配内存并给定默认值null,当在初始化阶段,完成静态代码块的执行,即完成了s的初始化,在整个加载过程中就完成了当前对象s的实例化,当调用Single2.getInstance()方法返回时,这个阶段是在使用阶段已经完成了类的加载初始化,不管当前的方法多次调用,当前的类的Single2的初始化只进行一次,这个是类加载机制来保证(JVM保证同一个类被加载一次)