JVM
- JVM&JRE&JDK
- 概念
- JDK
- JDK:java development toolskit,包含java开发工具和jre。
- JRE
- JRE:java runtime enviroment,包含java运行环境和jvm。
- JVM
- JVM:java virtual machine,java虚拟机,用于保证java的跨平台。
- 关系
- JVM体系
- ①:类装载子系统
- 概念:
什么是JVM?
JVM即java virtual machine (java虚拟机)是一个虚构出来的计算机,是一种专门用于计算设备的规范,java是根据“可在任何地方运行一次写入”的概念开发的。这个概念需要给予jvm环境。基于jvm后,java语言在不同平台上运行时不需要重新编译,java语言使用jvm屏蔽了与具体平台相关的信息,使得java语言编译程序只需生成在jvm上运行的字节码,这样就可以在多种平台上不加修改的运行了。
- 类加载过程
- 加载
- 首先,通过一个类的全限定名来获取此类的二进制字节流
- 其次,将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 最后,在java堆中生成一个代表这个类的Class对象,作为方法区这些数据的访问入口。总的来说就是查找并加载类的二进制数据
- 验证:(确保被加载类的正确性,确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全)
- 完成4个阶段的验证动作
- ①:文件格式的验证(验证字节流是否符合Class文件格式的规范)
- 例如:是否以魔术0xCAFEBABE开头
- 例如:主次版本号是否在当前虚拟机的处理范围之内
- 例如:常量池中的常量是否有不被支持的类型
- 例如:是否有不符合UTF-8编码的数据
- 例如:是否有被删除和附加的信息
- ②:元数据的验证(对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求)
- 例如:这个类是否有父类
- ③:字节码验证(通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的)
- ④:符号引用验证(确保解析动作能正确执行)
- 准备:(为类的静态变量分配内存,并将其初始化为默认值)
- 解析:(把类中的符号引用转换为直接引用)
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
- 什么是符号引用?
- 是用一组字符引用一个类、常量等,符号因为是以一定的规范规定在class的文件中的。
- 什么是直接引用?
- 就是我们常说的引用,就是内存引用
- 初始化
- (1)类中的static代码在类加载时就执行,且只执行这一次。(先静态)
- (2)如果一个类A继承了类B,在类A实例化时要先加载类B,在执行类A的构造函数时要先执行父类B的构造函数,所以此时要实例化类B。(先父后子)
- (3)一个类实例化之前要先实例化类的成员变量(非静态),再执行该类的构造函数。(先成员后构造)
- java类实例化详细顺序
- 1.父类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
- 2.子类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
- 3.父类的【普通成员变量】和【普通代码块】,按在代码中出现的顺序依次执行。
- 4.执行父类的构造方法。
- 5.子类的【普通成员变量】和【普通代码块】,按在代码中出现的顺序依次执行。
- 6.执行子类的构造方法。
- 使用
- 程序之间的相互调用
- 卸载
- 即销毁一个对象,一般情况下中有JVM垃圾回收器完成。代码层面的销毁只是将引用置为null
- 类加载器分类
- 启动类加载器/引导类加载器(Bootstrap ClassLoader)
- 这个类加载使用 C/C++语言实现的,嵌套在 JVM 内部;
- 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar、resource.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类;
- 并不继承自 java.lang.ClassLoader,没有父加载器;
- 加载扩展类和应用类加载器,并指定为他们的父类加载器;
- 出于安全考虑,BootStrap 启动类加载器只加载包名为 java、javax、sun等开头的类;
- 扩展类加载器(Extension ClassLoader)
- Java 语言编写,由 sum.misc.Launcher$ExtClassLoader 实现;
- 派生于 ClassLoader 类;
- 父类加载器为启动类加载器;
- 从java.ext.dirs 系统属性所指定的目录中加载类库,或从JDK的安装目录的 jre/lib/ext 子目录(扩展目录)下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载;
- 自定义类加载器(User-Defined ClassLoader)
- 从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类加载器,但是 Java 虚拟机规范却没有这么定义,而是将所有派生于抽象类 ClassLoader类加载器都划分为自定义类加载器
- ClassLoader 常用方法【ClassLoader 类,它是一个抽象类,其后所有的类加载器都继承自 ClassLoader(不包括启动类加载器)】
- getParent():返回该类加载器的超类加载器
- loadClass(String name):加载名称为 name的类,返回结果为 java.lang.Class 类的实例
- findClass(String name)查找名称为 name的类,返回结果为 java.lang.Class 类的实例
- findLoadClass(String name)查找名称为 name的已经被加载过的类,返回结果为 java.lang.Class 类的实例
- defineClass(String name, byte[] b, int off,int len)把字节数组 b 中的内容转换为一个Java 类,返回结果为 java.lang.Class 类的实例
- resolveClass(Class<?> c)连接指定的一个 Java 类
- 获取 ClassLoader的途径
- 方式一:获取当前类的 ClassLoader:clazz.getClassLoader()
- 方式二:获取当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader();
- 方式三:获取系统的 ClassLoader
ClassLoader.getSystemClassLoader();
- 方式四:获取调用者的 ClassLoader
DriverManager.getCallerClassLoader();
- 类的主动使用和被动使用
- 主动使用:
- ①:创建类的实例
- ②:访问某个类或接口的静态常量,或者对该静态变量赋值。
- ③:调用类的静态方法
- ④:反射
- ⑤:初始化一个类的子类
- ⑥:jvm启动时被标明为启动类的类
- ⑦:jdk7开始提供的动态语言支持(java.lang.invoke.MethodHandle 实例的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 句柄对应的类没有初始化,则初始化。)
- 被动使用:除过7种主动使用,其他使用java类的方式都被开作是对类的被动使用,都不会导致类的初始化。
- ②:运行时数据区
- 方法区
- ①:生命周期
- 方法区的创建和虚拟机一起启动,也就是说它的生命周期是和虚拟机(jvm)一致的。
- ②:存储的内容
- 存储已被jvm加载的类型信息(比如:接口、类、枚举)
- 方法信息(方法的执行就是字节码指令的执行)运行时变量、静态变量(1.7以后字符串常量被移到堆里面,1.6以前是在方法区),编译后的代码。
- 用来保存一些静态数据结构,这种也叫静态数据结构区
- 运行时常量池也属于方法区中的部分
- ③:非堆的概念
- 方法区在逻辑上属于堆(heap)的一部分,所以为了更好的内存对象的垃圾回收,所以也将方法区称为“非堆”
- ④:本质:方法区本质是一个接口,不同的版本有不同的实现
- jdk8以后方法实现叫:meta space(元空间)
- jdk8之前方法实现叫:perm space(永久代)
- ⑤:内存溢出
- 当方法区出现内存不足时,会报出内存溢出的异常,当内存溢出时整个jvm就会挂掉
- 虚拟机栈
- ①:生命周期:
- 与线程的生命周期保持一致
- ②:存储的内容:
- 栈帧(frames):
- 栈帧的组成
- 局部变量表
- 整个方法体中以数组的方法来保存这个方法的入参信息和方法中定义的成员属性
- 同样的方法的本地变量表,成员方法的0号对象就是this
- 操作数栈
- 本地变量表中的元素本身不参与计算,将自己本身的元素复制一份数据放到操作数栈中,通过操作数栈的压栈和出栈计算,然后将计算结果更新到本地变量表中的下标地址
- JVM是栈式的指令结构
- 指令大部分是0地址指令,其执行过程完全依赖与jvm中的操作数栈
- 由于是0地址指令,所以生成的指令空间占用更少
- 不受限于物理的硬件资源,可移植性强,更好的实现跨平台性
- 寄存器指令架构(Davlik)
- 1、指令采用地址指令
- 堆
- 本地方法栈
- 程序计数器
- ③:执行引擎
- ⑤:本地方法接口
- ⑥:垃圾回收
- 双亲委派机制
- 应用场景:
- java虚拟机对class文件采用的是(按需加载)的方式,也就是说需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个class文件时,jvm采用的是双亲委派机制,即(把请求交由父类处理,它是一种任务委派模式)
- 模型图解:
- 工作原理:
- ①:如果一个类加载器收到类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
- ②:如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终会抵达顶层的启动类加载器
- ③:如果父类加载器可以完成类加载器任务,就返回成功,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载
- 优点:
- ①:避免类的重复加载
- ②:保护程序安全,防止核心api被随意修改
- 沙箱安全机制
- 概念:
- 子主题 1
- 案例解释:
- 自顶一个一个java.lang包下的String类,但是在加载自定义String类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中的java.lang.String.class),此时报错信息会提示没有main方法,就是因为加载的是rt.jar包中的String。这样就可以保证对Java核心源代码的保护
- JMM(java内存模型)
- 字节码执行过程
- GC(垃圾回收算法)
- JVM的性能监控和故障定位
- JVM调优