深入理解 Java JVM

文章目录

    • 📕1. JVM简介
    • 📕2. JVM运行流程
    • 📕3. JVM运行时数据区
    • 📕4. JVM类加载
        • ✏️4.1 类加载过程
        • ✏️4.2 双亲委派模型
        • ✏️4.3 破坏双亲委派模型
    • 📕5. JVM垃圾回收机制(GC机制)
        • ✏️5.1 判断死亡对象的算法
        • ✏️5.2 垃圾回收算法

📕1. JVM简介

JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机。 虚拟机是指通过软件模拟的具有完整硬件功能的、运⾏在⼀个完全隔离的环境中的完整计算机系统。JVM 是⼀台被定制过的现实当中不存在的计算机。

📕2. JVM运行流程

JVM 是 Java 运⾏的基础,也是实现⼀次编译到处执⾏的关键,那么 JVM 是如何执⾏的呢?

程序在执⾏之前先要把java代码转换成字节码(class⽂件),JVM ⾸先需要把字节码通过⼀定的⽅式把⽂件加载到内存中,⽽字节码⽂件是 JVM 的⼀套指令集规范,并不能直接交个底层操作系统去执⾏,因此需要特定的命令解析器将字节码翻译成底层系统指令再交由CPU去执⾏,⽽这个过程中需要调⽤其他语⾔的接⼝来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。
在这里插入图片描述
总结来看, JVM 主要通过分为以下 4 个部分,来执⾏ Java 程序的,它们分别是:

  1. 类加载器(ClassLoader)
  2. 运⾏时数据区(Runtime Data Area)
  3. 执⾏引擎(Execution Engine)
  4. 本地库接⼝(Native Interface)

📕3. JVM运行时数据区

JVM 运⾏时数据区域也叫内存布局,但需要注意的是它和 Java 内存模型((Java Memory Model,简称 JMM)完全不同,属于完全不同的两个概念,它由以下 5 ⼤部分组成:
在这里插入图片描述

🌈 1.

堆的作⽤:程序中创建的所有对象都在保存在堆中(除了对象的实例属性外还有对象的头信息)

在这里插入图片描述

堆⾥⾯分为两个区域:新⽣代和⽼⽣代,新⽣代放新建的对象,当经过⼀定 GC 次数之后还存活的对象会放⼊⽼⽣代。新⽣代还有 3 个区域:⼀个 Eden + 两个 Survivor(S0/S1)。

在这里插入图片描述

垃圾回收的时候会将 Eden 中存活的对象放到⼀个未使⽤的 Survivor 中,并把当前的 Endn 和正在使⽤的Survivor 清除。

🌈 2. 虚拟机栈

Java 虚拟机栈的作⽤:Java 虚拟机栈的⽣命周期和线程相同,Java 虚拟机栈描述的是 Java ⽅法执⾏的内存模型:每个⽅法在执⾏的同时都会创建⼀个栈帧(Stack Frame)⽤于存储局部变量表、操作数栈、动态链接、⽅法出⼝等信息。咱们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈。

在这里插入图片描述
通俗来说,栈帧里面包括方法的参数,方法中的局部变量,方法结束后返回值结果,方法结束后跳转回的地址。

🌈 3. 线程计数器

程序计数器的作⽤:描述Java程序要运行的下一个字节码的位置。

程序计数器是⼀块⽐较⼩的内存空间,可以看做是当前线程所执⾏的字节码的⾏号指⽰器。

如果当前线程正在执⾏的是⼀个Java⽅法,这个计数器记录的是正在执⾏的虚拟机字节码指令的地址;如果正在执⾏的是⼀个Native⽅法,这个计数器值为空。

🌈 4. 元数据区(Java8之前被称为方法区)

我们在Java代码中会创建类,基于类创建对象,创建的对象会有内存空间进行保存,同理类也要有内存空间进行保存。Java中提出了类对象的概念,通过一个特殊的对象,表示一个类的基本信息。在元数据区中,存放的就是类的对应指令。

在元数据区中会保存:
1.类的名字是啥
2.类继承的父类是啥
3.实现的接口是啥
4.有啥属性(属性的名字,类型,限定修饰符)
5.有啥方法(方法的名字,参数列表,返回值,限定修饰符)
6.类静态成员
7.静态常量

元数据区的内容Java代码干预不了,在Java代码中写多少类,元数据区的内容就确定了。

🌈 5. 总结

在这里插入图片描述

📕4. JVM类加载

类加载是JVM从最开始读取的 .class文件,到最终构造完成类对象的整个过程。是把“类”从硬盘上加载到内存中。

✏️4.1 类加载过程

对于⼀个类来说,它的⽣命周期是这样的:

在这里插入图片描述
其中前 5 步是固定的顺序并且也是类加载的过程,其中中间的 3 步我们都属于连接,所以对于类加载来说总共分为以下⼏个步骤:

  1. 🌈加载

在加载 Loading 阶段,Java虚拟机需要完成以下三件事情:

1.通过⼀个类的全限定类名(包名+类名)来获取定义此类的⼆进制字节流
2.将这个字节流所代表的静态存储结构转化为方法区的运⾏时数据结构。
3.在内存中⽣成⼀个代表这个类的Class对象,作为⽅法区这个类的各种数据的访问⼊⼝

  1. 🌈验证

验证是连接阶段的第⼀步,校验上一步读出来的.class文件是否是合法的。 这⼀阶段的⽬的是确保Class⽂件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当作代码运⾏后不会危害虚拟机⾃⾝的安全。如果验证过程中发现某个地方的格式出现问题,就需要及时报错,告知给程序员。

  1. 🌈准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段。此处申请的空间是一个“未初始化”的空间,空间上的每个字节都是0。

//比如此时有这样一行代码:
private static int value = 123;
//此时value的值是0,而不是123
  1. 🌈解析

解析过程是针对代码中的常量进行初始化,也就是把.class文件中的常量也加载到内存中。
对于上述代码中value,此时value的值就是123了。

  1. 🌈初始化

初始化阶段,Java 虚拟机真正开始执⾏类中编写的 Java 程序代码,执⾏类构造器⽅法。

✏️4.2 双亲委派模型

双亲委派模型是用于类加载过程中的第一个环节,根据类的全限定类名(包名+类名)找到对应的.class文件。

🚩什么是双亲委派模型?

如果⼀个类加载器收到了类加载的请求,它⾸先不会⾃⼰去尝试加载这个类,⽽是把这个请求委派给⽗类加载器(每一个类加载器都有一个parent属性,记录了自己的父亲是谁,这个是无法篡改的,写死在JVM源码的)去完成,每⼀个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当⽗加载器反馈⾃⼰⽆法完成这个加载请求(它的搜索范围中没有找到所需的类)时,⼦加载器才会尝试⾃⼰去完成加载。

在这里插入图片描述

JVM自带了三种类加载器:

  1. Bootstrap ClassLoader :启动类加载器,负责在Java的标准库中进行查找
  2. Extension ClassLoader:扩展类加载器,负责在Java的扩展库中进行查找
  3. Application ClassLoader:应用程序类加载器,负责在Java的第三方库或者项目中进行查找

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🚩双亲委派模型的优点:

  1. 避免重复加载类:比如 A 类和 B 类都有⼀个⽗类 C 类,那么当 A 启动时就会将 C 类加载起来,那么在 B 类进⾏加载时就不需要在重复加载 C 类了。
  2. 安全性:使⽤双亲委派模型也可以保证了 Java 的核⼼ API 不被篡改。比如我自己写了一个String类,和标准库的String类正好重复了,此时JVM加载的仍然是标准库的String类,而不是我写的String类。保证了安全性。
✏️4.3 破坏双亲委派模型

双亲委派模型是可以被打破的,程序员在特定的条件下可以实现自己的类加载器,自己实现的类加载器可以让它遵守双亲委派模型,也可以不遵守双亲委派模型。这种情况会在编写库或者框架类代码时出现,平时我们写业务代码并不会遇见。

📕5. JVM垃圾回收机制(GC机制)

上面我们介绍了JVM运行时的数据区,对于程序计数器,虚拟机栈,本地方法栈的生命周期与其对应的线程有关,随着线程的结束进而销毁。因此GC主要是回收堆上的对象。在 Java 中,所有的对象都是要存在内存中的(也可以说内存中存储的是⼀个个对象),因此我们将内存回收,也可以叫做死亡对象的回收。GC回收并不是以字节为单位进行回收,而是以对象为单位进行回收。

✏️5.1 判断死亡对象的算法
  1. 🚩 引用计数算法

给对象增加⼀个引⽤计数器,每当有⼀个地⽅引⽤它时,计数器就+1;当引⽤失效时,计数器就-1;
任何时刻计数器为0的对象就是不能再被使⽤的,即对象已"死"。

引⽤计数法实现简单,判定效率也⽐较⾼,在⼤部分情况下都是⼀个不错的算法。⽐如Python语⾔就
采⽤引⽤计数法进⾏内存管理。

但是,引入计数算法有两个弊端:

a) 消耗额外的空间大 : 假如对象只有4字节,计数器有2字节,额外浪费50%的内存空间,造成内存浪费。

b) 循环引用问题 :

在这里插入图片描述

  1. 🚩可达性分析算法(Java所使用的方案)

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

在这里插入图片描述

对象Object5-Object7之间虽然彼此还有关联,但是它们到GC Roots是不可达的,因此他们会被判定为可回收对象。

在Java语⾔中,可作为GC Roots的对象包含下⾯⼏种:

  1. 虚拟机栈(栈帧中的本地变量表)中引⽤的对象
  2. ⽅法区中类静态属性引⽤的对象
  3. ⽅法区中常量引⽤的对象
  4. 本地⽅法栈中 JNI(Native⽅法)引⽤的对象
✏️5.2 垃圾回收算法
  1. 🚩标记-清除算法

“标记-清除"算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段 : ⾸先标记出所有需要回收的对象,在标记完成后统⼀回收所有被标记的对象。后续的收集算法都是基于这种思路并对其不⾜加以改进⽽已。

"标记-清除"算法的不⾜主要有两个 :

  1. 效率问题:标记(标记就是可达性分析找到碎片的过程)和清除(直接释放这部分的内存)这两个过程的效率都不⾼
  2. 空间问题:标记清除后会产⽣⼤量不连续的内存碎⽚,空间碎⽚太多可能会导致以后在程序运⾏中需要分配较⼤对象时,⽆法找到⾜够连续内存⽽不得不提前触发另⼀次垃圾收集。

在这里插入图片描述

  1. 🚩复制算法

"复制"算法是为了解决"标记-清理"的效率问题。它将可⽤内存按容量划分为⼤⼩相等的两块,每次只使⽤其中的⼀块。当这块内存需要进⾏垃圾回收时,会将此区域还存活着的对象复制到另⼀块上⾯,然后再把已经使⽤过的内存区域⼀次清理掉。这样做的好处是每次都是对整个半区进⾏内存回收,内存分配时也就不需要考虑内存碎⽚等复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运⾏⾼效。算法的执⾏流程如下图 :
在这里插入图片描述

复制算法很好的解决了内存碎片问题,但仍然存在两个弊端:

  1. 内存浪费比较多
  2. 如果存活的对象比较多,复制的开销会非常大
  1. 🚩标记-整理算法

这个方案类似于顺序表删除元素(我们会采用搬运元素的方法),这个方案虽然能解决内存碎片问题,也能避免复制算法的内存浪费,但是,搬运对象的成本也是比较高的。

在这里插入图片描述

  1. 🚩分代算法

分代算法和上⾯讲的 3 种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策略,从⽽实现更好的垃圾回收。这就好⽐中国的⼀国两制⽅针⼀样,对于不同的情况和地域设置更符合当地的规则,从⽽实现更好的管理,这就是分代算法的设计思想。

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

我们将整个堆空间,分为新生代和老生代。新⽣代中98%的对象都是"朝⽣⼣死"的,所以并不需要按照1 : 1的⽐例来划分内存空间,⽽是将内存(新⽣代内存)分为⼀块较⼤的Eden(伊甸园)空间和两块较⼩的Survivor(幸存者)空间,每次使⽤Eden和其中⼀块Survivor(两个Survivor区域⼀个称为From区,另⼀个称为To区域)。当回收时,将Eden和Survivor中还存活的对象⼀次性复制到另⼀块Survivor空间上,最后清理掉Eden和刚才⽤过的Survivor空间。当Survivor空间不够⽤时,需要依赖其他内存(⽼年代)进⾏分配担保。

HotSpot默认Eden与Survivor的⼤⼩⽐例是8 : 1,也就是说Eden:Survivor From : Survivor To =8:1:1

HotSpot实现的复制算法流程如下:

  1. 当Eden区满的时候,会触发第⼀次Minor gc,把还活着的对象拷⻉到Survivor From区;当Eden区再
    次触发Minor gc的时候,会扫描Eden区和From区域,对两个区域进⾏垃圾回收,经过这次回收后还存
    活的对象,则直接复制到To区域,并将Eden和From区域清空。
  2. 当后续Eden⼜发⽣Minor gc的时候,会对Eden和To区域进⾏垃圾回收,存活的对象复制到From区域,
    并将Eden和To区域清空。
  3. 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决
    定,这个参数默认是15),最终如果还是存活,就存⼊到⽼年代

哪些对象会进⼊新⽣代?哪些对象会进⼊⽼年代?

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

🌰举个例子:

在这里插入图片描述
当我们投简历时,简历会进入伊甸区,此时会收到非常多的简历。但是只有少数简历会经过挑选进入面试(从伊甸区进去幸存区),每一轮面试都会淘汰掉很多人,每次进入一轮面试就相当于存活过一轮GC,进入幸存区,当我们经过所有的面试拿到offer进入公司,就意味着我们进入了老生代。但是进入公司后也会有年终考核之类的评比,不过是考核时间长了。但是也有一类人能量非常强大,不用面试直接就能进入公司入职。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90955.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90955.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux内核高效之道:Slab分配器与task_struct缓存管理

前言 在Linux内核中,进程创建与销毁是最频繁的操作之一。想象一下:当系统每秒需要处理成百上千次fork()和exit()调用时,如何保证task_struct(进程描述符)的分配与释放既快速又不产生内存碎片?这就是Slab分配…

双esp8266-01之间UDP透传传输,自定义协议

使用AT模式的透传,串口打印的数据包含pd1,4,数据打印的数据不是直接将数据打印出来,包含了pd1,4,特殊字符,针对想要直接开机直接透传,打印数据且按照自主协议帧头的功能进行开发。1.server程序:/*************SERVER**…

BGP 路由优选属性(7)【MED】官方考试综合实验题【bgp】【acl】【ip-prefix】【route-policy】【icmp 环路】精讲

目录 一、MED 属性介绍 二、实验 2.1 实验目的 2.2 拓扑图 2.2 实验说明 2.3 配置脚本 2.4 验证配置 2.5 问题分析 2.7 题目需求解析 2.8 场景 1:只允许在 AS12 上操作 2.9 场景 2:只允许在 AS34 上操作 正文 一、MED 属性介绍 MED 全称 mu…

html-初级标签

一.浏览器能识别的标签 1.1 head标签里的编码和title <head><meta charset"UTF-8"><title>Title</title> </head>1.2 标题 <body><h1>Welcome to my website</h1><h2>Welcome to my website</h2><…

【八股消消乐】Kafka集群 full GC 解决方案

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;本专栏《八股消消乐》旨在记录个人所背的八股文&#xff0c;包括Java/Go开发、Vue开发、系统架构、大模型开发、具身智能、机器学习、深度学习、力扣算法等相关知识点&#xff…

《Java Web程序设计》实验报告二 学习使用HTML标签、表格、表单

目 录 一、实验目的 二、实验环境 三、实验步骤和内容 1、小组成员分工&#xff08;共计4人&#xff09; 2、实验方案 3、实验结果与分析 4、项目任务评价 四、遇到的问题和解决方法 五、实验总结 一、实验目的 1、HTML基础知识、基本概念 2、使用HTML标签、表格进行…

jenkins使用Jenkinsfile部署springboot+docker项目

文章目录前言一、前期准备二、编辑构建文件二、Jenkins构建总结前言 前面使用Jenkinsfile部署了前端vue项目&#xff0c;接着学习Jenkinsfile部署springboot项目。 一、前期准备 已经安装好centos,并且安装了jenkins和docker。本地新建springboot并上传到gitee上。 二、编辑…

使用ESM3蛋白质语言模型进行快速大规模结构预测

文章目录ESM3介绍ESM3在线使用本地使用api批量预测ESM相较于AlphaFold的优势ESM3介绍 ESM3是由EvolutionaryScale&#xff08;前Meta团队&#xff09;开发的一款蛋白质大语言模型&#xff0c;于2025年以《用语言模型模拟 5 亿年的进化》为题正式发表在Science上 文章链接: htt…

PostgreSQL 时间/日期管理详解

PostgreSQL 时间/日期管理详解 引言 PostgreSQL是一款功能强大的开源关系型数据库管理系统&#xff0c;在时间/日期管理方面具有独特的优势。本文将详细介绍PostgreSQL中时间/日期数据类型及其相关功能&#xff0c;帮助读者更好地理解和应用时间/日期管理。 时间/日期数据类型 …

Agent篇

Agent包含哪些模块&#xff0c;实现了什么功能Agent 就像一个多功能的接口&#xff0c;它能够接触并使用一套工具。根据用户的输入&#xff0c;Agent会规划出一条解决用户问题的路线&#xff0c;决定其中需要调用哪些工具&#xff0c;并调用这些工具。Agent 大语言模型规划记忆…

利用 MySQL 进行数据清洗

利用 MySQL 进行数据清洗是数据预处理的重要环节&#xff0c;以下是常见的数据清洗操作及对应 SQL 示例&#xff1a;1. 去除重复数据使用 ROW_NUMBER() 或 GROUP BY 识别并删除重复记录。-- 查找重复记录&#xff08;以 user_id 和 email 为例&#xff09; WITH Duplicates AS …

【MySQL笔记】事务的ACID特性与隔离级别

目录1. 什么是事务&#xff1f;2. 事务的ACID特性&#xff08;重要&#xff09;3. 事务控制语法4. 隔离级别与并发问题1. 什么是事务&#xff1f; 事务&#xff08;Transaction&#xff09;是由一组SQL语句组成的逻辑单元&#xff0c;这些操作要么全部成功&#xff0c;要么全部…

Mock 数据的生成与使用全景详解

Mock 数据的生成与使用全景详解 在后端开发过程中,真实数据往往受限于业务进度、隐私保护或接口未完成等因素,无法及时获取。这时,Mock数据(模拟数据)就成为开发、测试、联调不可或缺的利器。本文将从Mock数据的意义、常用场景、主流工具、实战案例到最佳实践,带你全面掌…

HTML 标题标签

需求&#xff1a;在网页显示六级标题标签。代码&#xff1a;//需求&#xff1a;在网页显示六级标题标签。 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title></head><body><h1>一级标题&l…

(限免!!!)全国青少年信息素养大赛-算法创意实践挑战赛小学组复赛(代码版)

选择题部分在 C 中&#xff0c;以下代表布尔类型的是&#xff08;  &#xff09;选项&#xff1a;A. double B. bool C. int D. char答案&#xff1a;B解析&#xff1a;C 中布尔类型的关键字为bool&#xff0c;用于存储逻辑值true或false。执行以下程序&#xff0c;输出的…

编译器优化——LLVM IR,零基础入门

编译器优化——LLVM IR&#xff0c;零基础入门 对于大多数C开发者而言&#xff0c;我们的代码从人类可读的文本到机器可执行的二进制文件&#xff0c;中间经历的过程如同一个黑箱。我们依赖编译器&#xff08;如GCC, Clang, MSVC&#xff09;来完成这项复杂的转换。然而&#x…

react中为啥使用剪头函数

在 React 中使用箭头函数&#xff08;>&#xff09;主要有以下几个原因&#xff1a;1. 自动绑定 this传统函数的问题&#xff1a;在类组件中&#xff0c;普通函数的this指向会根据调用方式变化&#xff0c;导致在事件处理函数中无法正确访问组件实例&#xff08;this为undef…

JavaSE-多态

多态的概念在完成某个行为时&#xff0c;不同的对象在完成时会呈现出不同的状态。比如&#xff1a;动物都会吃饭&#xff0c;而猫和狗都是动物&#xff0c;猫在完成吃饭行为时吃猫粮&#xff0c;狗在完成吃饭行为时吃狗粮&#xff0c;猫和狗都会叫&#xff0c;狗在完成这个行为…

TDengine 使用最佳实践(2)

TDengine 使用最佳实践&#xff08;1&#xff09; 安装部署 目录规划 软件安装 参数配置 时钟同步 验证环境 集群部署 写入查询 连接方式 数据写入 数据查询 运维巡检 运维规范 数据库启停 状态检查 运维技巧 日常巡检 数据库升级 故障排查 故障定位 日志调试 故障反馈 关于 T…

如何通过公网IP访问部署在kubernetes中的服务?

背景说明我们有些私有化部署的项目&#xff0c;使用k8s来承载服务&#xff0c;通过ingress-nginx转发外部的请求到集群。有时候业主的域名没有申请下来&#xff0c;我们会配置临时的域名&#xff0c;测试同事配置主机hosts来完成功能验证&#xff0c;等功能验证完毕后&#xff…