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代码用到哪个类就会触发哪个类的家加载
-
构造这个类的实例
-
调用/使用 类静态属性/静态方法
-
使用某个类的时候,如果他的父类还没有加载,也会触发父类的加载
类加载中的"双亲委派模型"
描述了类加载中,根据全限定类名找到 .class 文件的过程
类加载器:JVM中有专门的模块负责类加载
JVM默认提供了三种类加载器
-
BootstrapClassLoader(java标准库目录)
-
ExtensionClassLoader(java扩展库目录)
-
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都存活了下来,就会晋升到老年代
