2021年一线名企面经总结,聊一聊那些面试官最爱问的JVM面试题,附答案!

147 篇文章 0 订阅
140 篇文章 0 订阅

前言

这篇帖子也写了有一段时间了,现在写完,心中难免还有一些惆怅和不舍。
曾经我也有过很多矫情的感慨,也有过期待和失望。
这一路孤军奋战走来,可以说很不容易,但是现在回头看,也不过如此罢了。
曾经秋招上岸的时候,我感觉我行了,而现在抓住了更好的机会,反而感觉还有很长的路要走,要一直保持谦卑和低调。

Java内存区域

程序计数器:当前线程的字节码行号指示器,字节码解释器的工作就是通过改变计数器的值来来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
线程私有,没有OutOfMemoryError情况
Java虚拟机栈:Java方法执行的线程内存模型,每个方法被执行的时候,Java虚拟机都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接、方法出口等信息
局部变量表中存储的是基本数据类型,对象的引用和returnAddress类型
线程私有,生命周期与线程相同
如果线程请求的栈深度大于虚拟机所允许的深度,会发生StackOverflowError,若栈容量支持动态扩展,那么可以发生OutOfMemoryError情况,在HotSpot虚拟机中不会发生OutOfMemoryError
本地方法栈:为被native修饰的方法提供服务,与虚拟机栈类似
Java堆:所有对象实例以及数组都在堆上分配内存,也是垃圾回收器主要管理的内存区域
被所有线程共享的一块区域,当堆内存不够用时,会抛出OutOfMemoryError
方法区
用于储存被虚拟机加载的:类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据;在JDK1.8采用元空间来实现
也是线程共享的区域,垃圾回收主要针对常量池的回收和类型卸载,但是类型卸载比较苛刻
当方法区无法满足内存分配需求时,将会抛出OutOfMemoryError
类型卸载的条件
该类所有实例已经被回收
加载该类的类加载器已经被回收(Java自带的三个类加载器不会被回收,那么只有我们自己创建的类加载器加载的类型能被回收)
该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法

对象的创建过程?

当Java虚拟机遇到一条new指定后,首先检查这个这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载过(简单点儿说,首先检查对应的类型是否被加载过),若没有,则需要先执行类加载的过程,类加载通过后,为新生对象分配内存,并附上初始值0值并设置对象头信息,之后执行构造函数,进行对象的初始化,完成对象的创建
对象的组成?
对象头:MarkWord和类型指针(确定该对象是哪个类的实例)
实例数据:存储各种类型的字段
对其填充:任何对象都是8字节的整数倍,不足时用它来补齐,非必须有
啥是MarkWord?
MarkWord是一个有着动态定义的数据结构,包括哈希码,GC分代年龄,线程持有的锁,偏向线程Id,偏向时间戳等

内存溢出你给我说说?OutOfMemoryError和StackOverFlowError

如何产生OutOfMemory?
堆内存不够用了,会抛出这个OutOfMemoryError
你能用什么方法来抛出这个Error?
可以通过把堆内存通过参数-Xmx调小一些,然后写一个while的死循环,不断的执行append操作
那如何产生Stack Overflow Error?
这个是栈溢出,我们可以通过写两个方法,A方法调用B方法,B方法在调用A方法,这样可以产生这个Error
你还知道其他的JVM参数嘛?
知道,-Xms指定堆的初始大小,-Xss指定栈的大小,-XX:+HeapDumpOnOutOfMemoryError内存快照的Dump文件,可以分析Dump文件来查看OutOfMemoryError
列举一些垃圾回收的参数(大家随便看看吧,面试没人问过我)
指定期望的GC的停顿时间(在Parallel Scavenge、Parallel Old和G1回收器中指定):-XX:MaxGCPauseMills
改变G2的Rigion容量:-XX:G1HeapRegionSize
年轻代大小:-Xmn
比例:-XX:SurvivorRatio=8(8:1:1)
大对象直接进入老年代的阈值:-XX:PretenureSizeThreshold

判断对象已死的算法?

引用计数算法
Java没有采用这种算法,如果产生对象的循环引用会使对象无法被回收
可达性分析算法
从GC Roots根据引用关系乡下搜索,搜索过程中所走过的路径为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,则证明此对象是不可能再使用的
你给我说说啥样的对象是GC Roots?(《深入理解JVM 》 p70)
在虚拟机栈中引用的对象
在方法区中静态属性引用的对象
在方法区中常量引用的对象
在本地方法栈中引用的对象
Java虚拟机内部的引用(基本数据类型对应的Class对象,一些常驻的异常对象:NullPointException,OutOfMemoryError,还有系统类加载器)
被同步锁持有的对象
反映Java虚拟机内部情况的JMXBean,JVMTI中注册的回调,本地代码缓存等(面试我从没有说过这一条,再往下问我我不知道该怎么解释)
对象不能被GCRoots引用关联就立即被回收嘛?
其实也不是的,通过可达性分析算法分析后发现对象没有和GCRoots发生引用,那么它会第一次被标记为可回收,对象可以实现finalize()方法,如果在该方法中能够使得对象再次发生与GCRoots引用,那么便可以避免被回收,这个方法只会被调用一次

引用关系

强引用:引用赋值操作 Object o = new Object(); 无论什么情况下只用有强引用关系存在,那么对象就不会被回收
软引用:SoftReference,还有用但是非必须的对象,在发生内存溢出前,会对这些对象进行回收,如果回收完成后再不够用,便抛出内存异常错误
弱引用:WeakReference,在进行垃圾回收时,不论当前内存是否够用,都会将该引用的对象回收掉
虚引用:PhantomReference,最弱的引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象的实例,为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知

垃圾回收算法

标记复制算法:年轻代采用的都是标记复制算法,当一块内存用完了就把仍然存活的对象复制到另一块内存, 必然会产生一定空间的浪费,但是不会出现空间碎片的情况
为什么HotSpot虚拟机采用8:1:1的比例分区?
根据绝大多数的对象都熬不过第一轮GC,Hotspot采用8:1:1的分配策略,90%的空间为新生代可用内存空间,浪费的是10%,符合对象朝生夕灭的特点,但是如果10%的内存不够用时,有逃生门策略来分配对象(逃生门指的是不够分配的对象直接到老年代)
跨代引用的问题是如何解决的?(《深入理解JVM 》 p84)
垃圾回收器在新生代中建立了记忆集数据结构,用来避免进行垃圾回收的时候把整个老年代的GC Roots都扫描一遍(卡表是记忆集的一种具体实现)
标记清除算法:CMS垃圾回收器采用的算法,这种算***产生空间碎片
标记整理算法:让所有的存活对象都向内存空间的一端移动,然后清理掉边界以外的内存,没有空间碎片的烦恼

几种垃圾回收器

Serial,面向年轻代的,单线程的的垃圾回收器,采用的是标记复制算法,在进行垃圾回收的时候,必须执行Stop the world
ParNew,实际上是Serial的多线程版本,同样是标记复制算法,也需要在垃圾回收的时候Stop the world
Parallel Scavenge,面向年轻代,也是多线程的,关注的是达到一个可控制的吞吐量,采用的是标记复制算法,也需要在垃圾回收的时候Stop the world
Serial Old,Serial的老年代版本,采用的是标记整理算法,执行垃圾回收需要Stop the world
Parallel Old,是Parallel Scavenge的老年代版本,支持多线程并行收集,采用标记整理算法,同样也是关注吞吐量
CMS,获取最短回收停顿为目标,更加关注服务器的响应速度,希望给用户更好交互体验,采用的是标记清除算法,执行过程分为如下四步(两停顿两并发),会产生空间碎片,无法解决“浮动垃圾”
1 初始标记:标记GC Roots直接关联的对象,需要Stop the world
2 并发标记:从GC Roots遍历能引用到的所有对象
3 重新标记:对并发标记阶段的标记进行修正,需要Stop the world
4 并发清除:与用户线程一起运行,执行垃圾回收
Garbage First,一个浪漫的名字,它是一款面向服务器端应用的垃圾回收器,发布的初衷是为了替代掉CMS垃圾回收器,它的垃圾回收机制是面向整个堆,并将其划分为各个大小相等的Region,采用的算法是标记复制算法,它会维护一个优先级列表,根据我们设置的停顿时间来选择回收收益最大的Region进行垃圾回收,将存活的对象复制到空的Region中,通过设置停顿时间可以达到在吞吐量和响应速度上的协调,它还有一个Humongous区域,只要对象大小超过Region的一半,便直接放在这个区域中,它的执行过程为以下四个步骤(三停顿一并发)
1 初始标记:标记GC Roots直接关联的对象,需要Stop the world
2 并发标记:从GC Roots遍历能引用到的所有对象 (前连个阶段和CMS基本一致???)
3 最终标记:处理并发标记后的修正操作,需要Stop the world
4 筛选回收:对各个Rigion的回收价值进行排序,根据用户期望的停顿时间按计划回收,并将被回收的Region中存活的对象复制到空的Region中,再清理掉旧的Region,需要Stop the world
Shenandoha和ZGC这个不大问

类加载

讲讲类的生命周期
类的生命周期是七个阶段,首先类的加载,然后是连接,连接阶段分为三个步骤,是验证、准备和解析,连接完成之后是初始化,完成初始化之后是类的使用,最后是类的卸载

说说类加载的过程
类加载分三个阶段,加载、连接和初始化,其中连接阶段分为验证、准备和解析。
1 加载主要是加载二进制字节流,比如Class文件,在方法区中生成Class对象
2 验证阶段是确保Class文件中的字节流包含的信息是否符合《Java虚拟机规范》的全部约束要求,保证这些信息不会危害虚拟机的安全
(有文件格式验证、元数据验证、字节码验证、符号引用验证,我面试从没被问过具体的这几个阶段)
3 准备阶段是为类中定义的变量(静态变量)分配内存并设置变量的初始值,但是有一种特殊情况,被final修饰的话,则会直接赋值为我们要指定的值(初始值!就是0,false,null那种,初始化阶段才是我们程序员写的值,谨记)
4 解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,有类和接口的解析,字段解析、方法解析和接口方法解析(符号引用是以一组符号来描述所引用的目标,它是编译原理方面的概念,有被模块到处或者开放的包,类和接口的全限定名,字段的名称和描述符,方法的名称和描述符,方法的句柄和方法类型,动态调用点和动态常量 《深入理解JVM 》 p218;直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄)
5 初始化阶段是类加载的最后一个步骤,它会收集所有为类变量赋值和静态语句块中的语句,为这些静态变量赋值

类加载的触发条件
1 使用new关键字实例化对象的时候
2 读取或设置一个类型的静态字段
3 调用一个类型的静态方法
4 使用反射的方法对类型进行反射调用的时候
5 进行类初始化时,如果父类没有初始化,要先触发其父类的初始化
6 当一个接口中定义了JDK8加入的默认方法,如果有这个接口的实现类发生了初始化,那么接口需要在这之前完成初始化

比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被用一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等

双亲委派机制

工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父类反馈自己无法完成这个加载请求,子加载器才会尝试自己去完成类加载的过程。

双亲委派机制的作用:使得Java中的类随它的类加载器一起具备了层级关系,例如Object类,无论哪个类加载器要加载这个类,最终都会被启动类加载器加载,这样就使得Object类在类加载环境中始终是同一个类,若没有双亲委派机制的话,我们自己定义一个在java.lang目录下的Object类,那么系统中就会出现多个Object类

如何破坏双亲委派机制?
重写ClassLoader中的loadClass()方法

最后

在文章的最后作者为大家整理了很多资料!包括java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书等等!

全部免费分享给大家,有需要的朋友戳这里直接下载就好,验证码:csdn
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值