JVM(内存区域划分、类加载机制、垃圾回收机制)
目录
一. 内存区域划分
1.本地方法栈(Native Method Stacks)
2.虚拟机栈(JVM Stacks)
3.程序计数器(Program Counter Register)
4.堆(Heap)
5.元数据区(Metaspace)
二.类加载机制
1.加载
2.验证
3.准备
4.解析
5.初始化
"双亲委派模型"
三. GC 垃圾回收机制
GC实际工作过程
1.找到垃圾/判定垃圾
(1).引用计数(Java没有使用)
(2).可达性分析(Java使用的)
2.如何清理垃圾
(1).标记清除编辑
(2).复制算法编辑
(3).标记整理编辑
"分代回收"
一. 内存区域划分
正如一个公司的运作模式,首先公司需要一块场地,再接着需要根据公司的各个部分,将这块场地划分成一块一块的部门,每个部门都有独立的功能。
JVM的内存区域划分也是如此,JVM启动的时候会申请一整个很大的内存区域(JVM是个应用程序,要从操作系统中申请内存),接着根据需求,将整个空间分成几个部分。
1.本地方法栈(Native Method Stacks)
native表示JVM内部的C++代码(JVM是用C++写的),所以本地方法栈就是给调用native方法(JVM内部方法)准备的栈空间
问题:C++代码是不是不用跑在虚拟机上 答:像C、C++、Go、Rust都是把代码变成成native code,也就是可以直接被cpu识别的机械指令 而Java、Python、PHP为了跨平台,都是统一翻译成指定的字节码(字节码都是一样的),然后由对应的虚拟机转换成机械指令
2.虚拟机栈(JVM Stacks)
给Java代码使用的栈
注意:stack栈,在之前数据结构中也学习过,数据结构中的“栈”是“后进先出”的,和这里所说的栈不是一个东西。
这里所提的栈,是JVM中的一个特定的空间。
- 对于JVM虚拟机栈,这里存储的是方法之间的调用关系
- 对于本地方法栈,存储的是native方法之间的调用关系
整个栈空间内部,可以认为是包含很多个元素,每个元素表示一个方法,把这里的每个元素,称之为“栈帧”,这一个栈帧里,会包含这个方法的入口地址、方法参数、返回地址、局部变量.....
但是由于函数调用中,也有“后进先出”的特点,因此此处的栈也是后进先出的,只是数据结构中的栈是一个更通用的,更广泛的概念,而JVM中的栈是特指JVM上的一块内存空间。
从内存划分图中也可以看出,这里的栈有多个,每个线程有一个(线程是一个独立的执行流),使用jconsole(jdk下bin目录中)可以查看java进程内部的情况,点击线程就可以看到线程调用栈的情况。
问题:这些线程每次创建都分相同空间,那么栈会不会被很快就用完了? 答:栈的整体大小一般都不会很大,但是每个栈帧的大小也是比较小的,遇到无限递归的代码会遇到栈溢出的情况,就正常写代码,创建销毁线程一般是不需要担心这个问题的。
3.程序计数器(Program Counter Register)
记录当前线程执行到哪条指令(很小的一块存一个地址),也是每个线程有一份。
4.堆(Heap)
堆是整个JVM空间的最大的区域,new出来的对象,都是在堆上的,类的成员对象也就在堆上了
-
堆 是一个进程只有一份的
-
栈 是每个线程有一份,一个进程有多个线程
-
堆,多个线程用的都是同一个堆
-
栈,每个线程用自己的栈
每个jvm就是一个java进程
5.元数据区(Metaspace)
以前这个区域叫做方法区,java8开始改为了元数据区 这里存储的有类对象、常量池、静态成员
元数据区也是一个进程一份,多个线程共用这一份
问题:final修饰的变量在这里存储吗? 答:不一定,有一定概率被优化成字面值常量,也有可能没优化
总结
- 局部变量在栈
- 普通成员变量在堆
- 静态成员变量在方法区/元数据区
二.类加载机制
准确的来说,类加载就是将.class文件,从文件(硬盘)被加载到内存中(元数据区)这样的过程。
1.加载
- (1).通过一个类的全限定名来获取这个类的二进制字节流
- (2).将这个字节流多代表的静态存储结构转化成方法区的运行时数据结构
- (3).在内存中生成一个代表这个类的java.lang.Class对象
但是对应上述的要求并不是具体的,JVM的实现过程和应用是比较灵活的,比如获取类的二进制字节流,并没有说明如何获取,所以就有了从压缩包中获取(jar、war、ear),从网路中获取(Applet),运行时计算生成(动态代理)
对于不是数组的类的加载我们可以自定义去控制字节流的获取方式,而数组类就不一样了,因为数组类本身不是通过类加载进行创建的,而是由JVM直接创建
2.验证
根据jvm虚拟机规范,检查.class文件的格式是否符合要求
3.准备
给类对象分配内存空间(此时内存初始化成全0),在这个阶段里,为静态变量分配内存并设置静态变量初始值。这里说的初始值通常情况下,不是代码中写的初始值,而是数据类型的零值。代码中写的初始值,是在初始化阶段赋值的。如果是静态常量(被final修饰),这个阶段就会被直接赋值为代码中写的初始值
4.解析
针对字符串常量进行初始化,把符号引用转为直接引用
字符串常量,需要有一块内存空间存这个字符的实际内容,还需要一个引用,来保存这个内存空间的起始地址。 在类加载之前,字符串常量是处在.class文件中的,这个时候没有地址这个概念,此时这个"引用"记录的并非是真正的地址,而是他在文件中的"偏移量"(或者是个占位符)。 在类加载之后,才真正把这个字符串常量给放到内存中,此时才有"内存地址",这个原因才能真正赋值成指定的内存地址。
5.初始化
真正针对类对象里面的内容进行初始化,加载父类、执行静态代码块的代码.....
总结

问题:一个类,什么时候会被加载呢? 答:不是java程序一运行就将所有的类都加载了,而是真正用到的时候才加载(赖汉模式),比如构造类的实例、调用这个类的静态方法/使用静态属性、加载子类,就会先加载父类。 而一旦加载过后,后续再使用就不需要重新加载了。
"双亲委派模型"
JVM 默认提供了三个类加载器
- BootstrapClassLoader 负责加载标准库中的类
- ExtensionClassLoader 负责加载JVM扩展库中的类
- ApplicationClassLoader 负责加载用户提供的第三方库,用户项目代码中的类
而上述三个类存在"父子关系",BootstrapClassLoader为最大,ApplicationClassLoader为最小。
此处的"父子关系",不是父类、子类,而是每个ClassLoader中都有一个parent属性指向自己的父 类加载器
上述三个类加载器的工作模式:
首先加载一个类的时候,先从ApplicationClassLoader开始
但是ApplicationClassLoader会把加载任务交给父亲,于是ExtensionClassLoader就会去加载,但是也不是真加载,而是再委托自己的父亲去加载
于是BootstrapClassLoader就去加载,他也想委托给父亲,但是发现父亲为null,所以就由自己加载,那么BootstrapClassLoader就会搜索自己负责的标准库中的类,如果找到就加载,如果没找到就交给自己的子类进行加载
于是ExtensionClassLoader就去加载,搜索自己负责的JVM扩展库中的类,找到则加载,找不到就交给子类
于是ApplicationClassLoader进行加载,搜索用户用的第三方库和项目中的类,找到则加载,找不到的话由于自己没有子类,就只能抛出类找不到这种异常
使用这个顺序进行,主要的目的据说为了保证Bootstrap能够先加载,Application能够后加载,这就避免了用户创建一些奇怪的类而导致的bug
比如用户写了一个java.lang.String这个类,此时能保证JVM加载的还是标准库中的类,而不会加载到用户写的这个类,这样就能保证即使出现上述问题,也不会让jvm的代码混乱,最多就是用户写的代码不生效
三. GC 垃圾回收机制
首先要知道的是在JVM中进行垃圾回收的是"堆",并且GC是以"对象"为基本单位进行回收的
GC回收的是整个对象都不再使用的情况,而那种一部分使用,一部分不使用的对象先不回收了(一个对象里有很多属性,可能其中10个属性后面要用,10个属性后面再也不用了)
GC实际工作过程
1.找到垃圾/判定垃圾
关键思路在于看看到底有没有"引用"指向它,Java中使用对象只有一条路,通过引用来使用,如果一个对象有引用指向它那么就有可能被用到,如果没有引用指向它那么就不可能被用到
那么怎么知道对象是否有引用指向?
(1).引用计数(Java没有使用)
给每个对象分配一个计数器(整数),每次创建一个引用指向该对象,计数器就+1,每次该引用被销毁了计数器就-1
问题:这种方法很好理解,但是为什么Java不使用呢?
答: 1.引用计数内存空间浪费的多,每个对象都要分配一个计数器,计数器按照4个字节算,代码中对象非常少无所谓,怕的就是对象特别多,并且每个对象都比较小,那么这个时候占用的额外空间就会很多(一个对象的体积是1k,多4个字节无所谓,一个对象体积是4个字节,再多4个字节相当于体积扩大一倍)
2.存在循环引用问题
(2).可达性分析(Java使用的)
Java中的对象,都是通过引用来指向并访问的,经常是一个引用指向一个对象,这个对象里的成员又指向别的对象

整个Java中的所有对象,就通过类似这种树形/链式结构,整体串起来
可达性分析,就是把所有这些对象组织的结构视为树,从根节点出发,遍历树,所有能被访问到的对象就标记为"可达"(不能被访问到的,就是不可达)
此处的图中虽然只有root引用,但是上述6个对象都是可达的
- root-->a
- root.left-->b
- root.right-->c
- root.left.left-->d
- root.left.left.left-->e
小结:可达性分析需要进行类似于"树遍历"整个操作相比于引用计数来说肯定要慢一些的,但是速度慢没关系,上述遍历操作,并不需要一直执行,只需要每隔一段时间分析一遍即可
进行可达性分析遍历的起点,称为GCroots
1.栈上的局部变量
2.常量池中的对象
3.静态成员变量
一个代码中可能有很多这样的GCroots,把每个起点都往下遍历一遍就完成了扫描过程
2.如何清理垃圾
(1).标记清除
标记清理的特点是简单粗暴,但是会导致内存碎片化问题,被释放的空闲空间是零散的,不是连续的
但是我们申请内存的要求是需要连续的空间,总的空闲空间可能很大,但是每个具体的内存空间可能很小,可能导致申请大一点的内存就失败了
例如总的空闲空间是10k,分成1k一个,一共十个,此时如果需要申请2k的空间,那么就会申请失败
(2).复制算法
复制算法解决了内存的碎片化问题
复制算法把整个内存分为两半,用一半丢一半
把不是"垃圾"的对象复制到另外一半内存空间,然后把之前的一半整个空间删掉
若是后续再触发GC,那么就在右边这一半进行复制算法到左边即可
缺点:空间利用率低下,如果垃圾少,有效对象多,那么复制成本就比较大了
(3).标记整理
标记整理解决了复制算法的缺点
类似于顺序表中的删除中间元素,有元素搬运的操作,这样既保证了空间利用率,也解决了内存碎片化问题
但是,很明显,这种做法,效率也不高,元素搬运如果需要搬运的空间大,那么开销也大
上述的三种方法都有其对应的缺点,那么有没有比较完美的办法呢?
"分代回收"
把垃圾回收,分成不同的场景,对应不同的场景使用上述不同的办法
那么如何定义上述的不同场景呢?也就是分代是如何分的?
这里的分,是基于一个经验规律:如果一个东西(对象),存在的时间长了,那么大概率还会继续的长时间存在下去(比如C语言从197x年就开始诞生了,到如今还依然存在并且活跃,那么我们就有理由相信他还会存在很长时间),而上述规律对应Java中的对象也是有效的,java对象要么就是生命周期特别短,要么就是特别长
根据上方的规律,我们给对象引入一个概念"年龄"(这个年的单位是熬过的GC的轮次),年龄越大那么存在的时间就越久
刚new出来的对象,年龄是0的对象,放到伊甸区(出自圣经,上帝在伊甸园造小人)
熬过一轮GC,对象就要被放到幸存区了,那么伊甸区-->幸存区就使用复制算法(将有效对象复制到幸存区,伊甸区整体释放)
到幸存区后,也要接受GC的考验,如果变成垃圾就要被释放,如果不是垃圾,那么就拷贝到另一个幸存区(这两个幸存区,同一时刻值用一个,在两个幸存区反复横跳),所有这里也是使用复制算法(由于幸存区的体积不大,此处浪费的空间也能接受)
如果这个对象在幸存区中GC了很多次了,那么这个时候就进入到老年代,老年代都是年龄大的对象,生命周期普遍更长,那么针对老年代进行的GC扫描频率就低了,如果老年代的对象是垃圾了,那么就使用标记整理的方式进行释放
小结:
上述的GC中典型的垃圾回收算法:如何确定垃圾、如何清理垃圾,这里的策略,实际上在JVM实现的时候,会有一定的差异,JVM有很多的"垃圾回收实现"称为"垃圾回收器",回收器的具体实现做法,会按照上述算法思想展开,不同的垃圾回收器侧重点不同,有的追求GC扫描快、有点追求扫描好、有点追求用户打扰少(STW短).....
相关文章:
JVM(内存区域划分、类加载机制、垃圾回收机制)
目录 一. 内存区域划分 1.本地方法栈(Native Method Stacks) 2.虚拟机栈(JVM Stacks) 3.程序计数器(Program Counter Register) 4.堆(Heap) 5.元数据区(Metaspace) 二.类加载机制 1.加载 2.验证 3.准备 4.解析 5.初始化 "双亲委派模型" 三. GC 垃圾回收…...
C语言---基础内容(万字)
C 语言是一种通用的、面向过程式的计算机程序设计语言。1972 年,为了移植与开发 UNIX 操作系统,丹尼斯里奇在贝尔电话实验室设计开发了 C 语言。 C 语言是一种广泛使用的计算机语言,它与 Java 编程语言一样普及,二者在现代软件程…...
c语言从入门到函数速成(完结篇)
哈喽,小伙伴们大家好呀,本篇文章是这个系列的完结篇,希望大家看完后能有所收获哦 首先能看到这里的同学,一定也是自觉性比较强的了,我会在文章末尾给大家发点小福利 那么,我们先来通过数学中的函数来引入一…...
关于linux磁盘告警问题
案例:我们在执行df命令时,查看到磁盘利用率很高,但是到相对应的目录执行du -sh *来找大文件时进行删除时,发现各个目录相加并不大,如下图: 使用df命令查看到根(/)目录使用到33G,而du命令显示只使…...
冯喜运:5.27黄金暴跌大阴后出现“暂定符”今日黄金原油操作策略
【黄金消息面分析】:金价虽然有大阴线暴跌,但依然属于超买后的调整而非熊市,对中长线投资者来说只是市场洗牌。因此,在出现企稳迹象之后,随时关注反弹时机的启动。未来几日,黄金空头可能在进一步发力之前需…...
前端JS必用工具【js-tool-big-box】学习,获取全球重点城市时间
我们去住一些旅馆的时候,或者一些国际性网站,经常可以看见他们的钟表会展示一些国家地区的时间,这个就是很常用的功能。但如果不常接触这个功能的开发网站呢,大家就看自己电脑右下角的时间展示,就是自己当前的具体时间…...
BioTech - 将蛋白质的 PDB 格式文件 转换成 mmCIF 格式文件 (Python)
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://blog.csdn.net/caroline_wendy/article/details/139234247 蛋白质的三维结构信息通常可以通过两种格式的文件来获取:PDB (Protein Data Bank) 和 mmCIF (Macromolecular Crystallographic Information File…...
【编程题-错题集】奇数位丢弃(模拟 - 规律)
牛客对应题目链接:奇数位丢弃_牛客题霸_牛客网 (nowcoder.com) 一、分析题目 通过⼀两个例子的模拟,可以发现:每次起始删除的下标都是 2 的次方。根据这个规律,找到最后⼀次删除的起始位置的下标即可。 二、代码 #include <io…...
Docker安装MongoDB(Linux版)
文章目录 前言一、Docker环境的准备1.安装依赖2.安装Docker 二、使用Docker安装MongoDB1.mongo版本选取2.拉取合适的镜像3.宿主机创建MongoDB需要挂载的文件夹4.第一次无认证创建mongo用户5.启动需要认证的mongo容器 问题汇总总结 前言 本文章主要介绍在Centos系统,…...
【设计模式】JAVA Design Patterns——Commander(指挥官模式)
🔍目的 用于处理执行分布式事务时可能遇到的所有问题。 🔍解释 处理分布式事务很棘手,但如果我们不仔细处理,可能会带来不想要的后果。假设我们有一个电子商务网站,它有一个支付微服务和一个运输微服务。如果当前运输…...
解决vue3项目vite打包忽略.vue扩展名
项目打包时报could not relolve “...”,因为vite已不再默认忽略.vue扩展名。 解决方法如下: 在vite.config.js中配置vite使其忽略 .vue 扩展名(不建议忽略) 注意:即使忽略了.vue文件,在实际写的时候也要加…...
Vue基础(数据绑定、export使用)
1、简介 在使用vue开发的过程中,经常会遇到一些容易混淆的问题,因此,在本文中进行汇总操作,只有通过不断总结学习,才能更好掌握vue的使用(每天进步一点)。 2、数据绑定 在js中定义数据…...
【传知代码】基于图神经网络的知识追踪方法(论文复现)
前言:本文将深入探讨基于图神经网络的知识追踪方法,旨在通过构建知识图谱来捕捉知识之间的复杂关联,并利用图神经网络强大的表示学习能力来建模学生的学习过程。我们将首先介绍图神经网络的基本原理和关键技术,然后详细阐述如何将…...
Vue与React、Angular的比较
Vue、React和Angular是前端开发中三个流行的JavaScript框架,它们各自具有不同的特点、优势和适用场景。以下是对这三个框架的比较: 1. 基本概念 Vue:Vue是一套用于构建用户界面的渐进式框架,其核心库专注于视图层,易…...
LINQ(二) —— 流式语句
总目录 C# 语法总目录 LINQ 二 —— 流式语句 1.1 序列相关部分运算符1.2 查询相关部分运算符 1.1 序列相关部分运算符 Take 运算符:Take 是拿出序列的几个数 Skip 运算符:Skip 是跳过序列的前几个数 Reverse 运算符:Reverse 是将序列反转 …...
怎么查看MySQL服务的最大连接,已经使用的连接数?怎么配置最大连接数?
要查看和配置MySQL服务的最大连接数以及已经使用的最大连接数,可以使用以下SQL语句和步骤: 查看MySQL服务的最大连接数和已经使用的最大连接数 查看当前最大连接数: SHOW VARIABLES LIKE max_connections;查看已经使用的最大连接数ÿ…...
微信小程序毕业设计-跑腿系统项目开发实战(附源码+演示视频+LW)
大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:微信小程序毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计…...
stm32通过esp8266连接阿里云平台代码讲解
连接服务器 首先,按照一定的规则,获取连接阿里服务器所需要的ClientID(客户端D)、Username(用户名)、Passward(密码),ServerIP(域名),ServerPort(…...
突发!某大厂机房掉电,MySQL数据库无法启动,紧急恢复过程...
作者:IT邦德 中国DBA联盟(ACDU)成员,10余年DBA工作经验, Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主,全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复, 安装迁移,性能优化、故障…...
SpringCloudAlibaba:6.2RocketMQ的普通消息的使用
简介 普通消息也叫并发消息,是发送效率最高,使用最多的一种 依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSch…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...
五子棋测试用例
一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏,有着深厚的文化底蕴。通过将五子棋制作成网页游戏,可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家,都可以通过网页五子棋感受到东方棋类…...
Neko虚拟浏览器远程协作方案:Docker+内网穿透技术部署实践
前言:本文将向开发者介绍一款创新性协作工具——Neko虚拟浏览器。在数字化协作场景中,跨地域的团队常需面对实时共享屏幕、协同编辑文档等需求。通过本指南,你将掌握在Ubuntu系统中使用容器化技术部署该工具的具体方案,并结合内网…...
用递归算法解锁「子集」问题 —— LeetCode 78题解析
文章目录 一、题目介绍二、递归思路详解:从决策树开始理解三、解法一:二叉决策树 DFS四、解法二:组合式回溯写法(推荐)五、解法对比 递归算法是编程中一种非常强大且常见的思想,它能够优雅地解决很多复杂的…...
RKNN开发环境搭建2-RKNN Model Zoo 环境搭建
目录 1.简介2.环境搭建2.1 启动 docker 环境2.2 安装依赖工具2.3 下载 RKNN Model Zoo2.4 RKNN模型转化2.5编译C++1.简介 RKNN Model Zoo基于 RKNPU SDK 工具链开发, 提供了目前主流算法的部署例程. 例程包含导出RKNN模型, 使用 Python API, CAPI 推理 RKNN 模型的流程. 本…...
Vue3学习(接口,泛型,自定义类型,v-for,props)
一,前言 继续学习 二,TS接口泛型自定义类型 1.接口 TypeScript 接口(Interface)是一种定义对象形状的强大工具,它可以描述对象必须包含的属性、方法和它们的类型。接口不会被编译成 JavaScript 代码,仅…...
Springboot多数据源配置实践
Springboot多数据源配置实践 基本配置文件数据库配置Mapper包Model包Service包中业务代码Mapper XML文件在某些复杂的业务场景中,我们可能需要使用多个数据库来存储和管理不同类型的数据,而不是仅仅依赖于单一数据库。本技术文档将详细介绍如何在 Spring Boot 项目中进行多数…...

