JVM面试题
JVM面试题
java代码执行过程
Java源文件——>编译器——>字节码文件——>jvm——>机器码,如下步骤:
- 代码编译为class文件(指令:javac)
- 装载class(类加载器:ClassLoader)
- 执行class(解释执行、编译执行)
类加载器
- 启动类加载器:负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类
- 扩展类加载器:负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库
- 应用程序类加载器:负责加载用户路径(classpath)上的类库
类加载机制-双亲委派模型
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载
使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系
jvm内存空间划分
方法区
线程共享区域,即我们常说的永久代
(Permanent Generation),用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,运行时常量池(Runtime Constant Pool)是方法区的一部分
堆
线程共享区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域
Java 堆从 GC 的角度还可以细分为:新生代和老年代
- 新生代:是用来存放新生的对象一般占据堆的 1/3 空间;由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收
- 老年代:主要存放应用程序中生命周期长的内存对象
虚拟机栈
线程私有区域,描述java方法执行的内存模型;一个线程中,每调用一个方法创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息;每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程(栈帧随着方法调用而创建,随着方法结束而销毁);栈中可能会引发的异常:
- 线程请求的栈深度大于jvm所允许的深度-StackOverflowError
- 无法申请到足够内存-OutOfMemoryError
本地方法栈
线程私有区域,本地方法栈和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务,而本地方法栈则为Native 方法服务
pc寄存器
线程私有区域,指向虚拟机字节码指令的位置,唯一一个无OOM的区域
线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束;线程共享区域随虚拟机的启动/关闭而创建/销毁
垃圾回收算法
标记-清除
标记清除法是先找到内存里的存活对象并对其进行标记,然后统一把未标记的对象统一的清理
优点:
简单直接,速度也非常块,适合存活对象多,需要回收的对象少的场景
缺点:
会造成不连续的内存空间:清除后内存会有很多不连续的空间,这也就是我们常说的空间碎片,这样的空间碎片太多不仅不利于我们下次分配,而且当有大对象创建的时候,我们明明有可以容纳的总空间,但是空间都不是连续的造成对象无法分配,从而不得不提前触发GC
性能不稳定:内存中需要回收的对象,当内存中大量对象都是需要回收的时候,通常这些对象可能比较分散,所以清除的过程会比较耗时,这个时候清理的速度就会比较慢了
使用场景:只有小部分对象需要进行回收的,所以标记清除法比较适用于老年代的垃圾回收,因为老年代一般存活对象会比回收对象要多
标记-复制
将内存分为大小相同的两块,每次使用其中的一块。当这一块内存使用完之后,就将存活对象复制到另一块内存中;然后再把使用的内存一次清理掉,这样就使每次内存回收都是对内存空间的一半进行回收
优点:
解决了标记清除算法的空间碎片问题
缺点:
会浪费一部分空间:总是会有一块空闲的内存区域是利用不到的,这也造成了资源的浪费
存活对象多会非常耗时:因为复制移动对象的过程是比较耗时的,不仅需要移动对象本身还需要修改使用了这些对象的引用地址,所以当存活对象多的场景会非常耗时
需要担保机制:因为复制区总会有一块空间的浪费,而为了减少浪费空间太多,所以我们会把复制区的空间分配控制在很小的区间,但是空间太小又会产生一个问题,就是在存活的对象比较多的时候,这时复制区的空间可能不够容纳这些对象,这时就需要借一些空间来保证容纳这些对象,这种从其他地方借内存的方式我们称它为担保机制
适用场景:比较适合存活对象较少的场景,这也正是新生代对象的特点,所以一般新生代的垃圾回收器基本都会选择标记复制法
标记-整理
标记复制法算是完美的补齐了标记清除法的短板,即解决了空间碎片的问题,又适合使用在大部分对象都是可回收的场景。 不过标记复制法也有不完美的地方,一方面是需要空闲出一块内存空间用来腾挪对象,另外一方面它在存活对象比较多的场景也不是太适合,而存活对象多的场景通常适合使用标记清除法,但是标记清除法会产生空间碎片又是一个无法忍受的问题
所以就需要有一种算法,专门针对存活对象多,但是又产生空间碎片,还不浪费内存空间,这就是标记整理法的初衷
标记整理法分为标记和整理两个阶段,标记阶段会先把存活的对象和可回收的对象标记出来;标记完再对内存对象进行整理,这个阶段会把存活的对象往内存的一端移动,移动完对象后再清除存活对象边界之外的对象
优点:
标记整理法是解决了标记复制法浪费空间、不适合存活对象多场景的短板,又解决了标记清除法空间碎片的短板, 所以对于标记复制法不适合的场景,同时又不能忍受标记清除法的空间碎片问题,就可以考虑标记整理法
缺点:
没有任何一种算法是万能的,标记整理法看似解决了很多问题,但它本身存在很严重的性能问题,标记整理法是三种垃圾回收算法中性能最低的一种,因为标记整理法在移动对象的时候不仅需要移动对象,还要额外的维护对象的引用的地址,这个过程可能要对内存经过几次的扫描定位才能完成,同时还有清除对象的空座,既然做的事情这么多那么必然消耗的时间也越多
适用场景:内存吃紧,又要避免空间碎片的场景,老年代想要避免空间碎片问题的话通常会使用标记整理法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!