前言
🌟🌟本期讲解关于HTTPS的重要的加密原理~~~
🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客
🔥 你的点赞就是小编不断更新的最大动力
🎆那么废话不多说直接开整吧~~
目录
编辑
📚️1.认识JVM
📚️2.JVM的内部解析
2.1JVM的内存区域划分
1.堆区(只有一份)
2.栈区(可以有N份)
3.程序计数器
4.元数据区
2.2JVM类加载机制
1.加载
2.验证
3.准备
4.解析
5.初始化
2.3垃圾回收机制(GC)
1.GC的前言
2.回收的内存
3.识别垃圾
引用计数
可达性分析
4. 内存空间的释放
标记清除
复制算法
标记整理
分代回收
📚️3.总结
📚️1.认识JVM
在开始认识JVM的时候,这个本就不是我们工作中所运用得到的,那么我们在开始学习JAVA的时候,我们就听说过JVM,即如下三个:
jdk:Java开发工具包
jre:Java运行时环境
jvm:Java虚拟机
并且还了解过:编译型语言和解释型语言,而这里我们所学习的Java就是一个“半编译型”与“半解释型”的语言;
所以我们这里Java这样实现的目的就是为了跨平台,例如:C++编会编译成二进制的机器指令,而不同的CPU的机器指令是不一样的
具体的实现过程:
首先通过java.c将代码.java文件转化为.class字节码文件,然后再具体的系统平台上执行的时候,通过jvm将上述的字节码文件转化为CPU能够识别的机器语言
总结:所谓的jvm就是在Java编程中实现跨平台,充当中间人翻译官的角色
📚️2.JVM的内部解析
2.1JVM的内存区域划分
这里的jvm其实也是一个进程,那么我们之前在学习了解的计算机的工作原理:【后端开发】JavaEE初阶——计算机是如何工作的???-CSDN博客
了解到进程是需要操作系统分配内存空间的,这里支持了Java程序的执行,我们在Java程序中变量分配到的内存资源,其实就是jvm从操作系统内分配到的内存资源;而不同功能会对应分割不同的区域,这就是“内存的区域划分”
这里的内存内部如下:
1.堆区(只有一份)
即在代码中new出来的对象都是存储在这个区域里,对象中持有的非静态成员变量也是在这个堆里;
堆⾥⾯分为两个区域:新⽣代和⽼⽣代,新⽣代放新建的对象,当经过⼀定 GC 次数之后还存活的对象 会放⼊⽼⽣代。新⽣代还有 3 个区域:⼀个 Endn + 两个 Survivor(S0/S1),后面会将
2.栈区(可以有N份)
本地方法栈/虚拟机栈,描述了方法的调用的关系,和局部变量
补充:
3.程序计数器
占有比较小的空间,专门又来存储下一条要执行的java程序指令的地址
4.元数据区
元数据区是一个计算机常见的概念,这里主要指定就是辅助性质的,描述性质的属性;
例如:在硬盘上,不仅仅要存储文件的数据信息,还有其他的辅助属性:“文件的大小,文件的存储位置,文件的拥有者,文件的访问权限”统称为元数据;
那么此时就有如下的面试题:
class Test{private int n;private static int m;}public static void mian(String[] arg){Test t=new Test();
}
此时分析:n,t,m在内存区域的哪里???
n:由于n是一个成员变量,那么此时就是在堆上面的
t:是一个引用类型的局部变量,那么此时就是在栈上面
m:由于m是static修饰的变量,叫类对象,那么此时就是在元数据区里
被static修饰的变量叫类属性,修饰的方法叫类方法
非static修饰的变量叫实例属性,非修饰的方法叫实例方法
所谓的类对象:就是Test.class,在.class文件加载到内存上面的时候,就会将信息用对象来表示,这个对象就是 类对象,包含了很多的属性
2.2JVM类加载机制
类加载:所谓的加载其实指定就是Java进程运行吧.class文件从硬盘读取到内存并进行一系列的校验解析的过程;
这里的加载的步骤主要分为5步
1.加载
把硬盘上的.class文件找到,打开文件读取里面的内容(是一个二进制的数据)
重点(双亲委派模型)
所谓的双亲委派模型就是描述了如何查找.class文件的策略;
jvm在进行类加载的操作中,有个专门的模块称之为“类加载器”,这里的的类加载器默认是3个;
BootstrapClassLoader(负责查找目标库的目录)
ExtensionClassLoader(负责查找扩展库的目录)
ApplicationClassLoader(负责查找当前项目的代码目录,以及第三方库的目录)
注意:上述的三个类加载器的关系是父子关系,但是这里的父子关系指的就是二叉树的指针关系
具体的工作的流程:
从ApplicationClassLoader入口进入,但是自己不会搜索自己负责的目录,而是会发给ExtensionClassLoader,同上也不会搜索,继续给父亲BootstrapClassLoader,然后由于没有父亲,那么就会开始目标库目录,如果找到了就直接进入打开文件,若没有找到,那么给ExtensionClassLoader开始搜索扩展库目录,同上找到了打开文件,没有继续给儿子;
最后没有找到会继续在孩子这一辈的类加载器搜索,但是默认 ApplicationClassLoader是没有孩子的,那么就会类加载失败,抛出ClassNotFountExecption
具体的模型如下:
优点:
可以有效的避免自己写的类不小心和标准库的名字重复后,导致标准库的类功能失效
2.验证
当前需要知道保证读到的文件的内容是合法的.class文件内容
具体的验证的依据,在Java虚拟机规范中有明确的格式的说明:
解释:
u4:四字节无符号整数
u2:两个字节无符号整数
magic:魔幻数字,表示当前二进制的文件的格式是那种类型
field_info:另一个结构体
3.准备
给类对象申请内存的空间,此时申请到的内存空间,里面默认值就全部是0(这个阶段的类对象中的静态成员变量的值就相当于是0)
4.解析
主要针对的就是类中的字符串常量进行处理
例如:
class Test{private String s="Hello";
}
那么此时的.class文件就会包含这个hello,那么此时的存储的情况就是如下的:
解释:
我们可以知道这里的s变量存储了hello的字符串常量的地址,那么在文件中不存在地址这个概念的,那么这里的红线就是偏移量来代替了地址;
后面.class文件加载到内存中的时候,就可以直接替换成真实的“hello”的地址了;
5.初始化
就是针对这个类对象完成后续的初始化;还要执行静态代码块的逻辑,或者触发父类的加载
2.3垃圾回收机制(GC)
1.GC的前言
在C语言中,动态管理内存,那么就是molloc申请内存,free释放内存,那么此时申请的内存是跟随整个进程的,这一点对于服务器是非常不友好的,如果申请了内存不释放就会导致内存泄漏的问题,但是free需要手动调用,就会又不确定的因素
所以在java中就引入了自动释放内存的机制就是:GC
但是这里的C语言C++追求的是极致性能,所以就没有引入,因为垃圾回收有一个重要的问题
(stop the world)STW问题
当触发垃圾回收机制的时候,很有可能当前程序的其他的业务逻辑就会暂停;但是现在的STW的时间控制在1ms以内了,也是可以接受的
2.回收的内存
说到垃圾回收机制,我们知道在内存中的区域进行了分区的,那么垃圾回收,回收的是哪一部分的“垃圾”呢?
程序计数器:不需要GC
栈:不需要GC,局部变量在代码执行结束之后会自动的销毁,与垃圾回收没有关系
元数据区:不需要GC,这里只涉及类加载,很少涉及类卸载
堆:这里就是GC的主要战场
在垃圾回收中,跟准确的说法就是“回收对象”
在堆中,内存的回收具体是如下所示的:
解释:
中间的那块,代表就是一半不回收,一半回收,一般就是不进行回收,叫做“骑强派” 这里包含的空闲的内存就不用管了
3.识别垃圾
所谓的垃圾识别就是判断出当前这个对象是否还继续进行使用,如果没有继续进行使用,那么这个对象就可以进行回收了;
具体举例:
void func{Test t=new Test();t.func1;
}
那么此时的情况就是如下的:
解释:
那么我们可以知道,局部变量的生命的周期是很短的,那么接下来在执行到“}”的时候,这里就被释放了,没有指向这个地址,那么这个堆里的对象就是垃圾了;
那么此时涉及下面的代码,那么就比较复杂了:
Test t1=new Test();
Test t2=t1;
Test t3=t2;
那么就引入了一个新的概念,来解决这个问题复杂的情况;
引用计数
这种方法在JVM中并没有进行使用,那么此时主要是用在其他的主流的语言上面的(Python,PHP)
具体方法:
就是给对象安排一个额外的空间,来保存当前这个对象有几个引用
具体实例如下:
Test a=new Test();
Test b=a;
a=null;
b=null;
对应的图示:
解释:
此时我们可以看到,两个引用指向这个对象,那么申请空间的保存的就是2,两个引用,如果栈的局部变量被释放后,或者为“null”那么这个里面的引用就是0,此时就可以进行释放了;
缺点:
问题1:消耗额外的内存空间:如果对每个对象安排一个计数器,如果程序中的对象太多了,那么此时就会造成额外的空间消耗
问题2:引用计数器可能会导致“循环引用的问题”,应用计数就无法进行正常的工作了;
什么是循环引用,具体代码如下:
class Test{Test t;
}Test a=new Test();
Test B=new Test();a.t=b;
b.t=a;
a=null;
b=mull;
那么具体的图示就是如下所示的:
解释:
那么此时就会发现,由于a.t=b,这个操作,导致在堆里的new的对象中多出来了一个地址的指向,那么此时就会发现,计数器中的值就是2,如果a,b都为null了,那么就会出现计数器为1,但是没有任何指向对象的引用;
我们可以知道此时就相当于是死锁的情况了,两个对象不能调用,但是这两个对象却不是垃圾;那么此时就引入了另一个垃圾识别的分析
可达性分析
这里的可达性其实就类似于二叉树的遍历,若其中一个点被断了,那么后面的子结点就是不可达的,那么此时后面的就是垃圾,因该被回收了;
具体的情况图示如下:
解释:
如果此时的结点就都是可达的,若其中b=null,那么包括b和它的子结点都是不可达的,那么就是垃圾,就应该被回收了;
4. 内存空间的释放
在上述的垃圾的识别标记后,那么此时就应该执行内存空间的释放的操作了,具体的内存释放的操作即如下三种的操作的方式
标记清除
做法:将标记的垃圾直接进行清除(直接释放掉)
具体的图示如下:
那么此时的黑色部分就是垃圾被清除的部分内存;
问题:
此时就造成了内存的碎片问题,因为在申请内存空间的时候是申请的一段连续的内存空间,此时就会造成内存总空间够,但是申请不了,因为内存不是连续的是断层的
复制算法
做法:将不是垃圾的内存对象复制到另一半内存中,然后将复制前的内存空间连垃圾一起释放掉
那么此时的图示就是如下的:
解释:
那么此时就是将不是垃圾的内存对象复制到另一边,然后将左边区域直接全部释放掉,此时就直接规避了内存的碎片化的问题
缺点:
缺点显而易见,就是总共可用的内存直接性的下降了,并且复制的对象很多的时候,复制的开销也会变大
标记整理
做法:类似与顺序表中的删除中间元素的操作(搬运),就是将非垃圾的内存对象把垃圾给覆盖掉
具体的实例如下:
解释:
此时就将非垃圾的内存对象给往前面进行搬运,这样就会解决内存碎片化问题,以及内存总空间减少的问题,但是会涉及到内存搬运的开销
分代回收
做法:就是依据不同类的对象,采取不同的方式
此时就是jvm中有专门负责周期性的扫描,一个对象被扫描一次可达性满足,那么就会年龄+1;jvm机会根据年龄的差异把堆内存的空间分为两个部分,“新生代/老年代”
具体的图示如下:
解释:
new出来的新的对象就存储在伊甸区,经过扫描后,大部分的对象都直接GG,然后幸存下来的就在生存区,再次进行伊甸区和生存区的扫描,若在伊甸区就和上面一样,若是生存区,那么就拷贝到另一个区域,反复如此(每次扫描年龄+1)若若干轮的GC任然存在,那么就认为这个对象的生存的周期比较长,那么就会拷贝在老年区域了;(此时的扫描的频率就会下降)若老年区的对象变成垃圾了,那就会通过标记整理的方式进行释放内存;
ok以上就是垃圾回收的具体方法步骤,当然这里还涉及到“垃圾收集器”,小伙伴们可以去了解一下CMS,G1,ZGC这三个
📚️3.总结
💬💬本期小编主要总结了JVM面经的常考题,主要讲解了JVM的内部实现原理包括内存区域的划分,类加载机制,垃圾的回收机制(GC)从什么是垃圾,如何进行识别,到最后的内存对象的释放,都做了比较详细的介绍~~~
🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!
💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。
😊😊 期待你的关注~~~