愿你被这个世界温柔以待
从《关于 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&, 通过调整新生代的大小 (线上环境的虚拟机参数是默认的), 同时检查业务逻辑代码 &*&$$~~!
面试官: 恩? 还有么?
我:(面试这么久, 好怕面试官的下一句是 "恩? 还有么?", 显然面试官还不满足我的回答, 但是我也只能答到这个地步了...) 恩, 经验确实有限, 目前就根据这个项目做过一些相关的优化.
面试官: ......
我:......
面试官: 那我们看看别的吧.
细节问题
细节决定成败, 在面试过程中, 虽然也有运气的成分存在, 但是对于细节的掌握程度, 可以很好的衡量应试者的技术水平.
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 进行优化.
我这里有一个 java 新手学习交流群: 前面是 494 中间是 801 最后是 931, 无论你是大牛还是小白, 是想转行还是想入行都可以来了解一起进步一起学习! 裙内有开发工具, 很多干货和技术资料分享!
来源: http://www.jianshu.com/p/bacf53b1c34f