polin被吊打
MatrixYG是宇宙条的工程师,手撕红黑树,LeetCode800题,国内ICPC牌子拿到手软,脚踩八股文,阿里字节资深划水工程师。这天他听说好朋友polin面试被吊打了:
MatrixYG带着自己的字节工牌(pdd买的),来到了这家公司。
面试开始
MatrixYG来到了公司的面试房间,迎面走来的是一个看着不是很年轻的大哥,看上去有点东西。当场有点虚:
但是既来之则安之,MatrixYG很快镇定下来。简单自我介绍:
MatrixYG:面试官你好,我是xx。现就职于xx,职位是后端开发工程师。主要负责我们部门xx。在工作的两年多里面主要做了xx。我的本科是毕业于xx,。。。。
面试官:扫了一眼MatrixYG脖子上pdd买来的工牌说到:好的,看你简历很优秀啊。我看你简历上写的主要是Java技术栈,那我们聊点Java基础?
-
MatrixYG:完全OK。(内心OS:八股文我昨天才背过,嘿嘿)
第一回合
面试官:
==能给我讲讲Java为什么能够跨平台吗?==
MatrixYG内心一激灵,知道了面试官的套路了。这哥们上来就问我这个问题,看来是要问我虚拟机啊。懂了,看我的。
MatrixYG:
==Java之所以能够跨平台,是依赖于Java虚拟机,Java程序被编译成了字节码文件,而不是直接能够在计算机上运行的二进制文件。Java代码真正执行的时候,是通过虚拟机执行的,不同平台适配不同的虚拟机。所以Java虚拟机在这里就是一个翻译官的角色,有了它Java字节码就能在不同的平台上运行。==
面试官内心os:小子,入套了吧。你不说虚拟机我还不好意思问你呢,这可是你自己找上门的。
==嗯,理解的不错。看你提到了Java虚拟机,能给我讲讲虚拟机的内存区域一般都包括哪些吗?==
MatrixYG:嘿嘿,这题我会!赶紧说道:
==好的。其实虚拟机的内存区域按照不同的视角,可以有不同的划分。如果按照线程的视角,可以分为两种(不经意间提到线程,就等你问我)。线程私有和线程共享区。
(1)程序计数器PC。这个代表了当前字节码执行到了哪个位置
(2)虚拟机栈
(3)本地方法栈
这三个是线程私有,还有两个是线程共享
(4)方法区
(5)虚拟机堆
这两个区域是所有线程都共享的。
但是如果是GC(又不经意间透露出我懂这个)的角度,可以划分为新生代,老年代,永久代。不同的区域GC策略可以采取不一样的GC策略,来达到最优的GC性能。==
面试官:这小子看来八股背的不少啊,有bear而来!
那我就继续了。继续问到:
“嗯,不错。你刚才提到了虚拟机栈和本地方法栈,这是俩是干啥的。能讲讲区别吗?”
MatrixYG:这面试官咋不按套路出牌,难道接下来不是问我线程或者GC吗???
但是,还是微笑回答道:
==好的,我们知道调用函数的时候是需要保存函数相关的信息的,比如参数等。这个需要一个栈的结构来保存调用现场。虚拟机栈就是给虚拟机内部的函数调用使用的栈结构,本地方法栈是调用本地方法使用的栈结构。== ”
面试官:小伙子,这个答案可不是我想要的哦。就追问道:
==什么叫做本地方法?==
MatrixYG:内心一咯噔,突然忘了。
但是思考了1ms说到:
==本地方法指的是操作系统提供的一些方法。因为虚拟机其实需要和操作系统交互,有些能力需要借助操作系统内部提供的api完成。调用这些方法的时候也需要栈结构保存信息。比如Object类的hashCode就依赖本地方法,一般依赖本地方法的都适用native关键字修饰。==
面试官: 嗯,放你一马。差点被我逮住把柄。说到:
==好的,理解的不错。刚才听你说到了垃圾回收,能讲讲Java的垃圾回收机制嘛==
MatrixYG:虚惊一场,幸亏平时经常看jdk的源码。脑子差点没转过来。。什么?问我GC?这次终于被我逮住了吧:
然后微笑的说道:
==Java的垃圾回收主要分为两个重要的阶段,第一个阶段判断虚拟机内部的哪些类是垃圾,已经可以被回收。第二步是执行回收的操作。==
Matrix心想,暂时说这么多。后面再展开。
面试官:嗯,看来这小子等着我问他啊。换个方向,看看他是不是在背八股文。
说到:
==”是的,看你条理比较清晰。估计是对GC有一定了解,能给我讲讲GC现在一些进展吗“==
嘿嘿,考察一下你的技术视野,如果只懂得什么复制,清除,之类的,那可别怪我开门左转了。
MatrixYG:嗯?怎么不问我回收算法。垃圾回收器?不问我新生代,老年代这些??看来这个面试官确实有点东西啊。不按套路出牌。幸亏我前几天关注了ZZTPeople,上面讲了一下。
==好的,其实我对GC还是非常感兴趣的。最近发布的JDK15把ZGC成为正式支持的GC回收器,其实在此之前还有CMS和G1,这两种回收器性能也是很好的。以G1为例子,G1垃圾回收器性能瓶颈主要在于
(1)初始标记阶段:初始标记阶段是指从GC Roots出发标记全部直接子节点的过程。
(2)再标记阶段:重新标记那些在并发标记阶段发生变化的对象。
(3)清理阶段清点出有存活对象的分区和没有存活对象的分区,该阶段不会清理垃圾对象,也不会执行存活对象的复制。
(4)转移阶段。转移阶段需要分配新内存和复制对象的成员变量。转移阶段是STW的,其中内存分配通常耗时非常短,但对象成员变量的复制耗时有可能较长,这是因为复制耗时与存活对象数量与对象复杂度成正比。对象越复杂,复制耗时越长。所以G1的性能瓶颈在转移阶段。ZGC解决了这个问题,在转移阶段也是并发的,并且ZGC之所以是常数级别的耗时,不随着JVM堆增大是因为他只和当前GCROOT数量有关,这个是常数级别的==
面试官:哦呦,这小伙子看来对GC还是有点理解的啊。可以比上次那个polin啥只会八股要强一点了。
说到:
==嗯,可以。ZGC里面为了实现这些东西都有哪些方案,能介绍一下吗==
MatrixYG: 嘿嘿,这次我还会!就不慌不忙的解释道:
== G1之所以在转移阶段不适用并发,是因为对于对象不好判断是否被回收了。假设GC线程转移了一个对象,但是没有来及更新对象地址应用程序还能继续访问到。这就出现了不一致。ZGC通过读屏障和着色指针来保证并发转移的安全==
面试官:
说到:
==“很不错,那你介绍一下这两种技术吧”==
MatrixYG: 这有点顶啊,都快到知识盲区了。
微笑说道:
==读屏障其实是一种插桩技术。JVM会向代码中插入一小段代码,当应用程序使用对象时,就会执行这段代码。如果对象地址变了,那么读屏障就会把地址更新。着色指针实际上是一种使用指针记录信息的操作==
对于64位的操作系统,指针式一个字(word)64位。这64位可以被划分为三部分
000000000000000000 0000 000000000000000000000000000000000000000000
(1)第一部分:18位。暂时不用
(2)第二部分:4位。分别表示对象信息。
(4)最后42位表示对象地址。
类似这种。当对象被修改之后,就会更新中间四位信息。每次gc直接扫描这个着色指针即可。
面试官:哦呦,看来这小伙子确实不错。那GC就到这里了。他还提到了插桩技术,莫非对动态字节码有所研究?这个我自己也不太会,就不问了。
说到:
==嗯,确实不错。我们聊聊下个话题。==
第二回合
面试官:
==看来你对虚拟机有一定的了解,刚才听你说了线程。能介绍一些Java有哪些创建线程的方式嘛。==
MatrixYG:
==在Java程序里一般有三种方式创建线程,分别是:==
(1)继承Thread类
(2)实现Runnable接口
(3)实现Callable接口
其中1和2没有太大的区别,主要是一个是继承类,一个是实现接口。2和3的主要区别是Callable有返回值,但是runnable的run方法没有返回值
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return null;
}
}
class Task2 implements Runnable{
@Override
public void run() {
}
}
面试官:
==好的,你提到了callable。我们使用callable经常回个futrue配合起来使用,能讲讲这个吗==
MatrixYG:好的。
==future是一种回调机制,比如我们使用callable创建一个任务的时候,不知道这个任务的状态。future提供了这种机制,可以让我们知道任务是否完成,或者取消任务。==
例如:
public static void main(String[] args) throws Exception {
FutureTask<Integer> futureTask = new FutureTask<>(new Task());
futureTask.get();
}
get方法会得到结果。但是这是一个阻塞方法,所以一般我们会制定timeout时间。
面试官:
==嗯,小伙子还可以。那看来你对线程同步也很有了解吧。我就不浪费时间了,我们来写个题吧==
MatrixYG:大喜。看来面试官人很好啊。终于到了写题环节了。
第三回合
面试官:
==数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。==
例子:
输入:n = 1
输出:["()"]
MatrixYG假装思考了30s之后说到,这题我会了。这题可以采用深度优先搜索,考虑整个解空间是一个二叉树,往左走达标左括号,往右走代表右括号。那么搜索n层,每一条合法的路径就是答案。比如现在n=2,那么就表示左右括号分别2个。搜索的标准就是看当前还有几个括号,然后尝试加个左括号,尝试加个右括号。对于任意一个中间序列来说,左括号的数量一定是要比右括号少的。如果不符合就是非法序列。最后搜到底,就得到了全部答案。
面试官:哦呦,这小伙子可以啊。四位也挺敏捷的。说的正解。就问道:
==那你这个算法的时空复杂度是多少呢?==
MatrixYG:算法的时间复杂度和空间复杂度都是O(2^n)的。因为搜索每次都有两种选择,所以解空间的大小和数量都是固定的,但是可以加上一些不合法的剪枝加速,不过这并不影响时间复杂度。
面试官:可以小伙子,你过了。
==好的,那你实现一下==
MatrixYG:
class Solution {
public:
vector<string>ans;
void dfs(string curAns,int l,int r){
if(l==0&&r==0){
ans.push_back(curAns);
return ;
}
if(l>r){
return;
}
if(l>0){
dfs(curAns+"(",l-1,r);
}
if(r>0){
dfs(curAns+")",l,r-1);
}
}
vector<string> generateParenthesis(int n) {
if(n==0)return ans;
dfs("",n,n);
return ans;
}
};
1分钟完事。
面试官看了直呼内行,说到
==好的,我这边你没问题了。等下一个面试吧!加油==