2,锁的实现
在关于锁的面试过程中,一般主要问 Synchronized 和 ReentrantLock 的实现原理,更有甚者会问一些读写锁.
场景对话:
面试官:都了解 Java 中的什么锁?
我:比如 Synchronized 和 ReentrantLock... 读写锁用的不多,就没研究了
面试官:那好,你先说说 Synchronized 的实现原理吧
我:嗯,Synchronized 是 JVM 实现的一种锁,其中锁的获取和释放分别是 monitorenter 和 monitorexit 指令,该锁在实现上分为了偏向锁,轻量级锁和重量级锁,其中偏向锁在 1.6 是默认开启的,轻量级锁在多线程竞争的情况下会膨胀成重量级锁,有关锁的数据都保存在对象头中...&&@@#,(嗯,说了一大堆,面试官也没打断我)
面试官:哦,嗯,理解的还挺透彻,那你说说 ReentrantLock 的实现吧...
我:ReentrantLock 是基于 AQS 实现的
面试官:什么是 AQS?
我:在 AQS 内部会保存一个状态变量 state,通过 CAS 修改该变量的值,修改成功的线程表示获取到该锁,没有修改成功,或者发现状态 state 已经是加锁状态,则通过一个 Waiter 对象封装线程,添加到等待队列中,并挂起等待被唤醒 &&&$$(又说了一堆)
面试官:能说说 CAS 的实现原理么?
我:CAS 是通过 unsafe 类的 compareAndSwap 方法实现的(心里得意的一笑)
面试官:哦,好的,那你知道这个方法的参数的含义的么?
我:(这是在逼我啊... 努力的回想,因为我真的看过啊)我想想啊,这个方法看的时间有点久远了,第一个参数是要修改的对象,第二个参数是对象中要修改变量的偏移量,第三个参数是修改之前的值,第四个参数是预想修改后的值....(说出来之后都有点佩服自己,这个都记得,不过面试官好像还是不肯放过我...)
面试官:嗯,对的,那你知道操作系统级别是如何实现的么?
我:(我去你大爷...)我只记得 X86 中有一个 cmp 开头的指令,具体的我忘记了...
面试官:嗯,好,你知道 CAS 指令有什么缺点么
我:哦,CAS 的缺点是存在 ABA 问题
面试官:怎么讲?
我:就是一个变量 V,如果变量 V 初次读取的时候是 A,并且在准备赋值的时候检查到它仍然是 A,那能说明它的值没有被其他线程修改过了吗?如果在这段期间它的值曾经被改成了 B,然后又改回 A,那 CAS 操作就会误认为它从来没有被修改过.
面试官:那怎么解决?
我:(有完没完了啊... 我的心里是崩溃的)针对这种情况,java 并发包中提供了一个带有标记的原子引用类 "AtomicStampedReference",它可以通过控制变量值的版本来保证 CAS 的正确性.
面试官:嗯,好的,这个问题到此为止,我们再看看别的
我:.... 我能喝口水么
3,ConcurrentHashMap
当考察数据结构时,面试官一开始会问 HashMap 的实现原理,当你说出 HashMap 并非线程安全之后,会让你自己引出 ConcurrentHashMap,接着就可能开始如下的对话.
场景对话:
面试官:谈谈 ConcurrentHashMap 实现原理
我:@#¥@@基于分段锁的%%¥#@#¥,但是 1.8 之后改变实现方式了
面试官:1.8 啥方式
我:把 1.8 的实现原理说了一通,其中提到了红黑树...
面试官:能讲下红黑树的概念吗
我:红黑树是一种二叉树,并且是平衡......%......¥......,
面试官:能讲下红黑树的.....
我:打住,别问了,红黑树我只知道他是二叉树,比其他树多一个属性,其他的我都不知道
面试官:好的,那换个,你知道它的 size 方法是如何实现的么?
我:size 方法?是想要得到 Map 中的元素个数么?
面试官:对的....
我:我记得好像 size 方法返回是不准确的,平时也不会用到这个方法...
面试官:如果你觉得 size 方法返回值不准确,那如果让你自己实现,你觉得应该怎么实现呢?
我:...@#¥@@... 两眼一黑
我:等等,让我想想..... 应该可以用 AtomicInteger 变量进行记录... 嗯,对的,每次插入或删除的时候,操作这个变量,我得意的一笑...
面试官:哦,是么,那如果我觉得这个 AtomicInteger 这个变量性能不好,还能再优化么?
我:懵逼脸...(当时居然把 volitile 变量给忘记了)... 好像没有了,我想不出来了...
面试官:哦,那回头你再看看源码吧,jdk 中已经实现了...
我:哦,是么....
面试官:那今天的面试到此结束,我们后面会通知你.
我:..................
虚拟机 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 的实现写了一些分析,感兴趣的同学可以看看,这些文章看着确实有点枯燥.
细节问题
细节决定成败,在面试过程中,虽然也有运气的成分存在,但是对于细节的掌握程度,可以很好的衡量应试者的技术水平.
volatile
volatile 关键字很热门,面试很大概率会被问到
场景对话:
面试官:说说 volatile 关键字的实现原理
我:volatile 关键字提供了内存可见性和禁止内存重排序
面试官:分别解释一下
我:因为在虚拟机内存中有主内存和工作内存的概念,每个 cpu 都有自己的工作内存,当读取一个普通变量时,优先读取工作内存的变量,如果工作内存中没有对应的变量,则从主内存中加载到工作内存,对工作内存的普通变量进行修改,不会立马同步到主内存,内存可见性保证了在多线程的场景下,保证了线程 A 对变量的修改,其它线程可以读到最新值 &&%%......
面试官:如何保证的?
我:当对 volatile 修饰的变量进行写操作时,直接把最新值写到主内存中,并清空其它 cpu 工作内存中该变量所在的内存行数据,当对 volatile 修饰的变量进行读操作时,会读取主内存的数据 &&&%%¥@
面试官:你知道系统级别是如何实现的么?
我:(what,what are u 说啥呢)我记得操作 volatile 变量的汇编代码前面会有 lock 前缀指令
面试官:你这说的还是代码层面,我说的是系统级别
我:(懵逼脸...)这个再底层下去我真的没研究过了...
Object.finalize()
面试官:和我讲讲 Object 类的 finalize 方法的实现原理
我:(完全没想到面试官会问这个)新建一个对象时,在 JVM 中会判断该对象对应的类是否重写了 finalize 方法,且 finalize 方法体不为空,则把该对象封装成 Finalizer 对象,并添加到 Finalizer 链表.
面试官:恩,然后呢?
我:Finalizer 类中会初始化一个 FinalizerThread 类型的线程,负责从一个引用队列中获取 Finalizer 对象,并执行该 Finalizer 对象的 runFinalizer 方法,最终会执行原始对象的 finalize 方法,&&%%##(这块逻辑有点绕,当时答的也有点虚)
面试官:Finalizer 对象什么时候会在引用队列中?
我:(努力回想中)在发生 GC 的时候,具体在什么时间点或如何被插入到引用队列中,这块实现我已经忘记了...(我真的忘记了,只记得这块逻辑太复杂了)
面试官:恩,你验证过 finalize 方法是否会执行么?
我:恩,自己写过例子证明过,也看过源码的实现.
面试官:怎么证明的?
我:初始化一个大数组,可以明显看出 gc 之后是否被回收,然后执行 System.gc(),在 finalize 方法中输出信息 &&%%@@,(把之前做过的验证说一遍)
面试官:恩,可以...
大问题
什么是大问题,就是问题很大,让你自己去理解,把你的毕生所学都拿出来.
场景对话:
面试官:如果给你一个系统,如何去优化?
我:(优化什么?性能,稳定性,还是其它方面,只能硬着头皮上了,结合自己做的一个项目)
1,分析系统,定义指标
2,通过系统埋点,收集指标的度量值,对指标进行迭代优化 &&^%&$#
面试官:就这些?没了么?
我:(因为是电话面试,感觉当时脑袋是空白的,估计和面试官的级别也有关系)如果指标是接口性能的话,可以看下系统内存是不是可以使用缓存进行性能上的优化,比如 redis,如果是访问很频繁又不会经常变动的数据,如热点数据,可以直接使用本地缓存进行优化,毕竟一次网络请求也需要 1~2 毫秒
面试官:没了么?
我:(因为自己系统优化的经验确实不丰富,让面试官觉得怎么就只能想到如此少的优化点呢)数据库的读写分离,数据库的分库分表,如果经常条件查询数据库的话,可以引入搜索服务 es 或则 lucene 进行优化
面试过程不可能完全类似,只能见招拆招,希望大家都能拿到一个满意的 offer
—————END—————
来源: http://blog.csdn.net/bjweimengshu/article/details/79016019