❣博主主页: 33的博客❣
▶️文章专栏分类:JavaEE◀️
🚚我的代码仓库: 33的代码仓库🚚
🫵🫵🫵关注我带你了解更多进阶知识
目录
- 1.前言
- 2.JVM内存区域划分
- 3.类加载
- 3.1双亲委派模型
- 4.垃圾回收(GC)
- 4.1垃圾识别
- 4.1.1引用计数
- 4.1.2可达性分析
- 4.2垃圾释放
- 4.2.1标记释放
- 4.2.2复制算法
- 4.2.3标记整理
- 4.2.4分代回收
- 5.总结
1.前言
JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机。虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统,JVM本省是一个比较复杂的东西,我主要从三个方面进行讲述:内存区域划分,类加载机制垃圾回收算法。
2.JVM内存区域划分
JVM其实就是一个进程,进程在运行过程中,要从操作系统申请资源空间,JVM申请的空间会划分为几个不同的区域,每个区域作用各不相同。这些资源支持了后续Java程序的执行。
堆区:整个进程只有一份,代码中new出来的对象,对象中的非静态成员变量,放在堆区
栈区:虚拟机栈记录了JAVA代码中的调用关系,java局部变量。
程序计数器:专门用来存储下一条Java指令的地址
元数据:整个进程只有一份,一些辅助性质的,描述性质的属性,我们所写的JAVA代码,各种逻辑运算,会通过javac完成代码转换成字节码,此时这些字节码在程序运行时就会被JVM加载到元数据中,此时当前程序如何执行,做哪些事就按照上述元数据区记录的字节执行。
下列元素n,m,t各在什么区?
class Test{
int n;
static int m;
}
main(){
Test t=new Test();
}
t为局部变量在栈区
new Test在堆区
n是成员变量也在堆中
m是static修饰,类属性在元数据区
3.类加载
类加载就是指JAVA程序运行是,把.class文件从硬盘中读到内存,再进行一系列解析。
类加载大致可以分为5步:
1)加载
把硬盘上的.class文件找到,打开文件读取文件内容
2)验证
确保读到的文件内容是合法的
3)准备
给类申请内存空间,默认值为全0
4)解析
主要针对类中的字符串常量进行处理
例如有一串代码为String s=”hello";s变量存入的是hello的地址,但是再.calss文件中不纯在地址的概率,那么为了就可以给s填一个偏移量。
5)初始化
把类对象的各个部分的属性进行赋值填充
3.1双亲委派模型
在类加载的时候有一个重要模型就是双亲委派模型,描述了如何找到.class文件。在进行加载操作的时候有一个专门的模块叫做类加载器,默认含有三个
BootstrapClassLoader:负责查找标准库的目录
ExtensionClassLoader:负责查找扩展库的目录
ApplicationClassLoader:负责查找当前项目的代码目录,第三方库目录
上述三个类加载器存在父子关系,类似于二叉树,有一个指针指向父类加载器
双亲委派工作流程:
1)从ApplicationClassLoader作为入口,开始工作
2)ApplicationClassLoader不会立即搜索自己负责的目录,会把搜索的任务交给自己的父亲
3)进入ExtensionClassLoader,也不会立即搜索自己负责的目录,也会把搜索的任务交给自己的父亲
4)进入BootstrapClassLoader,不会立即搜索自己负责的目录,也会把搜索的任务交给自己的父亲
5)BootstrapClassLoader发现自己没有父亲节点,此时会真正的搜索负责的目录,如果找到了就执行后续操作,没有找到就返回给孩子
6)ExtensionClassLoader收到父亲的任务以后,会搜索自己负责的目录,如果找到了就执行后续操作,没有找到就返回给孩子
7)ApplicationClassLoader收到父亲的任务以后,会搜索自己负责的目录,如果找到了就执行后续操作,没有找到就返回给孩子,但如果没有孩子就说明类加载失败,抛出ClassNotFoundException
4.垃圾回收(GC)
垃圾回收是回收的内存,其中主要回收的是堆中的内存,栈中的内存在代码块结束以后会自动销毁。那么垃圾回收具体是怎么展开的呢?主要分为垃圾识别和垃圾释放
4.1垃圾识别
判定new出来的对象在后续是否要使用,如果不再使用旧标记为垃圾。
例:
void func(){
Test t=new Test();
t.find();
}
当程序执行到}时,t就被释放,此后就不再使用new Test()对象了,就可以标记为垃圾,但如果有些大妈比较复杂,例如
Test t=new Test();
Test m=t;
Test n=m;
Test z=n;
此时就有很多引用指向new Test()对象,就学要确保没有任何一个引用指向这个对象才能标记为垃圾,那么我们怎么知道什么时候没有引用指向它呢?
4.1.1引用计数
当我们创建一个对象时,给每个对象分配一个额外的空间记录当前对象有几个引用。
每增加一个引用,计数位置+1,每减少一个引用,技术位置-1,如果为0就标记为垃圾
问题一:
这样会消耗额外的空间,当我们的对象非常多,但对象的体积非常小,那么久可能导致计数所占的空间就占了所有空间的大部分。
问题二:
可能会引起循环引用,那么就永远释放不了资源
class Test{
Test t;
}
Test a=new Test();
Test b=new Test();
a.t=b;
b.t=a;
a=null;
b=null;
这俩对象不能再使用也释放不了
4.1.2可达性分析
在写代码的时候会定义很多变量,就可以从这些变量作为起点开始遍历,所谓的遍历就是会沿着这些变量的引用类型成员再京一部访问,所有能被访问到的自然不是垃圾
4.2垃圾释放
4.2.1标记释放
最直接的方法就是把标记为垃圾的直接释放掉:但是这样会生成很多内存碎片,后续如果有类对象再申请空间可能就不够用
4.2.2复制算法
把一个空间分成两半,假设数据存放于左半边那么把不是垃圾的数据全部赋值到右半再讲左半数据全部释放掉。
灰色为垃圾标记,数字为数据
这样总的内存空间减少,且复制的开销也很大。
4.2.3标记整理
该方案是把所有的数据依次向前搬运,覆盖掉垃圾区,再把剩下的垃圾进行释放。
虽然这样能解决内存碎片的问题,但搬运的内存开销很大
4.2.4分代回收
JVM中有专门的线程负责周期性扫描,一个对象如果被扫描了一次,年龄就+1,JVM会根据对象年龄的差异,把整个堆分成2部分,新生代,老年代。
1)当代码中new出一个新的对象,这个对象就是被创建在伊甸区,伊甸区的对象大部分都活不够第一轮,生命周期非常短
2)第一轮GC扫描完成以后,少数伊甸区幸存的对象会通过复制算法拷贝到生存区,在后续GC扫描的时候不仅会扫描伊甸区还会扫描生存区的对象,生存区的大多数对象也会在扫描中被标记为垃圾,少数存活,就会继续通过复制算法拷贝到另一个生存区,每次经历一轮GC年龄就+1.
3)如果这个对象在生存区中经历了若干轮依然在,那么就会把这个对象拷贝到老年区。
4)老年代的对象也会被GC扫描只是频次大大减小
5)对象在老年代结束以后就会释放内存。
5.总结
本篇文章主要JVM内存区域划分,类加载,双亲委派模型,垃圾识别,引用计数,可达性分析,垃圾释放,分代回收等等。
下期预告:MySQL