当前位置: 首页 > article >正文

JVM的一些知识

JVM简介

JVM 是 Java Virtual Machine 的简称,意为 Java 虚拟机。
虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。常见的虚拟机:JVM、VMwave、Virtual Box。
JVM 和其他两个虚拟机的区别:

  1. VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器;
  2. JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进
    行了裁剪。
    JVM 是一台被定制过的现实当中不存在的计算机。

JVM 类加载的过程

  1. 加载
    Java程序的 .Java 文件, 通过 javac 编译成 .class 文件, 存储在硬盘上, 当运行 Java 进程的时候, jvm 需要读去 .class 文件里面的内容,
  2. 验证
    验证读到的 .class 文件的数据是否正确, 是否合法 (在 Java 的标准文档中, 明确定义了 .class 文件的格式是什么样的)
  3. 准备
    根据读取到的内容尾类的静态变量分配内存, 将其设置为初始值比如 boolean 就设置成 false, 对象引用就设置成 null, 不会进行赋值的操作
    (创造一个内存空间, 全部设为初始值)
  4. 解析
    Java虚拟机将常量池内的符号引用替换成了直接引用 (符号引用相当于就是一个名字, 比如 String s = “hello” 符号引用类似 hello, 直接引用可以理解成内存地址比如 0x19)
  5. 初始化
    针对类对象做最后的初始化操作, 执行静态成员的赋值语句 (此时静态代码块以及父类也会在这一阶段被加载)

死亡对象的判断算法

1. 引用计数法

给对象增加一个计数器, 每当有一个地方引用这个对象, 计数器就 +1, 当引用失效的时候就 -1, 一旦对象的计数器变成了 0, 就代表失效
但是主流的 jvm 都没有使用引用计数法, 主要是无法解决循环引用的问题

public class Test {

public Object instance = null;

private static int _1MB = 1024 * 1024;

private byte[] bigSize = new byte[2 * _1MB];

public static void testGC() { Test test1 = new Test(); Test test2 = new Test(); test1.instance = test2; test2.instance = test1; test1 = null;

test2 = null;

// 强制jvm进行垃圾回收

System.gc(); }

public static void main(String[] args) {

testGC(); }

}  

比如以上的情况, 就会发生无法回收的情况, 但是其实也引入了回路检测的算法, 可以解决这种问题

2. 可达性分析算法

在这里插入图片描述

通过一系列成为 GC Root 的对象, 进行不断向下搜索
(类似 jvm 手上有一份名单, 然后所有的 root 进行向下搜索, 如果发现有无法到达的对象, 即可说明该对象是不可用的)

GC Root 有很多个比如

  1. 栈上的局部变量
  2. 元数据区的静态变量
  3. 常量池引用指向的对象

垃圾回收算法

1. 标记清除算法

先标记再清除, 根据上述可达性算法, 先从所有的 GC Root 遍历一遍, 标记为存活对象, 之后遍历清除没有被标记的对象, 从而回收内存

优点

  1. 实现简单
  2. 无需移动对象

缺点

清除后会产生内存碎片, 导致内存的利用率变低

2. 复制算法

内存区域直接划分成两块, 只使用其中一块, 单进行垃圾回收的时候, 将存活对象从当前区域, 直接移动到另一块区域, 对当前区域进行整体回收操作

优点

空间碎片减少

缺点

  1. 空间利用率较低
  2. 对象多的时候, 复制成本大

3. 标记整理算法

一样通过 GC Root 对所有对象进行可达性的判断, 标记一下对象是否存活, 对存活的对象进行整理, 连续排列, 清理出连续的空间

优点

解决了内存碎片的问题

缺点

移动整理对象会产生搬运的开销

4. 分代回收算法

在这里插入图片描述

解析
  1. 分成三个区, 分别是伊甸区、幸存区、老年区
  2. 开始 new 出来的对象, 都会先放在伊甸区 (伊甸区是比较大的) , 根据经验规律 90% 的对象都是活不过第一轮 GC, 所以剩下活下来的会放到其中一个幸存区, 然后清空另一个幸存者区和伊甸区, 下一轮对象加入伊甸区再次 GC 后, 将幸存的对象和存放上一轮 GC 的存活对象放入到另一个幸存者区, 然后回收伊甸区和另一个幸存区的空间
  3. 当经历过好几轮的 GC 之后, 就会把多轮存活的对象转移到老年代
  4. 老年代的 GC 频率相较伊甸区的 GC 频率要低很多

优点

  1. 提高了回收的效率
  2. 减少了 STW 的时间

缺点

堆内存进行分区管理, 较为复杂

双亲委派模型

输入: 类的全限定名 , 类似于 java.lang.String

在这里插入图片描述

目的

防止用户写的类, 把标准库的类给覆盖掉, 保证标准库的类优先级最高, 扩展库其次, 第三方库的优先级最低

JVM内存区域的划分

1. 程序计数器, 保存了下一条要执行的指令的地址 (下一条指令是 Java的字节码)
2. 堆, jvm 最大的空间, new 出来的对象都在堆上
3.
1. Java 虚拟机栈, 运行 Java 代码的方法调用关系, 存储函数中的局部变量, 函数的形参, 函数之间的调用关系
2. 本地方法栈, jvm 中 c++ 代码的函数调用关系
4. 元数据区(方法区), 代码中涉及到的类信息, 以及类的 static 属性, 静态变量

在这里插入图片描述

堆(线程共享)

堆是所有线程共享的, 分为新生代和老生代, 在堆上的 GC 操作在上述的分代算法有介绍

方法区(元数据区) (线程共享)

用来存储被虚拟机加载的类信息、静态变量等数据

Java虚拟机栈(线程私有的)

每个方法在执行的时候都会创建栈帧存储局部变量, 方法出口等等, 常说的堆内存, 栈内存指的就是虚拟机栈

在这里插入图片描述

  1. 局部变量表: 存储方法参数和局部变量
  2. 操作栈: 每个方法会生成一个先进后出的操作栈
  3. 动态链接: 指向常量池的方法引用
  4. 方法返回地址: PC 寄存器的地址

本地方法栈(线程私有)

本地方法栈和虚拟机栈类似, 但是 Java 虚拟机栈是给 jvm 使用的, 本地方法栈是给本地方法使用的

程序计数器(线程私有)

用来记录当前线程执行的行号

当前线程如果执行的是一个 Java 的方法, 这个计数器记录的是正在执行的虚拟机字节码指令的地址; 如果执行的是一个 Native 方法, 计数器的值为空 (因为调用的是其他语言的代码, 计数器并没有意义)

!!! 计数器是唯一一个在 jvm 规范中没有规定任何 OOM 情况的区域 (也就是内存溢出)


http://www.kler.cn/a/592319.html

相关文章:

  • 浏览器工作原理深度解析(阶段四):排版系统与布局计算一、引言
  • 基于百度翻译的python爬虫示例
  • C++高频(五)之虚函数
  • pipost 如何提升团队协作效率 [特殊字符]
  • 【SoC基础】单片机常用总线
  • spring 配置websocket
  • 好数 第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组
  • 5.2 Alpha to coverage in Depth
  • MySQL 调优
  • Vue的watchEffect的追踪逻辑
  • Docker 内部通信(网络)
  • BGP 路由选路、负载分担与发布策略
  • Android7 Input(一)Android Input服务初始化
  • Wireshark 远程 tcpdump使用配置
  • YOLOv11 目标检测
  • C程序设计(第五版)及其参考解答,附pdf
  • Android开发中的Native 调试
  • Python Matplotlib面试题精选及参考答案
  • 【Linux网络-数据链路层】以太网(以太网帧格式|MAC地址+模拟一次性局域网通信+MTU)+ARP协议
  • WebSocket 中的条件竞争漏洞 -- UTCTF Chat