JVM基础知识

JVM内存区域划分

为什么要划分区域?

JVM 是仿照真实的机器,真实的操作系统进行设计的,真实的操作系统中对于进程的地址空间进行了分区域设计

JVM具体是怎么划分的

核心区域有四个

1.程序计数器

很小的区域,只是用来记录当前指令执行到哪个地址了

2.数据元去

保存当前类被加载好的数据(类的名字是啥,继承了哪个类,实现了哪些接口,有哪些属性,属性的类型,有哪些方法......)

java =》class. =》加载到内存中

要想运行这个代码,就需要把class.加载到内存里面(类加载)

3.栈

保存方法的调用关系

每次调用方法就会进入方法内部执行,当方法执行完毕,返回到调用位置继续往后走

(此处谈到的栈、堆和数据结构中的没什么关系)

栈的空间不算很大,一般是几MB、几十MB这样的情况(可以配置)大部分情况下是够用的,少数情况下可能会出现"栈溢出" 例如递归代码出错

4.堆

保存new对象

堆事JVM中最大的空间区域了,往集合里添加元素,如果堆上的对象不再使用就需要被释放掉

类加载机制元信息指的是一些属性,比如类叫什么名字,是不是public,继承自哪些类,实现了哪些接口........

元数据和堆,整个java进程共用一份,程序计数器和栈,一个进程中可能有很多份(每个线程有一份)

JVM类加载

类加载本身是一个很复杂的事情,这里主要关系两个方面

类加载步骤有哪些

1.加载 找到.class文件(根据类的全限定名),打开文件读取文件内容到内存中

2.验证:解析检验,.class文件读到的内容是否是合法的,并且把这里的内容转成结构化数据

3.准备:给类对象申请内存空间,此处申请的内存空间相当于全是0

4.解析:针对字符串常量进行初始化(字符串本身包含在class.文件中,就需要.class里解析出字 符串常量放到内存空间中)

5.初始化:针对刚才谈到的类对象进行最终的初始化,针对对象的各种属性进行填充,如果这个类有父类,并且父类还没加载,此环节也会触发父类的类加载

类加载触发的时机(懒汉模式)java代码用到哪个类就会触发哪个类的家加载

  1. 构造这个类的实例

  2. 调用/使用 类静态属性/静态方法

  3. 使用某个类的时候,如果他的父类还没有加载,也会触发父类的加载

类加载中的"双亲委派模型"

描述了类加载中,根据全限定类名找到 .class 文件的过程

类加载器:JVM中有专门的模块负责类加载

JVM默认提供了三种类加载器

  1. BootstrapClassLoader(java标准库目录)

  2. ExtensionClassLoader(java扩展库目录)

  3. ApplicationClassLoader(Java第三方库/当前项目)

给出的这三个类加载器是属于JVM自带的,程序员是可以自定义类加载器的

垃圾回收机制

java中释放内存的手段

手动释放内存太麻烦,太容易出错,java引入垃圾回收,进行自动回收,JVM会自动识别某个内存后续不再使用了,自动释放

GC会一定程度影响程序运行的效率,并且可能会出现STW问题(触发大规模GC就可能因为GC使得其他业务代码不得不暂停下来,等待GC结束再继续走 =》卡了 java 17 及其以上的版本可以做到让STW大部分情况下 <1ms 的时间)

GC回收JVM中堆的内存区域

程序计数器:线程销毁自然就释放了

栈:方法执行结束,栈帧就结束,随之就释放了

元数据区:类对象,一般不会释放

堆:创建会多对象,也会有旧对象消亡

说是"回收内存",本质上是"回收对象"


GC工作过程

1.找到垃圾(不再使用的对象)

2.释放垃圾(对应内存释放掉)


找到垃圾

1.引用计数 (Python PHP 采用了这个方案)

每个对象在new的时候都搭配一个小的内存空间,保存一个数

缺点:

内存消耗的更多

可能出现"循环引用"这样的问题

2.可达性分析(java采取了这个方案)

  • 以代码中的一些特定对象作为遍历的"起点" =》GCROOTS 栈上的局部变量(引用类型)、常量池引用指向的对象、静态成员(引用类型)
  • 尽可能的进行遍历(判断每个对象是否能被访问到)
  • 每次访问一个对象,都会把这个对象标记成"可达"

JVM中一共有多少个对象,JVM自身是知道的,通过可达性分析,知道了哪些是"可达",剩下的就是不可达(接下来会被回收)

释放垃圾

1.标记清除

把垃圾对象的内存直接进行释放,这样就会产生内存碎片问题

内存碎片如果非常多,总的空闲时间虽然很大,但是申请大一点内存就会失败

2.复制算法

申请到内存后一次只使用其中的一般,把不是垃圾的对象拷贝到另一侧,然后把这一侧给整体释放掉,此时可以确保空闲的内存是连续饿

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

一旦不是垃圾的对象较多,复制的成本就会很高(尤其是对象中包含大的对象)

3.标记-整理

类似于顺序表的搬运

优点:解决内存碎片&保证内存利用率

缺点:内存搬运数据的操作开销大,复制成本的问题仍在

4.分代算法

"代" =》年龄的对象 某个对象进过一轮GC可达性分析之后不是垃圾,此时对象的年龄就会+1;

针对不同年龄的对象采取不同的策略,不同年龄的东西特点是不一样的

如果某个对象已经是一个年龄大的对象了,此时大概率还会继续存很久

老生代:GC的频次就可以降低

新生代:GC的频次会比较高

新创建的对象放到"伊甸区",绝大部分活不过第一轮的GC

伊甸区 =》幸存区 复制算法,复制的对象规模是很少,复制的开销可控

幸存区的对象也要经理GC扫描,每一轮GC都会消灭一大部分数据,剩余的东西再次通过复制算法复制到另一个幸存区

如果这个对象在幸存区经历了多次GC都存活了下来,就会晋升到老年代

相关推荐
平凡但不平庸的码农1 小时前
Go 语言:值传递 vs 指针传递
开发语言·后端·golang
Allen_LVyingbo1 小时前
面向医疗群体智能的协同诊疗与群体决策支持系统(下)
开发语言·数据结构·windows·python·动态规划
读书札记20221 小时前
Qt Creator 调试报错:Unable to create a debugging engine.
开发语言·qt
透明的玻璃杯1 小时前
Qt Creator + Windows + Protobuf 最优方案(Mqqt通讯采用的方式)
开发语言·windows·qt
冷小鱼1 小时前
JVM 深度调优实战:从 JDK 8 到 JDK 21 的演进与中间件落地
java·jvm·中间件
小书房1 小时前
Kotlin协程的运行原理
android·开发语言·kotlin·协程
隐退山林1 小时前
JavaEE进阶:SpringIoC&DI
java·开发语言·java-ee
水煮白菜王1 小时前
Claude Code 全方位使用手册
java·开发语言·网络
Highcharts.js1 小时前
金融Web App中的复杂时序数据可视化:从选型到高性能实践
开发语言·金融·highcharts·实战代码·响应式图表