JVM基础概念作用类加载运行时数据区执行引擎本地方法
JVM的一些基础概念
目录如下
- JVM的一些基础概念
- JVM概念
- JVM的作用
- JVM的构成部分
- JVM类加载
- 类加载过程
- 加载
- 链接
- 初始化
- 类加载器(加载类的实现者)
- 引导类加载器
- 拓展类加载器
- 应用程序类加载器
- 双亲委派机制
- 运行时数据区
- 程序计数器
- 特点:
- Java虚拟机栈
- 栈的特点(后进先出)
- 本地方法栈
- Java堆内存
- 堆的概念和作用
- 堆内存区域分布
- 分区原因
- 对象在堆空间中分配的过程
- 堆空间的参数设置
- 方法区
- 方法区的概念与作用
- 方法区的大小设置
- 方法区的垃圾回收
- 本地方法接口
- 概念
- 什么是本地方法
- 为什么要用本地方法
- 执行引擎
- 作用
- 两次编译
- 解释器与编译器
- 解释器
- 编译器
- 为什么区分设计解释器和编译器
JVM概念
JVM是Java虚拟机(Java Virtual Machine)的缩写,是Java程序运行的基础
JVM负责加载字节码文件、管理内存、执行字节码指令、处理垃圾回收等工作。
JVM的作用
负责把字节码装载到虚拟机内部,把字节码转为机器码执行
JVM不仅可以执行java字节码文件,还可以执行其他语言编译后的字节码文件,是一个跨语音的平台
JVM的构成部分
1.类加载器(ClassLoader)
2.运行时数据区(Runtime Data Area)
3.执行引擎(Execution Engine)
4.本地库接口(Native interface)
程序在执行之前需要先将java代码转换成字节码(class文件)
JVM首先要将字节码通过类加载器(ClassLoader)将文件加载到内存中的运行数据区(Runtime Data Area)
字节码文件是JVM的一套指令规范,无法直接通过底层操作系统执行,因此需要特定的命令解析器,**执行引擎(Execution Engine)**将字节码翻译成底层系统的指令再交由CPU执行
翻译字节码的过程中需要调用**本地库接口(Native InterFace)**来实现整个程序的功能,本地库接口是由其他语言实现的
JVM类加载
作用:类加载系统负责把字节码文件(class文件)加载到内存中,存储在方法区中
字节码文件是模板,由模板创建n个对象
字节码文件加载到JVM中,被称为DNA元数据模板
类加载过程
加载
1.通过类名(地址)获取此类的二进制字节流
2.将字节流代表的存储结构转换为方法区的运行结构
3.在内存中生成Class对象,作为该类的访问入口
总结 : 使用流将硬盘上的字节码读取到内存中成为类
链接
初始化
类加载器(加载类的实现者)
类加载器可以分为使用java语言实现的和不使用java语言实现的
引导类加载器
不是用java写的,是使用c/c++编写实现的
用来加载java系统中的类,例如String之类的类,都是使用引导类加载器实现的
拓展类加载器
此加载器,由java语言实现,用于加载java8/jre/lib/ext目录下的类
应用程序类加载器
此加载器,由java语言实现,用于加载我们自己开发的程序类
双亲委派机制
当加载一个类时,.先让上级的类加载器去加载,优先加载系统中的类,知道启动类加载器,如果到达顶部的类加载器,当找到了该类则直接返回,若顶部类加载器找不到,再次向下委派,让字类类加载器去加载
在哪一级找到了,那就返回即可,如果向下找也没有找到,那么直接抛出类找不到异常ClassNotFoundException
好处:优先加载系统中的类,防止我们的类替换系统中的类
如何打破双亲委派机制?: 可以使用自定义的类加载器
运行时数据区
运行时数据区就是用来存储各种运行时产生的数据
方法区 堆 虚拟机栈 本地方法栈 程序计数器
程序计数器
程序计数器,用来记录每个线程执行的指令的位置,因为cpu要切换执行
特点:
程序计数器占用内存小,速度最快
每个线程都有自己的程序计数器,是线程私有的,生命周期与线程的生命周期一致
程序计数器存储当前正在执行的java方法的JVM指令地址
它是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
Java虚拟机栈
栈是运行时的单位,即栈解决程序的运行问题,即程序如何执行
栈是一个运行结构,主要用来执行使用java语言写的方法
栈的特点(后进先出)
栈是线程私有的,每个线程运行,都有一个自己的栈空间
栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
JVM直接对java栈的操作只有两个:调用方法入栈,执行结束出栈
栈中是会出现内存溢出的(递归调用越多,栈就越有可能溢出)
一个方法被调用后,在栈中称为是一个栈帧,保存局部变量,操作数栈,方法返回地址
本地方法栈
java虚拟机管理java方法的调用,本地方法栈管理本地方法的调用(native关键字修饰的方法,是由操作系统实现的方法) hashCode() read0() start0()
本地方法也是线程私有的,本地方法栈也是会出现内存溢出的问题
Java堆内存
堆的概念和作用
java中产生的对象都存储在堆空间中
堆空间是虚拟机中空间最大的一块区域,而且大小是可以调整的(运行时数据区,除了程序计数器区域不能调整,其他四个区域都可以对大小进行相应的调整)
堆空间被所有线程共享,是线程共有的
堆空间是会出现内存溢出的问题
堆空间是GC(Garbage Collection垃圾收集器)执行垃圾回收的重点区域。
堆内存区域分布
java8之后堆内存分为:
新生区 : 新生区分为Eden(伊甸园)区和 Survivor(幸存者)区
幸存者区分为:Survivor0(from),Survivor1(to)
老年区 : Old Gen
分区原因
将对象根据存活概率进行分区,将存活时间长的对象放到一个固定的区域
减少扫描垃圾时间以及GC频率,针对分类进行垃圾回收算法,对算法进行扬长避短
频繁回收新生代,较少回收老年代
对象在堆空间中分配的过程
1.新建的对象默认储存在Eden区
2.当垃圾回收时,把Eden中存活的对象移动到Survivor区,不再被引用的对象被垃圾回收
3.后续继续进行垃圾回收时,再把Eden区和Survivor0区中存活的区域移动到Survivor1区,每一次垃圾回收都需要保证有一个Survivor区是空闲的,减少内存碎片
4.当一个对象经历过15次垃圾回收后依然存活,那么将这个对象直接放入老年区,倘若有一个对象相对较大,则可以直接放入老年区
15次是一个默认的参数,可以通过设置-XX:MaxTenuringThreshold= 参数来调整次数,最大值为15次,因为在对象头有4bit位来记录回收次数,4bit位最多只能表示15
5.当老年代内存不足时,触发GC,会对老年代进行内存清理
6.若老年代执行了GC之后发现依然无法进行对象存储,会对堆进行GC,之后依然无法进行对象存储,就会出现OOM异常
Java.lang.OutOfMemoryError:Java heap space
堆空间的参数设置
以下是一些常用的堆空间的参数,更多的可以在官网进行查询
官网地址:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html -XX:+PrintFlagsInitial 查看所有参数的默认初始值
-Xms:初始堆空间内存
-Xmx:最大堆空间内存
-Xmn:设置新生代的大小
-XX:MaxTenuringTreshold:设置新生代垃圾的最大年龄
-XX:+PrintGCDetails 输出详细的 GC 处理日志
参数的设置也是JVM调优的一部分,根据实际使用的情况设置各参数的大小
方法区
方法区的概念与作用
方法区主要存储加载到内存中的类信息
方法区物理上与堆属于同一个空间,但是一般逻辑上进行区分称为元空间(Meta Space方法区),对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是与堆区分
方法区在JVM启动时被创建,实际物理内存空间中和Java堆区一样可以是不连续的
下图为方法区.栈,堆的交互关系
方法区的大小设置
方法区的大小可以进行设置,参数是-XX:MetaspaceSize,方法区的大小可拓展,方法区的空间一旦不足,就会触发FULL GC(整堆收集),会影响应用程序线程,一般情况下会将方法区的大小设置的尽量大
方法区是会出现内存溢出问题的
方法区是可以进行垃圾回族的
方法区的垃圾回收
方法区中的类什么时候会被卸载(方法区的垃圾回收)
1.该类及其字类的所有对象已经不存在了
2.加载该类的类加载器不存在了
3.该类的class对象不存在了
由于条件比较严苛,所以一般情况下可以认为类被加载后就不再释放了
本地方法接口
概念
用于对接调用的本地方法
什么是本地方法
被native修饰的方法就是本地方法
// new Object().hashCode(); //public native int hashCode();获取内存地址
// new FileInputStream("").read(); private native int read0()读硬盘数据
// new Thread().start(); private native void start0()把线程注册到内存中
为什么要用本地方法
与java之外的环境进行交互
java是应用层开发语言,无法直接对计算机底层进行调用操控,需要与java外部的环境进行交互,本地方法是一个接口,可以让java开发者无需了解java应用之外的繁琐细节
执行引擎
作用
可以将字节码指令解释/编译为机器码指令
两次编译
第一次编译是将.java文件编译为.class文件,此处的编译是开发阶段,与运行无关,可称为前端编译
第二次编译是运行时,在虚拟机中通过执行的引擎将字节码编译为机器码,称为后端编译
解释器与编译器
解释器
解释器采用不编译,逐行解释的执行方式
无需编译,可以实现立即的解释执行,但是效率相对较低
编译器
编译器是将字节码整体编译后执行
编译需要花费一定时间,但是编译后执行的效率高
为什么区分设计解释器和编译器
当程序启动后,解释器可以快速发挥作用,尽管它的效率较低,因为它的响应速度快,可以省去编译的时间,立即执行,
当程序启动一定时间后,编译器也会发挥更多作用,它会将一些热点代码编译后缓存起来,执行效率高,但是它需要花费一定的时间
所以将二者结合,在程序开始时使用解释器执行,等编译器编译完成后采用编译执行