JVM面试常考三大部分

135 阅读5分钟

一、JVM 内存区域划分

JVM 本质上就是一个Java进程,启动的时候,就会从OS上申请一大块内存【租了个写字楼】,然后进行装修【划分出多块区域,每块区域都有不同的功能】

  1. 堆(线程共享):创建的对象都在堆上
  2. 栈(线程私有):Java虚拟机栈【Java代码使用的】,本地方法栈【给JVM内部C++代码使用的】 主要存储的是:局部变量、方法之间的调用关系【方法调用栈】
  3. 程序计数器(线程私有):保存下一个要执行的指令地址【相当于书签】,也和程序调度有关
  4. 方法区(线程共享):存储类对象【代码 + 静态变量】

二、JVM 类加载机制

编译生成的 .class 文件是在硬盘中的,Java进程启动需要把 .class 文件从硬盘读到内存中,并构造出类对象 --->类加载

  1. 加载:找到 .class文件,打开文件,读文件;同时在内存中创建类对象【在方法区为类对象分配空间,用来存储类的结构信息(如字段、方法、常量池等)】
  2. 链接:
    • 验证:针对 .class 文件内容进行解析/校验
    • 准备:给静态变量分配内存空间,并进行赋默认值
    • 解析:把字符串常量进行初始化【将字符串常量使用某些特定符号进行标识,当真正进行类加载的时候,才真正把字符串常量放入内存,这时才使用地址替换掉刚刚的符号占位符】
  3. 初始化:针对类的静态成员进行初始化,执行静态代码块【也需要加载父类】
  4. 初始化执行完成后,,JVM会创建一个与该类对应的Class对象,用来在运行时表示这个类。

双亲委派模型

描述的是"加载"阶段,去哪个目录下找 .class 文件

每次进行类加载的时候,首先会从ApplicationClassLoader开始,向上找,一直到BootstrapClassLoader,然后扫描目录,若没有,就找ExtensionClassLoader,若没有,最后找ApplicationClassLoader,若都没有,就会抛异常 ClassNotFoundException

目的:防止程序猿写的类把标准库中的类给覆盖了

JVM自带的三个类加载器

  1. BootstrapClassLoader:加载标准库中的类【如:String,ArrayList】
  2. ExtensionClassLoader:负责加载扩展类
  3. ApplicationClassLoader:加载用户写的类

如果自己写个类必须遵守双亲委派模型吗

不必,Tomcat中。加载一些webapps中的类的时候就有自己的类加载器,并没有遵守双亲委派模型

三、JVM 垃圾回收机制

在C语言中需要用malloc申请堆上的空间,而这个空间是在操作系统上的,如果不free掉,就会造成内存泄漏;而在JVM中。它实现了自动回收机制 GC【回收单位:对象】

哪些区域需要回收

  1. 栈和程序计数器都是属于线程的,都会自动释放
  2. 方法区:放的是类对象,主要干的就是"类加载",很少涉及"类卸载",需要但是不迫切
  3. 堆:放的都是 new 出来的对象,GC 的主战场

如何确定回收的对象

Java 使用的是"可达性分析"

Python使用的是"引用计数"

  1. 引用计数

使用额外的变量进行存储有多少个引用引用着该对象,计数器为0时,则需要被回收了

- 多线程中,需要修改同一个变量,需要考虑线程安全问题
- 如果是个大对象,引入一个程序计数器负担不大;若是小对象,则会使不小的开销
- 可能会导致循环引用【致命缺陷】

image.png

  1. 可达性分析

以代码中的一些特殊的变量作为起点,然后以起点触发,看看哪些对象都能被访问到,只要对象能被访问到,就被标记为可达【比如二叉树:如果是节点就可到达,如果连接节点的线断了,就相当于不可达了】,当完成一圈标记后,剩下没有标记的就是不可达,就是垃圾

起点称为GCRoot

  1. 局部变量表中的引用【局部变量】,所有的局部变量都可以视为是 GCRoot
  2. 常量池中对应的对象
  3. 方法区中,静态引用类型的成员

怎么进行回收

标记清除

先通过可达性分析,找出垃圾,然后释放垃圾,会出现很多内存碎片

image.png

复制算法

把内存一分为二,用一半,留一半 把不是垃圾的拷贝到另一半中,然后释放之前的一整块【空间一下子少了一半,空间利用率降低了

标记整理

类似于 [顺序表] 删除元素【耗时】

分代回收

给对象引入了一个概念,年龄,这里的年龄指的是对象活过 GC 的轮次;对象刚创建出来,未被 GC 洗礼过,年龄就是0,洗礼过一次就 + 1,根据年龄不同,被分为新生代和老年代

image.png

  1. 新创建出来的对象在伊甸区
  2. 伊甸区中的对象绝大部分都活不过一轮GC,活过一轮 GC 的就放到幸存区【幸存区中的对象不会很多,所以不需要很大的空间】,从伊甸区到幸存区就是采用复制算法
  3. 幸存区的对象又会经过一轮又一轮的 GC 洗礼,每次都会淘汰一部分,未被淘汰的对象就被放入另一个幸存区中【复制算法】
  4. 当在幸存区中,很多轮的 GC 洗礼后,对象依旧存在,就将其复制到老年代中
  5. 对象在老年代中,依然要经历 GC 洗礼,但是周期性就可以缩短了【这就可以采用 标记-整理算法了,因为回收的次数减少了】

特例

如果当前的对象占用空间特别大,就直接将其放入老年代【大对象在复制算法中,不怎么友好】