【JVM】JVM相关机制

1. JVM内存区域划分

1.1 内存区域划分简介

内存区域划分:实际上JVM也是一个进程,进程运行时需要向操作系统申请一些系统资源(内存就是典型的资源),这些内存空间就支撑着后续Java程序的运行,而这些内存又会根据不同的应用场景被划分为不同的空间,这就叫做"内存区域划分",例如下图所示:

1.2 不同区域的应用场景

JVM内存区域大致分为四个部分:堆、栈、程序计数器、元数据区

  1. :代码中使用new关键字创建的对象(不是引用)都会存放在堆上,而且对象中持有的非静态成员变量也存放在堆上
  2. :栈又被分为"本地方法栈"和"虚拟机栈",主要包含代码中的局部变量以及方法的调用关系,需要注意本地方法栈是指JVM内部通过C/C++等语言编写的代码中的调用关系和局部变量(一般来说谈到栈,指代的是虚拟机栈)
  3. 程序计数器:这是相对来说区域较小的空间,专用用来存放下一条要执行的Java代码的地址,与x86CPU中的eip等寄存器类似
  4. 元数据区:在1.8之前也被称为"方法区","元数据"是计算机中一个常用术语,通常是起辅助性质、描述性质的属性,主要包含类的信息、方法的信息(一个程序有哪些类、一个类有哪些方法、每个方法中包含哪些指令)

如果面试官提问:“了解Java中的堆和栈吗?”,这个时候一定要先反问面试官:“您说的是数据结构中的堆和栈还是JVM内存区域中的堆和栈”

特点小结:

  • 其中栈和程序计数器可能在程序中持有多份(每个线程占有一份),而堆和元数据区所有线程共享一份

1.3 经典笔试题

给出如下代码:

class Test {private int n;private static int m; 
}public class Main {public static void main(String[] args) {Test t = new Test();}
}

问:上述代码中,变量n、m、t各自处于JVM内存中的哪个区域?
答:其中n在堆上,m在元数据区中,t在栈上

  • 变量n是对象中持有的非静态成员变量,因此在"堆"上
  • 变量m是对象中持有的静态成员变量,又被称为"类属性",因此存放在"元数据区"
  • 变量t是方法中的局部变量,是一个引用类型(不是对象本身),因此存放在"栈"上

区分一个变量究竟处于哪一块内存区域归根到底是看变量的类型,究竟是局部变量还是静态成员变量还是实例成员变量!

2. 类加载机制

2.1 类加载的过程

类加载:指的是Java进程运行的时候,需要把.class文件从硬盘上,读取到内存中,并进行一系列校验解析的过程
类加载的过程大体上可以分为如下五个步骤(网上有些说法是三个步骤,这个情况及时把3、4、5步骤整合到一起了)

  1. 加载:把硬盘上的.class文件找到(涉及双亲委派模型,后续进行介绍),打开文件,读取文件内容(二进制文件)
  2. 验证:当前需要确保读取到的二进制文件内容,是合法的.class文件(字节码文件)格式

具体的验证依据,在Java的虚拟机规范中有明确说明

image.png如上图所示,我们可以简单分析一下其中部分字段含义:

  • magic:也被称为"magic number"(魔幻数字),广泛应用于各种二进制格式文件中,用来标识当前的二进制文件是哪种类型
  • u4、u2:表示当前字段是一个由多少字节构成的无符号整数,例如u4就代表4字节的无符号整数,u2代表2字节的无符号整数
  • major_version、minor_version:主版本和次版本,平常所说的Java8、11、17都是日常中使用的版本,但是JVM内部还有另一套版本体系,此处用来验证版本是否符合要求
  1. 准备:给类对象申请内存空间,此时申请到的内存空间,里面的默认值为全0(这个阶段类对象中的静态成员变量的值也是0了)

  2. 解析:主要是针对类中的字符串常量进行处理,是Java将常量池内部符号引用替换为直接引用的过程,也就是初始化常量的过程,此处.class文件中填充给s的内容可以看做是"符号引用"

    比如说类中有一个字符串常量String s = “hello”,那么这个字符串是否需要存储在.class文件中呢?是一定需要进行存储的,但是文件没有"地址"这样的概念,于是可以存储一个类似于"地址偏移量"的概念,当.class文件加载到内存中时,就具有了"地址",于是s中的内容就可以替换为真实的地址了

  3. 初始化:针对类对象完成后续的初始化(例如执行静态代码块的逻辑、可能还会触发父类的加载)

2.2 双亲委派机制

双亲委派机制:首先需要明确,“双亲委派"机制是在上述五步骤中 加载 环节生效的!它描述了如何在硬盘上查找.class文件的策略
类加载器:在JVM中用于执行类加载的过程中,有一个专门的模块就叫做"类加载器”(ClassLoader),通过给定全限定类名,如java.lang.String,就可以找到对应的.class文件,JVM的默认类加载器有三个(可以自定义),分别称为"BootstrapClassLoader"、“ExtensionClassLoader”、“ApplicationClassLoader”

  • BootstrapClassLoader:负责查找标准库当中的目录
  • ExtensionClassLoader:负责查找扩展库当中的目录
  • ApplicationClassLoader:负责查找当前项目的目录以及第三方库的目录

上述三个类加载器,存在着"父子关系"(不是继承中的父子关系,而是类似于"二叉树",孩子节点中有一个parent引用指向自己的父亲节点),下面我们就来介绍查找项目目录org.example.MyClass为例介绍"双亲委派机制"的工作流程

  1. ApplicationClassLoader作为入口出发,开始工作
  2. ApplicationClassLoader不会立刻搜索自己负责的目录,而是会将搜索任务交给父加载器
  3. ExtensionClassLoader也不会立刻搜索自己负责的目录,而是会将搜索任务交给父加载器
  4. BoostrapClassLoader发现自己没有父加载器,于是搜索自己的负责目录(标准库目录),没找到则返回给自己的子类加载器
  5. ExtensionClassLoader搜索自己的负责目录(扩展库目录),没找到则继续返回给自己的子加载器
  6. ApplicationClassLoader搜索自己的负责目录(项目目录以及第三方库),此时找到了,就打开文件进行读取,如果此时还未找到,就抛出ClassNotFoundException异常信息,表明类加载失败

该种设定方式,可以有效避免因自己命名的类路径与标准库类冲突而导致标准库功能失效!

注意:上述JVM的"双亲委派机制"只是JVM提供的默认类加载器的默认遵守标准,如果实现一个自定义的类加载器,完全可以打破此规则,此时就不适用"双亲委派机制"了

3. GC垃圾回收机制

3.1 GC背景介绍

垃圾回收机制的引入:相信大家都学过C/C++这样的语言,其中malloc/free这样的函数就是用来进行动态内存管理的函数,此处申请到的内存的生命周期伴随整个进程,这对于服务器程序而言是相当不友好的!试想一下如果一个请求就要通过malloc申请一块内存,但是不使用free函数释放,那么终究会导致服务器内存不够用,这就是典型的 “内存泄漏” 问题

事实上很容易出现忘记手动调用free函数的情况,因为一段程序中一旦业务逻辑十分复杂,执行过程中很容易出现异常、return语句提前结束,就存在安全隐患!

因此大佬们就在探究,是否可能做到让程序来执行自动回收内存这样的机制呢?Java就属于早期便支持垃圾回收机制这样的语言,程序会自动判定某个内存是否还会被使用,如果不使用就自动释放

背景介绍:C/C++这样的语言为什么不补充垃圾回收机制呢?C语言比较"摆烂",自然没有引入新技术的动力,而C++标准委员会的大佬们认为GC这种机制会影响程序的执行效率以及引入额外的系统开销,这与C++追求性能到极致的初衷违背,例如说:引入GC会导致有些程序的正常业务逻辑暂停执行,业界称为"STW(Stop The World)"问题

3.2 GC工作流程

首先我们需要明确,内存区域中哪些部分需要进行GC:

  • 程序计数器:不需要进行GC
  • 栈:不需要GC,因为栈中的局部变量出了作用域就会被回收,这是栈的特性,与GC无关
  • 元数据区:不需要GC,因为通常只有"类加载"的过程,没有"类卸载"的说法
  • 堆:是GC的主战场

这里的GC,我们通常指的是回收 对象 来达到释放内存的目的,我们需要明确以下两个问题:1、如何识别出对象是"垃圾";2、被标记为垃圾的对象如何进行回收

3.2.1 如何识别"垃圾"

由于在Java中我们都是通过 引用 的方式来使用对象的,所以我们只需要判断当前对象是否存在引用指向,就可以判断出这个对象是否是"垃圾",我们常用的判断方法有以下两种:1、引用计数;2、可达性分析

3.2.1.1 引用计数

引用计数:通过给每个对象分配额外的内存空间,用来保存当前对象存在多少个引用指向
例如有如下程序:

class Test {}public class Main {public static void main(String[] args) {Test t1 = new Test();Test t2 = t1;}
}

此时内存空间可以简单表示为下图:

由于垃圾回收机制中专门的扫描线程,会获取到每个对象的引用计数情况,当发现引用计数为0时,就标记为"垃圾",有此时堆上的对象具有两个引用指向,即t1和t2,因此此时引用计数器的值为2,然后将上述main方法的代码修改一下:

public class Main {public static void main(String[] args) {Test t1 = new Test();Test t2 = t1;t1 = null; // 新增代码t2 = null; // 新增代码}
}

此时引用计数情况就会置为0!

此时发现该对象的引用计数为0,就可以将对象标记为垃圾,然后后续进行释放了!但是引用计数机制存在以下致命问题:

  1. 占用额外的存储空间:倘若每一个对象都是用2字节的内存空间来表示当前的引用计数值,那么如果堆空间中的对象很多时,总的消耗空间也会非常大
  2. 存在 循环引用 问题:我们下面专门分析循环引用的场景

循环引用示例
倘若具有如下代码:

class Test {private Test t;
}public class Main {public static void main(String[] args) {Test a = new Test();Test b = new Test();a.t = b;b.t = a;a = null;b = null;}
}

此时我们执行a = null; b = null;代码之前的内存区域示意图:

此时堆空间地址为0x11的对象引用计数值为2,因为存在引用a以及堆空间地址为0x12的成员变量t,此时我们执行代码a = null; b = null;此时内存示意图如下图所示:

此时堆空间中地址为0x11的对象内部引用计数仍为1,因为有地址为0x12的对象中的成员变量t引用,但是此时我们已经无法通过引用来使用对象了,因为此时堆中的对象都需要使用对方来进行访问!形成了类似"死锁"的局面

  • 同时,也因为上述的问题,JVM中并没有使用"引用计数"的方案,而是采用下面"可达性分析"的方式,但是诸如PHP、Python等就是用这样的方式来处理的(如何解决我就不得而知了~)
3.2.2.2 可达性分析

可达性分析:在程序中会存在各种各样的变量,如栈中的局部变量、对象中的非静态成员变量、static修饰的静态变量、常量池中的变量,以这些变量作为根节点出发,沿着这些变量找到其中持有的引用类型的成员,逐步遍历继续访问,所有能够被访问到的对象就不是"垃圾"了,此外的不能被访问到的变量就会被回收,算法类似于图的遍历
可达性分析代码示例

class Node {public int val;public Node left;public Node right;
}public class Main {public static void main(String[] args) {Node root = buildTree();}public static Node buildTree() {Node a = new Node();Node b = new Node();Node c = new Node();Node d = new Node();Node e = new Node();Node f = new Node();Node g = new Node();a.left = b;a.right = c;b.left = d;b.right = e;e.left = g;c.right = f;return a;}
}

其中数据结构可以表达如下图所示:

其中我们虽然栈中只有一个引用root,但是从root节点出发我们就可以遍历到a-g中所有的节点,但是如果其中执行代码root.right = null;那么此时节点c、e就无法被遍历到,此时对象c和e就会被标记为"垃圾"

3.2.2 "垃圾"如何回收

下面我们要探究的就是如何将已经被标记为"垃圾"的对象进行回收释放内存了:主要的释放策略有三种:1、标记-清除算法;2、标记-复制算法;3、标记整理算法

3.2.2.1 标记-清除算法

这是最朴素的方式,例如在堆空间中有如下需要释放的对象:

标记-释放算法就是将已经被标记为"垃圾"的空间直接释放,但是此时可能存在 内存碎片 问题:
上述的释放方式会导致有很多离散的、小的空闲内存空间,就很有可能导致后续申请连续内存失败!例如我们需要申请1M内存空间,剩余内存空间远远大于1M,但是并没有连续的1M内存空间可以提供,此时申请就会失败!

3.2.2.2 标记-复制算法

标记-复制 算法示意图如下:

"标记-复制"算法的核心就是采用"半区"复制的方式,不直接释放内存,而是将不是"垃圾"的对象,拷贝到另一半区,然后整体释放左半区内存空间,其虽然能够解决"内存碎片"问题,但是也有如下弊端:

  • 总的可用内存变少了
  • 当需要释放的对象远远少于剩余空闲内存时,此时需要复制的对象很多,复制开销就会很大!
3.2.2.3 标记-整理算法

"标记-整理"算法示意图如下:

类似于顺序表中的删除中间元素的搬运过程,然后也能解决"内存碎片"问题,而且不像"标记-复制"算法那样浪费大量内存空间,但是其中"搬运"的开销也是很大的!因此JVM并没有采取上述三种方法,而是采用"取长补短"这样的综合性方案

3.2.3 JVM分代回收算法

分代回收:JVM当中引入了一个概念:“对象年龄”,由于JVM中有专门的扫描线程周期性扫描/释放,如果一个对象被扫描了一次,仍然可达(不是垃圾),就将年龄+1(初始值可以看做0),JVM会根据对象的年龄的大小划分为不同的区域

新生代:对象年龄较小的区域,内部含有Eden区(伊甸区),Survivor区(幸存区)
老年代:对象年龄较大的区域

  1. 首先使用new关键字刚刚创建的对象都会被存放在Eden伊甸区,一个经验规律是伊甸区中的很多对象都是活不过第一轮GC扫描的
  2. 第一轮GC扫描完毕,少数伊甸区中存活的对象就会通过"标记-复制"算法拷贝到"幸存区",后续GC扫描线程不仅需要扫描伊甸区,也要扫描幸存区,而幸存区大部分对象也会被扫描而标记为垃圾,如果存活,就继续使用"标记-复制"算法拷贝到另一个幸存区当中(注意这里的幸存区是两块大小相同的内存空间),每次经历一轮GC存活就将年龄+1
  3. 如果有对象在幸存区当中存活了多轮GC,JVM就会认为这个对象生命周期很长,就会使用"标记-复制"算法将其拷贝到老年代
  4. 老年代的对象当然也会被GC扫描,但是扫描频次就大大降低了
  5. 对象在老年代如果标记为垃圾,JVM就会使用"标记-整理"算法释放内存

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

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

相关文章

Python环境搭建:一站式指南

在当前AIGC技术蓬勃发展的背景下,Python作为人工智能领域最受青睐的编程语言之一,成为我们必须掌握的技能。因此,搭建一个适合自己的Python环境成为了每个Python开发者的首要任务。本文将为您提供一站式的Python环境搭建指南,帮助…

力扣精选算法100道——颜色分类(双指针和三指针俩种方法解决此题)

目录 🚩了解题意 🚩算法分析 第一种方法:双指针 🚩代码实现一 第二种方法:三指针 🚩代码实现二 🚩了解题意 本题将整数0,1,2代表红白篮,nums中的整数并…

仿牛客网项目---私信列表和发送列表功能的实现

这篇文章我们来讲一下我的这个项目的另外一个功能&#xff1a;私信列表和发送列表功能。 先来设计DAO层。 Mapper public interface MessageMapper {// 查询当前用户的会话列表,针对每个会话只返回一条最新的私信.List<Message> selectConversations(int userId, int of…

【激光SLAM】基于已知位姿的构图算法 (Grid-based)

文章目录 地图分类概念 覆盖栅格建图算法栅格地图的特征数学描述假设 算法流程激光雷达的逆观测模型 计数(Count Model)建图算法概念数学描述观测模型地图估计 地图分类 概念 地图即为环境的空间模型。环境地图是机器人进行定位和规划的前提。定位可以用特征地图&#xff08;…

基础内容哦!!!吴恩达deeplearning.ai:利用计算图求导(反向传播)

以下内容有任何不理解可以翻看我之前的博客哦&#xff1a;吴恩达deeplearning.ai专栏 文章目录 一个小型神经网络的例子利用计算图逐步计算价值函数J利用计算图求出价值函数的导数 计算图是深度学习中的一个关键概念&#xff0c;它也是Tensorflow等编程框架自动计算神经网络导…

Linux之sed命令详解及实践

1、定义 sed全称是&#xff1a;stream editor 流编辑器 对文件的操作无非就是”增删改查“&#xff0c;**sed命令就是实现对文件的”增删改查“。** **man sed//man 的解释** 用于过滤和转换文本的流编辑器 2、功能 Sed 主要用来自动编辑一个或多个文件、简化对文件的反复…

实现定时器的两种方法:使用windows api定时器 和使用c++11/14 定时器

前言&#xff1a; 当我有一个开发需求&#xff0c;符合下面的条件 1.需要某个任务在程序中每隔一段时间就要执行一次&#xff0c;可能把这个任务封装成了一个函数。 2.这种需要定时执行的任务&#xff0c;有2个&#xff0c;3个....越来越多。 这个时候我们就可以考虑使用定时…

第三百七十六回

文章目录 1 .概念介绍2. 实现方法3. 示例代码 我们在上一章回中介绍了在页面之间共传递数据相关的内容&#xff0c;本章回中将介绍如何拦截路由.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1 .概念介绍 本章回中介绍的路由拦截是指在路由运行过程中&#xff0c;对路由做…

论文阅读_代码生成模型_CodeLlama

英文名称: Code Llama: Open Foundation Models for Code 中文名称: Code Llama&#xff1a;开放基础代码模型 链接: https://arxiv.org/abs/2308.12950 代码: https://github.com/facebookresearch/codellama 作者: Baptiste Rozire, Jonas Gehring, Fabian Gloeckle, Sten So…

【前端素材】推荐优质在线花卉商城电商网页Flowery平台模板(附源码)

一、需求分析 1、系统定义 在线花卉商城是一个通过互联网提供花卉销售服务的电子商务平台&#xff0c;用户可以在该平台上浏览、选择和购买各种花卉产品。 2、功能需求 在线花卉商城是一个通过互联网提供花卉销售服务的电子商务平台&#xff0c;用户可以在该平台上浏览、选…

vscode在windows环境不能使用终端安装依赖

会报这样的错误提示 解决思路&#xff1a; 1、vscode用管理员打开 (非必须) 2、设置策略 打开 windows powerShell . 输入命令 set-ExecutionPolicy RemoteSigned 然后 Y . 查看是否设置成功 get-executionpolicy 3、下载总是超时&#xff0c;设置镜像源 查看镜像源 npm …

蓝桥杯-天数

//此题属于简单 #include <iostream> using namespace std; int main() { int n; cin>>n; if(n2) { cout<<28; return 0; } if(n1||n3||n5||n7||n8||n10||n12)//一定要记得写成n什么&#xff0c;每个都要写&#xff0c;不要漏掉 { cou…

常见漏洞的流量特征

1、SQL注入漏洞 查看url / Referer字段/User-Agent字段/cookie字段 出现一些特殊字符&#xff08;eg&#xff1a;单引号【‘】、双引号【“”】、括号【&#xff08;&#xff09;】、单引号括号【‘&#xff08;】、双引号括号【“&#xff08;】等一些常见的特殊的字符&#…

数通HCIE和云计算HCIE哪个好一点?

数通是网络的基础知识&#xff0c;也是入门人员必学的方向&#xff0c;相对也会简单些&#xff0c;学习数通&#xff0c;可以很好的学习其他的方向。数通的就业范围也比较广&#xff0c;运营商、企业、政府还是互联网公司&#xff0c;都需要大量的数通工程师来搭建和维护网络&a…

大数据毕业设计之前端04:管理系统为什么要自己实现图标组件

关键字&#xff1a;BuildAdmin、Icon、图标、Vue、ElementUI 前言 说到图标&#xff0c;在BuildAdmin中用到的地方很多。比如上一篇中的折叠图标&#xff0c;还有菜单栏图标、导航菜单栏图标等。常见的图标有&#xff1a;ElementUI图标、font-awesome、iconfont阿里图标以及本…

94. 递归实现排列型枚举 刷题笔记

思路 依次枚举 每个位置用哪个数字 要求按照字典序最小来输出 而每次搜索下一层时i都是从1开始 也就是说 如果有小的数可以填上 那么该方案会填上这个数字 例如 当n等于3 第一次搜索 1 2 3输出后返回 返回后此时i3 第二个位置填3 1 3 2 输出后返回 此时返回到第一层…

云计算 2月21号 (linux文件及用户管理)

一、文件管理 1.1快捷键 编辑命令&#xff1a; Ctrl a &#xff1a;移到命令行首 Ctrl e &#xff1a;移到命令行尾 Ctrl u &#xff1a;从光标处删除至命令行首 Ctrl k &#xff1a;从光标处删除至命令行尾 Ctrl w &#xff1a;从光标处删除至字首 Ctrl d &#x…

20240301-2-ZooKeeper面试题(二)

11. Chroot 特性 3.2.0 版本后&#xff0c;添加了 Chroot 特性&#xff0c;该特性允许每个客户端为自己设置一个命名空间。如果一个客户端设置了 Chroot&#xff0c;那么该客户端对服务器的任何操作&#xff0c;都将会被限制在其自己的命名空间下。 通过设置 Chroot&#xff…

Win11远程桌面登陆教程

必备软件 Remote Desktop 这个软件用于便捷操作 Tailscale 这个用于创建虚拟局域网让两台设备处于同一个网段便于远程连接 详细步骤 0、打开电脑设置允许远程连接模式 把这里的开关打开就行。 1、设置允许登陆的用户以及密码 在管理员模式下的宿主电脑上面输入以下命令…

寒假作业Day 02

这是第二天的作业&#xff0c;fighting&#xff01; Day 02 一、选择题 首先char* s[6]是指针数组&#xff0c;也就是其存储的都是这些字符串的地址&#xff0c;其实际上的类型为char**&#xff0c;而fun函数传入了s数组的首地址。而后续fun函数中打印字符&#xff0c;p[i]即…