第二次滴滴出行(小桔科技)亲身面试经验分享,java开发岗
首先,截图为证:
简述:
本次滴滴面试,是之前那次滴滴面试挂了之后,另一个HR又捞起来的(还成了高级java岗位);
通过本次面试,本人深刻认识到了自己的不足(也许是高级java岗位所以面试难的原因?2年java的本人,自我感觉也就初中级java的水平);
通过本次面试,本人深刻认识到了自己与大佬的差距(看面试官的年龄感觉比本人大不了几岁);
通过本次面试,本人发现了一条规律:什么事情都没有看起来那么简单,还得继续努力。(只在网上背面试题是不够的,面试官一问的深了就凉凉了,还得自己看源码,自己多思考)
滴滴面试内容(主要看问题,答案暂时不标准):
1.自我介绍
大概介绍了下个人信息,工作项目。但是没有深入询问工作项目,一面的面试官主要不聊这个。
2.jvm讲一下
讲了栈、堆、元空间(jdk1.8前是方法区,1.8后是元空间);
新生代、老年代;
后期百度的答案: https://blog.csdn.net/yunzhaji3762/article/details/81038711
JVM是java虚拟机,用来实现java程序的跨平台运行;
java文件要编译成class文件才能运行,jar包与war包中实际包含的也是class文件;
编译命令,-d用来指定生成的class文件的位置
javac -d C:\test C:\test\Hello.java
执行命令,不用输入.class
java Hello
.java源代码->.class字节码->类加载器->字节码校验器->解释器与JIT代码生成器->硬件
JVM体系结构分为:
类加载器(ClassLoader):用来装载class文件
执行引擎:执行字节码或执行本地方法
运行时数据区:方法区、堆、栈、PC寄存器、本地方法栈
JVM运行时数据区:
可以分为堆、栈、元空间(其中有常量池)、寄存器、本地方法栈。
PC寄存器:用于存储每个线程下一步执行的JVM指令,如果该方法是本地方法(native),则PC寄存器中不存储任何信息。
栈(stack):是线程私有的,每个线程创建一个栈,存放当前线程中的基本类型的局部变量(boolean,char,byte,short,int,long,float,double)、部分返回结果以及栈帧(stack frame),非基本类型的对象在JVM栈上仅存放一个指向堆的地址。
堆(heap):存储对象实例以及数组值,可以认为所有通过new创建的对象的内存都在此分配,堆中的对象的内存需要等待GC回收。
●堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要加锁,这也导致了new对象的开销是比较大的。
new对象过多会导致堆溢出:java.lang.OutOfMemoryError: Java heap space
方法递归会导致栈溢出:StackOverflow
方法区/元空间:也称为永久代;jdk1.8后方法区改名为元空间;存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义的final类型的常量、类中的Field信息、类中的方法信息。
●jdk1.8后,元空间使用物理内存,不再使用JVM内存。
//常量池:存放类中固定的常量信息、方法和Field的引用信息等,其空间从方法区分配。
本地方法栈:支持本地方法运行,此区域存储每个native方法调用的状态。
3.什么对象会被移动到老年代?
多次GC后依然存活的对象。
4.什么类型的对象会参与GC?
new创建的、还有不知道什么。
百度的答案:
https://blog.csdn.net/qq_39839075/article/details/84867025
//由于无法避免对象相互引用,所以JVM不用这个:引用计数算法:给对象添加引用计数器,每次被引用就+1,引用失效就-1,当为0时就回收。
对象可达性算法:选择一个GC ROOTS,当做根节点,向下寻找,能到达的对象就不回收,不能到达的对象就回收。
以下4种对象可以作为根节点:栈中引用的对象、元空间中静态属性所引用的对象、元空间中常量所引用的对象、本地方法栈中引用的对象;
注意静态变量(static)、final常量不会被GC;而final变量是会被GC的,还有普通new的对象也会被GC。
5.GC机制?
百度的答案:
首先,堆中发生GC;
堆分为新生代、老年代、永久代(元空间),GC发生在新生代与老年代与永久代;
永生代也是可以回收的,条件是 1.该类的实例都被回收。 2.加载该类的classLoader已经被回收 3.该类不能通过反射访问到其方法,而且该类的java.lang.class没有被引用 当满足这3个条件时,是可以回收,但回不回收还得看jvm。
永久代中也有废弃常量(一段时间后没有被使用的常量),如果有必要也会被回收。
新生代又分为3部分,一个eden,两个survivor(from survivor、to survivor)。
https://blog.csdn.net/qq_36314960/article/details/79923581
大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC;同理,当老年代中没有足够的内存空间来存放对象时,虚拟机会发起一次Major GC/Full GC。只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。
虚拟机通过一个对象年龄计数器来判定哪些对象放在新生代,哪些对象应该放在老生代。如果对象在Eden出生并经过一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将该对象的年龄设为1。对象每在Survivor中熬过一次Minor GC,年龄就增加1岁,当他的年龄增加到最大值15时,就将会被晋升到老年代中。虚拟机并不是永远地要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,如果在Survivor空间中所有相同年龄的对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
当Java程序运行时,如果遇到大对象将会被直接存放到老生代中。如果老生代的空间也被占满,当来自新生代的对象再次请求进入老生代时就会报OutOfMemory异常。新生代中的垃圾回收频率高,且回收的速度也较快。
新生代约占堆大小的 1/3,老年代约占堆大小的 2/3;新生代较小,可以多触发minor GC,并且此时要操作的对象也较少,效率较高;
当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,例如新生代的eden满后触发minor GC,之后将eden存活下来的对象移动到survivor,将survivor中符合条件的对象移动到老年代;
老年代满后触发Full GC,由于Full GC很慢,因此要尽量避免,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
永久区满后触发Full GC,为避免永久区(Perm Gen)占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。
代码中调用System.gc(),可以建议JVM执行FULL GC,但是最终不一定执行。
minor GC:清理年轻代。
//major GC = Full GC
Full GC:清理整个堆。
6.垃圾收集器?
不知道。
百度发现这篇文章说有7种:https://www.cnblogs.com/super-jing/p/10795099.html
7种垃圾收集器:
Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1;
垃圾收集器所处的位置:
新生代收集器:Serial、ParNew、Parallel Scavenge;
老年代收集器:Serial Old、Parallel Old、CMS;
整堆收集器:G1;
如何设置垃圾收集器:
https://www.cnblogs.com/Joy-Hu/p/10577103.html
启动java应用时,使用-XX参数设置垃圾收集器
查看jvm默认使用的垃圾收集器的命令:
java -XX:+PrintCommandLineFlags -version
默认的垃圾收集器是-XX:+UseParallelGC,也就是说,Minor GC使用Parallel,Full GC使用Serial Old,具体见下方。
注意垃圾收集器不是最新的最好,需要考虑实际场景,选择合适的垃圾收集器。
GC组合 |
Minor GC |
Full GC |
描述 |
-XX:+UseSerialGC | Serial收集器串行回收 | Serial Old收集器串行回收 | 该选项可以手动指定Serial收集器+Serial Old收集器组合执行内存回收 |
-XX:+UseParNewGC | ParNew收集器并行回收 | Serial Old收集器串行回收 | 该选项可以手动指定ParNew收集器+Serilal Old组合执行内存回收 |
-XX:+UseParallelGC | Parallel收集器并行回收 | Serial Old收集器串行回收 | 该选项可以手动指定Parallel收集器+Serial Old收集器组合执行内存回收 |
-XX:+UseParallelOldGC | Parallel收集器并行回收 | Parallel Old收集器并行回收 | 该选项可以手动指定Parallel收集器+Parallel Old收集器组合执行内存回收 |
-XX:+UseConcMarkSweepGC | ParNew收集器并行回收 | 缺省使用CMS收集器并发回收,备用采用Serial Old收集器串行回收 |
该选项可以手动指定ParNew收集器+CMS收集器+Serial Old收集器组合执行内存回收。优先使用ParNew收集器+CMS收集器的组合,当出现ConcurrentMode Fail或者Promotion Failed时,则采用ParNew收集器+Serial Old收集器的组合 |
-XX:+UseConcMarkSweepGC -XX:-UseParNewGC |
Serial收集器串行回收 | ||
-XX:+UseG1GC | G1收集器并发、并行执行内存回收 | 暂无 |
7.用过线程池吗?讲下线程池
讲了线程池的4个参数。
8.如果线程池达到最大线程数,此时又来新线程,会怎样?
9.线程等待队列是怎么用的?什么时候会进入队列?什么时候出队列?
10.讲下hashmap
讲了hashmap结构,默认大小,jdk1.8后什么情况下转为红黑树,如何扩容
11举实际数字,问这时会不会扩容。
记住创建hashmap时实际大小是2的幂次、填充元素超过实际大小的0.75(如果不修改就是0.75)倍就会扩容即可。
12.详细讲下hashmap的put操作
计算key的hash值,如果没有冲突,则放入元素;如果有冲突,说明目标位置有元素,则在已有元素后用链表的形式放入元素。
13.如果hash值大于hashmap的数组长度,怎么放入数组
当时没答出来,(早)被问懵了。据说是取模运算就行。
14.如何实现线程安全?
synchronized、reentracklock
15.synchronized用法?
给方法上加、给代码块上加
16.synchronized给代码块上加,锁住的是什么?(是用什么当锁的)
可以用任意对象,string、class等都可以。
17.synchronized给方法上加,锁住的是什么?
18.hashTable是怎么实现线程安全的?
put方法与get方法加synchronized
19.所以一个线程用put,一个线程用get,他们需要等待对方的锁吗?
正确答案是需要,要不怎么实现线程安全;所以synchronized给方法加锁,锁住的是类对象。(如果new HashTable(),就用这个hashTable对象当锁,线程需要这个锁才能执行synchronized标注的方法)
20.用20分钟写一个程序,一个二叉搜索树,求最小差。例如:
35
29 90
20 34 47 101
此时,最小差是1.(35-34=1);
二叉搜索树特点,左边的小于根,右边的大于根。
要求时间复杂度o(n),空间复杂度o(1)
当时考虑的思路:遍历树,用一个new int[1]数组来存储差值,每个差都与数组比较、存更小的即可。
然而对树的算法不熟悉,完成了50%吧。
后来才明白的思路:中序遍历。
PS:据说Integer也可以存储差值,在方法中改变它的值也有效果。(与int数组效果相同,不过当时互相不知道这个...)
总结:
面试官深入问了下jvm、线程池、hashmap、synchronized,再加一道二叉搜索树求最小差的编程题;
很多都不会,有些不会的或者答错的面试官会告诉你,虽然告诉了也不一定能听懂;
整体就hashmap答的凑付能看,其余一塌糊涂——只背网上的根本不够,稍微问深一点就不会;
面试时间累计1小时10分左右。
最后,问了下面试官正常技术面有几面,面试官回答有三面;
问了下面试官面试没有通过会有通知吗?面试官说HR应该会通知。
其实不等通知也知道,又凉了呗。
----------------------------------------------------------------------
大概就问了这些,答案后续有空了完善。
然后就很迷,本人就是简历上的水平,简历初筛过了;
3个大厂面了4次(看前几篇文章),都凉了……
这次明显感觉比前几次面的难,估计是高级的缘故;
如果有大佬感觉很简单,那就是本人太菜了,本人怕不是个假的2年java(滑稽);
如果说前几次是不明不白的凉,这次就是实实在在的凉了。
个人感觉,大厂一面基本都是这些概念,算法题,有浅有深。
想过一面,只看网上的文章不够,还得自己翻源码,自己思考理解。
任何事都没有看上去那么简单,成功没有捷径,还得不断努力。
努力不一定能成功,但是不努力一定不会成功。