作者:zhou | 来源:互联网 | 2023-09-02 10:23
简书占小狼转载请注明原创出处,谢谢!如果读完觉得有收获的话,欢迎点赞加关注愿你被这个世界温柔以待从《关于Java面试,你应该准备这些知识点》一文的阅读量和点赞程度可以发现,貌似大家
简书 占小狼
转载请注明原创出处,谢谢!
如果读完觉得有收获的话,欢迎点赞加关注
愿你被这个世界温柔以待
从《关于Java面试,你应该准备这些知识点》 一文的阅读量和点赞程度可以发现,貌似大家更喜欢这类文章,也许是技术型的文章看着比较的枯燥,这些只是我近段时间求职面试时所遇到的一些问题,整理出来希望对有需要的同学提供帮助,可以更系统的去学习各个知识点。
虚拟机JVM相关
这块内容并非每个面试官都会问,但是如果是应聘高级职位的话,这一环节是不可缺少的,面试的难易程度也不一样,有些面试官或许让你讲讲虚拟机的内存模型即可,有些也会让你解释垃圾回收的实现,当然也会有虚拟机调优的实战经验,线上问题排查等等。
场景对话:
面试官:Java虚拟机有了解么?
我:恩,略有接触过…(水哥说过,话不能说太满,容易打脸)
面试官:那你先讲讲它的内存模型吧
我:Java堆,Java栈,程序计数器,方法区,1.7的永久代,1.8的metaspace….(噼里啪啦概念讲一通,简短描述下每个内存区的用途,能想到的都讲出来,不要保留,不要等面试官问 “还有吗?”)
面试官:好,一般Java堆是如何实现的?
我:在HotSpot虚拟机实现中,Java堆分成了新生代和老年代,我当时看的是1.7的实现,所有还有永久代,新生代中又分为了eden区和survivor区,survivor区又分成了S0和S1,或则是from和to,(这个时候,我要求纸和笔,因为我觉得这个话题可以聊蛮长时间,又是我比较熟悉的…一边画图,一边描述),其中eden,from和to的内存大小默认是8:1:1(各种细节都要说出来…),此时,我已经在纸上画出了新生代和老年代代表的区域
面试官:恩,给我讲讲对象在内存中的初始化过程?
我:(千万不要只说,新对象在Java堆进行内存分配并初始化,或是在eden区进行内存分配并初始化)要初始化一个对象,首先要加载该对象所对应的class文件,该文件的数据会被加载到永久代,并创建一个底层的instanceKlass对象代表该class,再为将要初始化的对象分配内存空间,优先在线程私有内存空间中分配大小,如果空间不足,再到eden中进行内存分配…^&&*%
面试官:恩,好,说下YGC的大概过程…
我:先找出根对象,如Java栈中引用的对象、静态变量引用的对象和系统词典中引用的对象等待,把这些对象标记成活跃对象,并复制到to区,接着遍历这些活跃对象中引用的对象并标记,找出老年代对象在eden区有引用关系的对象并标记,最后把这些标记的对象复制到to,在复制过程还要判断活跃对象的gc年龄是否已经达到阈值,如果已经达到阈值,就直接晋升到老年代,YGC结束之后把from和to的引用互换(能多说点就多说点,省的面试官再提问,我把老年代的cms回收也大致说了一遍,以为面试官会跳过这个话题了,还是太年轻了)。
面试官:你刚刚说到在YGC的时候,有些对象可能会发生晋升,如果晋升失败怎么处理?
我:….(断片了几秒钟,我记得我分析过这段代码的,但是印象不深刻了)我记得在标记阶段时,会把对象和对应的对象头数据保存在两个栈中,如果晋升失败的话,就把该对象的对象头复原…
面试官:那你在实际项目中有碰到这种情况么,会导致什么问题?
我:…(这我真没有遇到过)对,有遇到过一次,在分析gc日志的时候,发现YGC发生之后,日志显示gc后的内存变大了,后来查出来是因为对象的晋升失败造成的。(我隐约记得看过笨神的一篇文章,回答的心里很虚)
面试官:(没有反驳,继续问)有过虚拟机性能调优的经验么?
我:(说实话,调优经验真的不多)恩,有一点吧,不是很足,就是我们XX项目上线的时候,发现YGC特别的频繁^&8&,通过调整新生代的大小(线上环境的虚拟机参数是默认的),同时检查业务逻辑代码&*&$$~~!
面试官:恩?还有么?
我:(面试这么久,好怕面试官的下一句是 “恩?还有么?”,显然面试官还不满足我的回答,但是我也只能答到这个地步了…)恩,经验确实有限,目前就根据这个项目做过一些相关的优化。
面试官: 。。。。。。
我:。。。。。。
面试官: 那我们看看别的吧。
关于虚拟机方面的文章,我针对hotSpot的实现写了一些分析,感兴趣的同学可以看看,这些文章看着确实有点枯燥。
相关文章:
JVM源码分析之JVM启动流程
JVM源码分析之堆内存的初始化
JVM源码分析之Java类的加载过程
JVM源码分析之Java对象的创建过程
JVM源码分析之如何触发并执行GC线程
JVM源码分析之垃圾收集的执行过程
JVM源码分析之新生代DefNewGeneration的实现
JVM源码分析之老年代TenuredGeneration的垃圾回收算法实现
细节问题
细节决定成败,在面试过程中,虽然也有运气的成分存在,但是对于细节的掌握程度,可以很好的衡量应试者的技术水平。
volatile
场景对话:
面试官:说说volatile关键字的实现原理
我:volatile关键字提供了内存可见性和禁止内存重排序
面试官:分别解释一下
我:因为在虚拟机内存中有主内存和工作内存的概念,每个cpu都有自己的工作内存,当读取一个普通变量时,优先读取工作内存的变量,如果工作内存中没有对应的变量,则从主内存中加载到工作内存,对工作内存的普通变量进行修改,不会立马同步到主内存,内存可见性保证了在多线程的场景下,保证了线程A对变量的修改,其它线程可以读到最新值&&%%……
面试官:如何保证的?
我:当对volatile修饰的变量进行写操作时,直接把最新值写到主内存中,并清空其它cpu工作内存中该变量所在的内存行数据,当对volatile修饰的变量进行读操作时,会读取主内存的数据&&&%%¥@
面试官:你知道系统级别是如何实现的么?
我:(what,what are u 说啥呢)我记得操作volatile变量的汇编代码前面会有lock前缀指令
面试官:你这说的还是代码层面,我说的是系统级别
我:(懵逼脸…)这个再底层下去我真的没研究过了…
相关文章:《java volatile关键字解惑》
Object.finalize
场景对话:
面试官:和我讲讲Object类的finalize方法的实现原理
我:(完全没想到面试官会问这个)新建一个对象时,在JVM中会判断该对象对应的类是否重写了finalize方法,且finalize方法体不为空,则把该对象封装成Finalizer对象,并添加到Finalizer链表。
面试官:恩,然后呢?
我:Finalizer类中会初始化一个FinalizerThread类型的线程,负责从一个引用队列中获取Finalizer对象,并执行该Finalizer对象的runFinalizer方法,最终会执行原始对象的finalize方法,&&%%##(这块逻辑有点绕,当时答的也有点虚)
面试官:Finalizer对象什么时候会在引用队列中?
我:(努力回想中)在发生GC的时候,具体在什么时间点或如何被插入到引用队列中,这块实现我已经忘记了…(我真的忘记了,只记得这块逻辑太复杂了)
面试官:恩,你验证过finalize方法是否会执行么?
我:恩,自己写过例子证明过,也看过源码的实现。
面试官:怎么证明的?
我:初始化一个大数组,可以明显看出gc之后是否被回收,然后执行System.gc(),在finalize方法中输出信息 &&%%@@,(把之前做过的验证说一遍)
面试官:恩,可以…
相关文章: 《深入分析Object.finalize方法的实现原理》
大问题
什么是大问题,就是问题很大,让你自己去理解,把你的毕生所学都拿出来.
场景对话:
面试官:如果给你一个系统,如何去优化?
我:(优化什么?性能,稳定性,还是其它方面,只能硬着头皮上了,结合自己做的一个项目)
1、分析系统,定义指标
2、通过系统埋点,收集指标的度量值,对指标进行迭代优化&&^%&$#
面试官:就这些?没了么?
我:(因为是电话面试,感觉当时脑袋是空白的,估计和面试官的级别也有关系)如果指标是接口性能的话,可以看下系统内存是不是可以使用缓存进行性能上的优化,比如redis,如果是访问很频繁又不会经常变动的数据,如热点数据,可以直接使用本地缓存进行优化,毕竟一次网络请求也需要1~2毫秒
面试官:没了么?
我:(因为自己系统优化的经验确实不丰富,让面试官觉得怎么就只能想到如此少的优化点呢)数据库的读写分离,数据库的分库分表,如果经常条件查询数据库的话,可以引入搜索服务es或则lucene进行优化