【JVM】JVM相关机制

news2024/9/22 7:20:49

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.coloradmin.cn/o/1481090.html

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

相关文章

php 支持mssqlserver

系统不支持:sqlsrv 需要一下几个环节 1.准备检测php版本 查看 VC 版本 查看操作系统位数:X86(32位) 和X64 2.下载php的sqlserver库 extensionphp_sqlsrv_74_nts_x64.dll extensionphp_pdo_sqlsrv_74_nts_x64.dll extensionphp_sqlsrv_74_nts_x64 extensionphp_…

用HTML5的<canvas>元素实现刮刮乐游戏

用HTML5的<canvas>元素实现刮刮乐 用HTML5的<canvas>元素实现刮刮乐&#xff0c;要求&#xff1a;将上面的“图层”的图像可用鼠标刮去&#xff0c;露出下面的“图层”的图像。 示例从简单到复杂。 简单示例 准备两张图像&#xff0c;我这里上面的图像top_imag…

【Spring】spring中怎么解决循环依赖的问题

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Spring ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 解决步骤 考虑 结语 我的其他博客 前言 在软件开发中&#xff0c;依赖注入是一种常见的设计模式&#xff0c;它可以帮助我们管…

探讨javascript的程序性能

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 Web WorkerWorker之间通讯Worker销毁 Web Worker 当我们需要处理一些比较耗时的任务时&#xff0c;我们…

杭电OJ 2045 不容易系列之(3)—— LELE的RPG难题 C++

思路&#xff1a;我先模拟了一下1&#xff0c;2&#xff0c;3的情况&#xff0c;对应的是3 6 6&#xff0c;模拟到4的时候就有感觉了&#xff0c;1是不受到任何制约的&#xff0c;2到n-1是收到了前面一个的制约&#xff0c;n受到了n-1与1的制约&#xff0c;那么就可以去判断4 …

七通道NPN 达林顿管GC2003,专为符合标准 TTL 而制造,最高工作电压 50V,耐压 80V

GC2003 内部集成了 7 个 NPN 达林顿晶体管&#xff0c;连接的阵列&#xff0c;非常适合逻辑接口电平数字电路&#xff08;例 如 TTL&#xff0c;CMOS 或PMOS 上/NMOS&#xff09;和较高的电流/电压&#xff0c;如电灯电磁阀&#xff0c;继电器&#xff0c;打印机或其他类似的负…

构造pop链

反序列化视频笔记 第一步&#xff1a;找到目标触发echo调用$flag 第二步&#xff1a;触发_invoke函数调用appeng函数$varflag.php&#xff08;把对象当成函数&#xff09; 第三步&#xff1a;给$p赋值为对象&#xff0c;即function成为对象Modifier却被当成函数调用&#xff…

csv大数值不显示E科学计算法的解决方案

背景&#xff1a; 从其他系统获取到一个商品mid的大的数值的csv文件&#xff0c;然后使用excel打开的时候有各种问题&#xff0c;本文记录下怎么正确的展示这个大数值的csv文件 正确展示数值精度&#xff1a; 数值展示错误 正确展示的方法&#xff1a; 1使用文本编辑器比如…

分割回文串 复原IP地址 子集 递增子序列

131.分割回文串 力扣题目链接(opens new window) 给定一个字符串 s&#xff0c;将 s 分割成一些子串&#xff0c;使每个子串都是回文串。 返回 s 所有可能的分割方案。 示例: 输入: "aab" 输出: [ ["aa","b"], ["a","a"…

【报名指南】2024年第九届数维杯数学建模挑战赛报名全流程图解

1.官方报名链接&#xff1a; 2024年第九届数维杯大学生数学建模挑战赛http://www.nmmcm.org.cn/match_detail/32 2.报名流程&#xff08;电脑与手机报名操作流程一致&#xff09; 参赛对象为在校专科生、本科生、研究生&#xff0c;每组参赛人数为1-3人&#xff08;指导老师不…

「算法」常见位运算总结

位运算符 异或 按位异或可以实现无进位相加&#xff0c;所谓无进位相加&#xff0c;就是在不考虑进位的情况下将两个数相加&#xff08;后面有道题需要用到这种操作&#xff09; 异或的运算律 ①a ^ 0 a ②a ^ a 0 ③a ^ b ^ c a ^ ( b ^ c ) 有符号右移>> 将一个…

(C语言)函数详解(下) 项目多文件操作 extern ,static详解

&#xff08;C语言&#xff09;函数详解上&#xff1a;http://t.csdnimg.cn/ceDqA 下面我们进行函数下的学习。 目录 我们 1. 函数的声明和定义 1.1 单个文件 1.2 多个文件 2. static和extern 2. 1 static 修饰局部变量&#xff1a; 2. 2 static修饰全局变量 2. 3 st…

HarmonyOS—编译构建概述

编译构建是将应用/服务的源代码、资源、第三方库等&#xff0c;通过编译工具转换为可直接在硬件设备上运行的二进制机器码&#xff0c;然后再将二进制机器码封装为HAP/APP软件包&#xff0c;并为HAP/APP包进行签名的过程。其中&#xff0c;HAP是可以直接运行在模拟器或真机设备…

Java8 - LocalDateTime时间日期类使用详解

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

又挖到宝了!国人团队研发的AI视频工具PixVerse,这么好用居然还完全免费!(强烈推荐)

昨天发了一款国产免费的 AI 绘画工具 Dreamina 的介绍&#xff1a; 居然才发现&#xff01;字节跳动旗下国产AI绘画工具Dreamina&#xff0c;这么好用居然还免费&#xff01;&#xff08;强烈推荐&#xff09; 发现大家对国产 AI 工具还挺感兴趣的。今天继续帮大家挖国产的 A…

Python环境搭建:一站式指南

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

学习:Sora技术报告Video generation models as world simulators,2024.2

原文链接&#xff1a; Video generation models as world simulators (openai.com) 摘要&#xff1a; 我们探索了在视频数据上大规模训练生成模型。具体来说&#xff0c;我们在可变片长、分辨率和纵横比的视频和图像上联合训练文本条件扩散模型text-conditional diffusion mo…

腾讯云2024年优惠活动和云服务器优惠价格清单,3月最新整理

腾讯云优惠活动2024新春采购节活动上线&#xff0c;云服务器价格已经出来了&#xff0c;云服务器61元一年起&#xff0c;配置和价格基本上和上个月没什么变化&#xff0c;但是新增了8888元代金券和会员续费优惠&#xff0c;腾讯云百科txybk.com整理腾讯云最新优惠活动云服务器配…

上云还是下云,最大挑战是什么?| 对话章文嵩、毕玄、王小瑞

近半年来&#xff0c;公有云领域频频发生阿里云、滴滴等平台崩溃事件&#xff0c;与此同时&#xff0c;马斯克的“X 下云省钱”言论引起了广泛关注&#xff0c;一时间&#xff0c;“上云”和“下云”成为热议话题。在最近举办的 AutoMQ 云原生创新论坛上&#xff0c;AutoMQ 联合…

【计算机网络】五种IO模型与IO多路转接之select

文章目录 一、五种IO模型二、非阻塞IO1.fcntl2.实现函数SetNoBlock3.轮询方式读取标准输入 三、I/O多路转接之select1.初识select2.select函数原型3.socket就绪条件4.select的特点5.select缺点6.select使用案例--只读取数据的server服务器1.err.hpp2.log.hpp3.sock.hpp4.select…