JVM虚拟机(面试重点)

目录

前言

JVM相关

面试相关

深入学习JVM

JVM运行时数据区(内存划分)

类加载

1、类加载简述 

2、类加载过程 

3、何时触发类加载

4、类加载器 

5、双亲委派模型

GC(垃圾回收机制)

1、GC回收的内容

2、GC如何回收

3、找出垃圾(判定某个对象是否是垃圾)

4、回收垃圾


前言

JVM是JavaVirtualMachine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。我们为什么要学习JVM?Java霸占企业级开发是因为内存管理,我们只需要考虑业务实现就可以,不用考虑资源释放问题,JVM就会做完这些操作,但是如果某一天出现内存溢出,那该怎么办

JVM相关

常见的虚拟机:JVMVMwaveVirtual Box 

JVM和其他两个虚拟机的区别:

1. VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器;
2. JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进行了裁剪

面试相关

当前有关JVM的面试范围大概包括以下几个方面:

1、JVM内存划分

2、JVM类加载

3、 JVM的垃圾回收

深入学习JVM

JVM会先从操作系统这里申请一大块内存空间,在这基础上再把内存空间划分成几个小的区域

JVM运行时数据区(内存划分)

区域划分:

1、堆:放的是new的对象(代码里的成员变量

2、 方法区:放的是类对象(加载好的类)(代码里的静态变量

3、栈:放的是方法之间的调用关系(代码里的局部变量

虚拟机栈:java里边用来保存调用关系的内存空间

本地方法栈:本地方法,也就是jvm内部c++写的代码,调用关系的内存空间

4、程序计数器:放的是下一个要执行的指令的地址

一个JVM进程中,堆和方法区只有一份;栈和程序计数器,每个线程有自己的一份

public class Test {
    private int x=54;  //成员变量(堆上)
    private static int y=12;  //静态变量(方法区上)

    public static void main(String[] args) {
        Test test=new Test();  //局部变量(栈上)
    }
}

类加载

1、类加载简述 

java程序在运行之前,需要先进行编译,将.java文件编译为.class文件(二进制字节码文件),运行的时候,java进程(JVM)就会读取对应的.class文件,并解析内容,在内存中构造出类对象并进行初始化。在这里的类对象在之前博客反射、jackson 、synchronized中都有用到过,类对象主要描述了这个类长撒样子,有哪些属性(属性名字、类型、private/public),有哪些方法(方法名称、参数个数、类型、返回值类型、private/public),继承自哪些父类,实现了哪些接口,类对象也是创建实例的具体依据

2、类加载过程 

程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。

加载:找到.class文件,读取文件内容,并且按照.class规范的格式来解析

验证:检验当前的.class里的内容格式是否符合要求(.class文件的要求在官方文档有明确描述)

准备:给类里的静态变量分配内存空间

解析:初始化字符串常量(把符号引用替换为直接引用)

初始化:针对类进行初始化,初始化静态成员,执行静态代码块,并且加载父类

3、何时触发类加载

使用到一个类的时候,就触发加载(类并不一定就是程序一启动就开始加载,往往是第一次使用才加载,类似于懒汉模式)

(1)创建这个类的实例

(2)使用了类的静态方法/静态属性

(3)使用了类的子类(加载子类会触发加载父类)

4、类加载器 

Java加载类,是由类加载器这样的模块来负责的,JVM自带了多个类加载器,其各自负责各自的片区(负责各自的一组目录)

1、BootStrap ClassLoader:自责加载标准库中的类

2、Extension  ClassLoader:负责加载JVM扩展的库的类

3、Application ClassLoader:负责加载自己项目里的自定义类

5、双亲委派模型

描述类加载器相互配合的工作过程,就是双亲委派模型

 

1、上述三个类加载存在父子关系

2、进行类加载的时候,输入的内容全限定类名,形如java.lang.Thread

3、 加载的时候,从Application ClassLoader开始

4、某个类加载器开始加载的时候,不会立即扫描自己负责的路径,而是先把任务委派给父“类加载器”来先进行处理

5、找到最上边的BootStrap ClassLoader再往上,没有父类加载器了,就只能自己动手加载

6、如果父亲没有找到类,就交给自己的儿子,继续加载

7、如果一直找到最下边的Application ClassLoader也没有找到类,就会抛出一个“类没找到”异常,类加载就失败了

双亲委派机制的优势:采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Thread的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Thread,而直接返回已加载过的Thread.class,这样便可以防止核心API库被随意篡改。

GC(垃圾回收机制)

对于申请的内存,进行手动释放,最大的问题就是内存泄漏。针对内存泄漏,GC(垃圾回收)就是一个主流的方案,程序员只需要负责申请内存,释放内存的工作,交给JVM来完成。JVM会自动判定当前的内存是撒时候需要释放,认为这个内存不再使用,就自动释放。

GC可以基本把内存泄漏问题处理的差不多,但是在GC中存在一个最大的问题,STW问题(Stop The World)

1、GC回收的内容

GC主要用于Java堆的回收。Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

GC中回收垃圾,不是以“字节”为单位,而是以“对象”为单位

在这里,一定要保证,是彻底不使用的内存才能进行回收 

2、GC如何回收

(1)先找出垃圾(看看谁是垃圾)

(2)在回收垃圾(释放内存)

3、找出垃圾(判定某个对象是否是垃圾)

如果一个对象再也不用了,就说明是垃圾。所以,最关键的就是,通过引用来判定当前对象是否还能被使用,因为在java,对象的使用,需要凭借引用,没有引用指向就视为是无法被使用。

判定对象是否存在引用的方法:

1、引用计数(Python、PHP采用的方法)

给每个对象都加上各个计数器,这个计数器就表示“当前对象由几个引用”每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已“死”

优点:简单,实现容易,执行效率也比较高

缺点:在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题

//循环引用,内存无法被释放
class Test1{
    Test1 x;
}

Test1 a=new Test1();
Test1 b=new Test1();
a.x=b;
b.x=a;
a=null;
b=null;

2、可达性分析(JVM采取的办法)

约定一些特殊的变量,成为“GC roots”,每隔一段时间,从GC roots出发,进行遍历,看看当前哪些变量是能够被访问到的,能被访问到的变量称为“可达”(否则就是不可达)

可以作为GC Roots 的对象:1、栈上的变量;2、常量值应用的对象;3、方法区,引用类型的静态变量

每一组都有一些变量,每个变量都视为起点,从这些起点出发,尽可能遍历,就能够找到所有访问到的对象了

4、回收垃圾

垃圾回收机器使用的几种算法:

1、标记清除

算法分为"标记"和"清除"两个阶段 : 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象

不足之处:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中
需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。

2、复制算法

针对上述内存碎片问题引进的 ,将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。

不足之处:空间利用率低;大部分对象要保留,少部分对象要开销的这种情况,复制开销就比较大

3、标记整理

 类似于顺序表删除元素,搬运操作,标记过程仍与"标记清除"过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。

相对于复制算法来说,空间利用率提高了,同时也能解决内存碎片问题,但是搬运操作比较耗时 

4、分代回收

分代算法是通过对象的不同特点(根据年龄(GC轮次计算的)来划分),采用不同的回收方式,从而实现更好的垃圾回收。是根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代(GC扫描的频率更高)和老年代(GC扫描的频率降低)。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记清理"或者"标记整理"算法。

刚创建出来的新对象,进入伊甸区;

如果新对象熬过一轮GC,仍然存在,就通过复制算法,复制到生存区中;

生存区的对象,也要经历GC的考验,每次熬过一轮GC,通过复制算法拷贝到另外一个生存区中,只要这个对象不消亡,就会在两个生存区中间来回拷贝,每一轮拷贝,每一轮GC都会筛选掉一大波对象;

如果一个对象在生存区中,反复坚持了很多轮,没有消亡,则到达到老年代;

如果对象来到了老年代,也不是进入保险箱,也会定期进行GC,频率更低了,这里采取标记整理的方式来处理老年代对象

如果对象是一个非常大的对象,则直接进入老年代(大对象进行复制算法,开销太大)

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值