一.JVM内存区域划分
一个Java程序跑起来就得到一个Java进程=JVM+上面运行的字节码指令
1.程序计数器
JVM上比较小的空间。
保存下一条要执行的指令的地址。
2.堆
JVM上最大的空间。只有一份!
new出来的对象都在堆上。
3.栈
分为:Java虚拟机栈、本地方法栈。只有一份!
stack、overfloow
4.元数据区
保存代码中涉及到的类的相关信息。
包含Java程序中的指令、类的static属性
一个变量处于哪个内存区域和变量是不是内置类型无关,和变量的形态有关:
①局部变量------栈
②成员变量------堆
③静态成员变量------元数据区
二.JVM类加载的过程
1.简单解释
Java程序 .java文件
javac编译 .class文件(磁盘)
运行Java进程时,JVM需要**读取.class中的内容(类加载)**并执行里面的指令。
类加载:把类涉及到的字节码从硬盘读到内存中(元数据区),加载一个.class文件也会对应创建一个类对象(基于类对象可以创建该类的实例)
2.类加载的具体步骤
JVM默认有三个加载器
|------------------------|---------------------|
| BootstrapClassLoader | 加载标准库中的类 |
| ExtensionClassLOader | 加载扩展类 |
| ApplicationClassLoader | 加载第三方库中的类/自己写的代码中的类 |
(1)加载
把.class文件找到,代码中先见到类的名字,然后进一步找到对应的.class文件,打开并读取文件内容。
双亲委派模型
输入类的全限定名(字符串),找到对应的.class文件
(2)验证
验证读到的.class文件的数据名是否正确合法
(3)准备
分配内存空间。
根据刚才读到的内容,确定类对象需要的内存空间,申请这样的内存空间,并把内存空间中的所有内容初始化为0
(4)解析
主要针对类中的字符串常量进行处理。
Java虚拟机将常量池的符号引用替换为直接引用,即初始化常量
(5)初始化
执行静态成员的初始化。
若当前加载的类还有父类,且父类未被加载,则此环节也去触发针对父类的加载
三.垃圾回收机制(GC)
C/C++中,malloc/new一个对象,需要手动释放内存free/delete
1.C++引入GC的代价
(1)消耗不少CPU开销,进行垃圾的扫描和释放
(2)进行GC可能会触发STW问题,导致程序卡顿
2.JVM垃圾回收过程
垃圾回收就是回收内存(以对象为维度进行回收,也可以说是回收对象)

(1)找出谁是垃圾(不再使用的对象)
①引用计数
给每个对象分配一个计数器,衡量有多少个引用。计数器计算引用数(增加一个对象,计数+1,减少一个对象,计数-1),计数器为0时,对象就是垃圾。
问题:消耗额外的时间;引用计数可能导致循环引用,使判定出错
②可达性分析
JVM中有一种周期性线程,扫描代码中的所有对象,判断某个对象是否可达(可被访问到),对应的不可达对象就是垃圾。
可达性分析的起点成为GC root,一个程序中有很多GC root
GC root种类:
栈上的局部变量(引用类型)
方法区中静态的成员(引用类型)
常量池引用指向的对象
(2)释放垃圾的内存空间
①标记-清除

直接对内存中的对应对象进行释放。
问题:会引入内存碎片问题,后续申请内存可能失败。
②复制算法

将空间一分为二,只使用其中一半,把不是垃圾的对象拷贝到另一侧(确保被拷贝对象连续),然后把一半空间释放。
缺点:内存空间利用率低;若存活下来的对象比较多,复制成本比较大
③标记整理

类似于顺序表删除中间元素
真正机制------分代回收
JVM堆主要分为新生代、老年代、元空间
1.新生代:复制算法(Copying)
适用原因:新生代对象 98% 朝生夕死,存活对象极少 ,复制成本极低。原理:将内存分为两块,只使用一块;GC 时把存活对象复制到另一块,直接清空原内存,无内存碎片
2.老年代:标记 - 清除 / 标记 - 整理算法
适用原因:老年代对象存活率高,复制算法效率极低。
标记 - 清除:先标记存活对象,再清除垃圾,简单但产生内存碎片。
标记 - 整理:标记后将存活对象向一端规整,清除端外垃圾,无碎片但效率稍低。
3.元空间:无专门 GC,仅在类卸载、常量池清理时触发回收。