文章目录
JVM
JDK: Java语言的软件开发工具包
JRE:包含Java核心库和支持文件
JVM:JDK和JRE的重要组成部分,Java运行平台
前言
JVM:Java运行平台
-
JVM(Java Virtual Machine):Java 虚拟机,用来执行 class 文件、保证 Java 语言跨平台
-
Java 虚拟机可以看做是一台虚拟的计算机,和真实的计算机一样,它有自己的指令集 以及各种运行时内存
-
JVM 就是一个字节码翻译器,它将字节码文件翻译成各个系统对应的机器码,确保字节码文件能在各个系统上正确的运行
1 JVM体系概述
1.1 JVM体系结构
1.1.1 类加载系统
在运行程序时首次运行类时进行加载->连接->初始化
作用:加载类文件
①加载
- Bootstrap Class Loader:引导类加载器:主要用于加载核心类库
- Extension Class Loader: 加载扩展类库
- Application Class Loader:应用程序类库:(当前项目的类库)加载的是项目中classpath路径下的类
②连接
- Verify:验证,检查字节码文件是否存在问题
- Prepare:准备,为所有静态内容分配内存空间和默认值
- Resolve:解析
③初始化
- Initialzation:初始化,初始化所有静态变量并执行静态代码块
1.1.2 运行时数据区
作用:存储数据
运行时数据区包含:
① 方法区(Method Area):存储所有类,相关信息,属性,常量池(字符串常量),静态变量,常量等
- 方法区在 JVM 中只有一个是多个线程所共享的。
②堆区(Heap Area):所有的对象、变量、数组都存在在该区域
- 堆区在 JVM 中只有一个是多个线程所共享的
③虚拟机栈(Stack Area):线程私有的,每个线程会创建单独的运行时栈,局部变量都存在于栈内存中
- 每个方法调用会在运行时栈中创建一个"栈帧"
④程序计数器(PC Registers):线程安全的,每个线程都有单独的计数器,主要用于保存当前要执行的指令号,一旦指令执行,计数器将更新到下一条指令号
⑤本地方法栈(Native Method stack):线程安全的,每个线程单独创建自己的本地方法区,主要用于保存本地方法信息
1.1.3 执行引擎
① 解释器:读取字节码,读一行,解释一行
② 即时编译器:由于一段代码被多次调用,都需要解释执行,即使编译器可以将这样的字节码编译为本地代码,用于多次调用时提高系统星能
③垃圾回收器:收集并删除未引用对象,可以调用System.gc()
来触发垃圾回收器进行垃圾回收,垃圾回收器只回收new关键字创建的对象,使用finalize方法清理其他对象。
④本地方法接口:与本地方法库交互,提供执行引擎所需要的本地库
⑤ 本地方法库:(本地方法接口实现类),执行引擎所需要方法库集合
2 程序计数器
作用:保存当前要执行的指令的地址(指令号),一旦执行指令,计数器将更新到下一条指令的地址(指令号)
-
每个线程都有单独的计数器,它属于线程私有的,用于存储当前线程要执行的代码指令地址 (行号)
程序计数器是线程安全的(运行数据区中唯一一个不会发生内存泄漏的区域)
JVM 指令通过
javap –v test.class
反解析获得(javap反解析工具,它的作用就是根据 class 字节码文件,反解析出当前类对应的 code 区(汇编指令)、本地变量表、异
常表和代码行偏移量映射表、常量池等等信息。)
执行过程:
-
计数器读入要执行的 JVM 指令号
-
CPU 获取计数器中的指令号
-
CPU 根据从计数器中获取的指令号执行相对应的指令
-
计数器更新要执行的下一个 JVM 指令号
-
3 虚拟机栈
Java方法执行的内存模型
- 每个执行的线程都有自己的虚拟机栈,线程的程序执行是通过方法调用实现的
- 在虚拟机栈中,每个方法对应一个栈帧。
- 每个线程只有一个活动栈帧,就是当前执行的方法,该栈帧处于栈顶。
- 栈可能发生内存溢出
- 栈帧包括:局部变量表,操作数栈,动态链接,方法返回值等信息
3.1 栈帧
- 局部变量表(重点)
用来存储局部变量
- 基本数据类型:直接存储他们的值(long和double占两个局部变量)
- 引用数据类型:存的指向变量的引用。
局部变量大小在编译期间就可以确定其大小,程序执行期间不会局部变量表大小是不会改变的
- 操作数栈
存储当前正在计算的数据
-
当一个方法开始执行时操作数栈创建并且为空
随着方法的执行,会从局部变量表或对象实例的字段中复制所需要的数据到操作数栈,再将计算的进行将栈的元素出栈到局部变量表或返回给调用者
- 动态链接
指向运行时常量池的引用
- 在 class 文件中,描述一个方法调用或访问其成员变量时通过符号引用来表示的,动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用
- 方法返回地址
方法返回地址是方法调用的返回,包含正常返回(有返回值)和异常返回(无返回值),不同的返回值类型有不同的指令
无论方法采用何种方式退出,在方法退出后都需要返回到方法调用的位置,程序才能继 续执行,方法返回时可能需要在当前栈帧中保存一些信息,用来帮助它恢复上层方法的执行 状态
3.2 栈内存溢出
当栈内存不足时就会发生栈内存溢出错误,可以通过设置栈的大小来改变栈所占空间的 大小
-
栈帧多过导致栈内存溢出 (方法递归调用)
-
栈帧过大导致栈内存溢出(方法中局部变量占用空间过大)
栈溢出错误:java.lang.StackOverflowError
- 设置栈内存大小
-
栈内存大小默认值:
Linux:1024KB
,Mac:1024KB
Windows:依赖虚拟机大小(1024kB)
4 本地方法栈
本地方法栈的功能和特点类似于虚拟机栈,也是线程私有的,不同的是虚拟机栈服务的 是 Java 方法,而本地方法栈服务的是 Native 方法
- 使用
native
修饰的方法为本地方法,没有方法体; - 它和 Java 方法不同,本地方法不是由Java实现,而由有本地的动态库提供,可以是任意的语言实现,如 C,C++;
5 堆
JVM内存空间所占区域最大的一块
- 堆内存被多个线程共享
- 堆内存是用于存放实例对象和数组的区域
堆内存溢出是Java非常常见的故障
5.1 堆内存划分
堆内存分区管理是便于垃圾回收
在进行堆内存垃圾回收时,堆内存中所有对象都不能使用,程序会等同于暂停,暂停后进行回收显然不行。
堆内存回收,是通过将不再存活的对象进行标记,来进行垃圾回收;
回收后,会产生内存碎片,会对内存进行整理空闲
- 老年代:存活时间久的对象
- 年轻代(逻辑):存活时间短的对象,年轻代又分为,生成区和幸存区
- 生成区:新创建对象的存储的区域,当生成区满后,会标记不再存活的对象触发MinorGC清理生成区中不再存活的对象
- 幸存区(逻辑):就是经过垃圾回收后活下来的对象存储的区域,幸存区又分为FromSpace和ToSpace
- FromSpace:就是存活下来的对象存储的区域,此时这些对象会被标记为1(躲过一次垃圾回收)
- 再次创建新的对象,不影响其他区域
-
当from区满后,再次创建对象,此时对from区进行垃圾回收,存活下来的对象存储到ToSPace
-
ToSpace:经from区再次存活下来的对象存储到该区,并标记为2(意为躲过两次垃圾回收),此时生成区存活下来的对象也存储在该区,标记为1
-
to区满后,对to区进行垃圾回收,再次存活下来的对象存储在from区并进行标记
-
依次,如上过程,当一个对象标记为15(躲过15次垃圾回收),就会转到 老年代
- to区和from区总是有一个是空的,用来做交换
- 存活15次两种情况①该对象重要,经常会被使用 ②该对象无法被清理
- 老年代里的对象:也不是都是存活15次的,如果某次新生成的对象存活下来的对象过多,to区和from区经过依次垃圾回收,to区和from区仍然无法容纳,此时老年区会触发内存担保机制,将这些对象都转移到老年代
- 但是老年代会有风险,当老年代空间满后,也会触发一次垃圾回收
Minor GC : 清理年轻代
Major GC : 清理老年代
Full GC : 清理整个堆空间,包括年轻代和永久代
所有 GC 都会停止应用所有线程。
5.2 堆内存溢出
-
堆的所占用的内存大小是可以扩展的,使用“-Xms”和“-Xmx”来控制堆的最小和最大内存
-
会发生内存泄漏: 是指程序中已动态分配的堆内存由于某种原因程序(僵尸对象)未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果,内存泄漏最终会导致内存溢出。
-
堆内存中会不断生成新对象,不断的被使用,如果不使用垃圾回收,就可能发生内存溢出
-
内存溢出:指程序在申请内存时,没有足够的内存空间供其使用。
申请5kb内存但内存只剩2kb
JVM 堆内存常用参数
参数 | 描述 |
---|---|
-Xms | 堆内存初始大小,单位 m、g |
-Xmx(MaxHeapSize) | 堆内存最大允许大小,一般不要大于物理内存的 80% |
-XX:PermSize | 非堆内存初始大小,一般应用设置初始化 200m,最大 1024m 就够了 |
-XX:MaxPermSize | 非堆内存最大允许大小 |
-XX:NewSize(-Xns) | 年轻代内存初始大小 |
-XX:MaxNewSize(-Xmn) | 年轻代内存最大允许大小,也可以缩写 |
-XX:SurvivorRatio=8 | 年轻代中 Eden 区与 Survivor 区的容量比例值,默认为 8,即 8:1 |
-Xss | 堆栈内存大小 |
System.gc()
:清理对象
finalize()
:对象清理前被回收,主要用于清理对象所占用的资源
可以通过-XX:+ PrintGCDetails
JVM设置打印垃圾清理日志
堆内存溢出错误: java.lang.OutOfMemoryError: Java heap space
6 方法区
- 方法区被多个线程共享
一般用于存储,类相关信息,常量,静态变量,常量池(字符串常量), 即时编译器编译后的代码缓存,属性,方法信息等。。。
类相关信息(类class、接口interface、枚举enum、注解annotation):
① 这个类型的完整有效名称(全名=包名.类名)
② 这个类型直接父类的完整有效名(对于interface或是java.lang.0bject,都没有父类)
③这个类型的修饰符(public,abstract,final的某个子集)
④这个类型直接接的一个有序列表
方法信息
① 方法名称
② 方法的返回类型(或void)
③方法参数的数量和类型(按顺序)
④ 方法的修饰符(public,private,protected,static,final,synchronized, native,abstract的一个子集)
⑤ 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
⑥ 异常表( abstract和lnative方法除外)每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
- 方法区在逻辑上是堆的一部分,但在具体实现上不强制方法区的位置,不同的虚拟机厂 商可以有不同的实现,如 JDK1.8 之前使用永久代实现,1.8 后使用元空间实现。
- 学习来自于西安加中实训