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

初识JVM

这篇博客主要介绍JVM中的内存区域划分、双亲委派模型、垃圾回收机制。

一、JVM简介

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

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

JVM 是一台被定制过的现实当中不存在的计算机。

二、内存区域划分

JVM是虚拟的,仿造了操作系统在进程运行时的区域划分。

JVM内存区域划分,相当于就是JVM进程从操作系统申请到了一部分空间,然后将这个空间划为不同模块,执行不同功能。

就像房间的布局:

JVM划分出的四个核心区域:

(一)程序计数器(线程私有)

它是一个很小的空间,用于记录cpu指令执行到哪一个地址了。

(二)方法区(线程共享)

方法区的作用:用来存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据的。 在《Java虚拟机规范中》把此区域称之为“方法区”,而在 HotSpot 虚拟机的实现中,在 JDK 7 时此区域叫做永久代(PermGen),JDK 8 中叫做元空间(Metaspace)。

当我们写一段代码,代码执行的流程是:

.java->.class->加载到内存中,元数据区就是用来存放当前类被加载好的数据。

(三)java虚拟机栈(线程私有)

Java 虚拟机栈的作用:保存方法的调用关系。Java 虚拟机栈的生命周期和线程相同,Java 虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。咱们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈。 Java 虚拟机栈中包含了以下 4 部分:

(四)堆(线程共享)

堆的作用:程序中创建的所有对象都在保存在堆中。

 堆里面分为两个区域:新生代和老生代,新生代放新建的对象,当经过一定 GC 次数之后还存活的对象会放入老生代。新生代还有 3 个区域:一个 Endn + 两个 Survivor(S0/S1)。

三、JVM类加载

(一)类加载的步骤

1.加载

根据类的全限定名(包名+类名,形如java.lang.String),找到并打开文件,读取文件内容到内存里。

2.验证

校验.class文件读到的内容是否是合法的,并且把这里的内容转化成结构化的数据。

3.准备

给类对象申请内存空间并设置类变量初始化。

public static int value = 123;

初始化value的值为0,并非123。

4.解析

从.class文件里解析出来的字符串常量,放到内存空间里,并进行初始化,也就是初始化常量的过程。

5.初始化

针对刚才谈到的类对象进行最终的初始化,对类对象的的各种属性进行填充(包括这个类中的静态成员,如果这个类还有父类,而且父类还没加载,此环节也会触发父类的类加载)。

(二)双亲委派模型

提到类加载机制,不得不提的一个概念就是“双亲委派模型”。 站在 Java 虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用 C++ 语言实现,是虚拟机自身的一部分;另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。 站在 Java 开发人员的角度来看,类加载器就应当划分得更细致一些。自 JDK 1.2 以来,Java 一直保持着三层类加载器、双亲委派的类加载架构器。

⭐️双亲委派模型的过程

进行类加载,通过全限定类名找 .class 文件时,会从 ApplicationClassLoader 作为入口开始。


然后把加载类的任务,委托给 “父亲”ExtensionClassLoader 来进行。ExtensionClassLoader 也不会立即进行查找,而是同样委托给 “父亲”BootstrapClassLoader 来进行。


BootstrapClassLoader 也想委托给 “父亲”,但由于没有 “父亲”,只能自己进行类加载。它会根据类名,在标准库范围内查找是否存在匹配的 .class 文件。若 BootstrapClassLoader 没有找到,会把任务还给 “孩子”ExtensionClassLoader,接下来由 ExtensionClassLoader 负责找 .class 文件的过程。找到就加载,没找到就把任务还给 “孩子”ApplicationClassLoader。最后由 ApplicationClassLoader 负责找 .class 文件,找到就加载,没找到就抛出异常。

⭐️双亲委派模型的优点

  1. 避免重复加载类:比如A类和B类都有一个父类C类,那么当A启动时就会将C类加载起来,那么在B类进行加载时就不需要在重复加载C类了。
  2. 安全性:使用双亲委派模型也可以保证了 Java 的核心 API 不被篡改,如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类,而有些 Object 类又是用户自己提供的因此安全性就不能得到保证了。

四、JVM中的垃圾回收机制(GC)

在JVM划分的内存区域中,程序计数器中的数据在线程销毁的时候就自然释放掉了。

栈中的栈帧在方法结束,栈帧就释放了。

元数据区存放的是类加载好的数据,一般不会释放。

GC回收的是JVM中堆中的数据。

(一)找到垃圾

有以下几种方式:

1.引用计数

引用计数描述的算法为:

给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已"死"。引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个不错的算法。比如Python语言就采用引用计数法进行内存管理。

但是引用计数不能解决循环引用的问题。

2.可达性分析

此算法的核心思想为:通过一系列称为 "GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为 "引用链",当一个对象到 GC Roots 没有任何的引用链相连时 (从 GC Roots 到这个对象不可达) 时,证明此对象是不可用的。以下图为例:

对象 Object5-Object7 之间虽然彼此还有关联,但是它们到 GC Roots 是不可达的,因此他们会被判定为可回收对象。
在 Java 语言中,可作为 GC Roots 的对象包含下面几种:

  1. 虚拟机栈 (栈帧中的本地变量表) 中引用的对象;
  2. 方法区中类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中 JNI (Native 方法) 引用的对象。

(二)回收垃圾

JVM使用可达性分析算法,就可以将要回收的垃圾进行标记,标记之后就能进行回收了。

1.标记-清除算法

把标记为垃圾的内存,直接进行释放。

因为在申请内存的时候,是申请的连续的空间,如果直接进行释放,就会造成内存碎片问题。

2.复制算法

当为对象申请空间时,额外申请一倍的空间。

申请的对象只会在一个空间里,当GC时会标记清除所有的垃圾,再把剩下的移到右边的新空间里,这样就能确保对象所在的内存是连续的。

缺点:内存的空间利用率是很低的。

一但不是垃圾的对象较多,那么复制空间的成本就很高。

3.标记-整理算法

在标记-清除算法的基础上,回收垃圾之后,将不是垃圾的对象进行“插空转移”,解决了内存碎片化问题。缺点是复制成本依旧很大。

4.分代算法

当前 JVM 垃圾收集都采用的是 "分代收集 (Generational Collection)" 算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。一般是把 Java 堆分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用 "标记 - 清理" 或者 "标记 - 整理" 算法。

哪些对象会进入新生代?哪些对象会进入老年代?

  • 新生代:一般创建的对象都会进入新生代;
  • 老年代:大对象和经历了 N 次(一般情况默认是 15 次)垃圾回收依然存活下来的对象会从新生代移动到老年代。

新生代区又有两个部分:伊甸区(Eden)和幸存区(S0、S1)。

新创建的对象就先放在伊甸区,经过一次GC后,存活下来的就放入幸存区。

在幸存区中执行的就是复制算法,当在幸存区中经过GC达到一定次数后,就进入老年区。

http://www.dtcms.com/a/362130.html

相关文章:

  • Linux之Docker虚拟化技术(三)
  • STM32项目分享:基于单片机的图书馆座位监测系统
  • docker-nacos-v3
  • 告别发票山,拥抱高效流:一位财务经理的“解放”宣言
  • 第四次工业革命简史:从图灵测试到ChatGPT的AI革命
  • ModelScope 开发环境配置指南
  • leetcode笔记
  • OpenCV轻松入门_面向python(第四章色彩空间类型转换)
  • 从全栈开发到微服务架构:一次真实面试的深度解析
  • Ansible 常用模块归纳总结
  • 【Axure高保真原型】表格增删改查(含下拉列表)
  • Swift 解法详解:LeetCode 368《最大整除子集》
  • SQL Server从入门到项目实践(超值版)读书笔记 25
  • 使用 Google OR-Tools 轻松解决复杂优化问题(如排程优化)
  • HarvardX TinyML小笔记2(番外3:数据工程)
  • Node.js版本管理工具 || 全配置安装
  • Claude AI 因编写勒索软件和开展勒索活动而被滥用
  • Agent落地元年:谁在成为最坚实的土壤?
  • 【前端】跨域
  • 懒加载详细讲解
  • 在Linux系统上第一次创建java项目并运行
  • `[特殊字符]LeetCode每日一题 1792. 最大平均通过率(打卡第一天)`
  • 在 React Native 层禁止 iOS 左滑返回(手势返回/手势退出)
  • Unity 串口通讯2 硬件SDK 开发[数据监听,按键监听]
  • 人工智能——课程考核
  • Python OpenCV图像处理与深度学习:Python OpenCV图像几何变换入门
  • 线程池发生了异常该怎么处理?
  • Groovy 的核心语法
  • 计算机视觉与深度学习 | 传统图像处理技术的未来发展前景分析
  • 算法练习——169.多数元素