自从2015年11月份成功进入第一家公司以后就再也没有面试过。上个月尝试性的在某招聘网站上投了几波简历,终于在1月2号晚上收到了一个电话面试,那真的是一个激动啊。当时我正在公司整理垃圾回收机制的笔记的时候,一个电话打来说是XX公司,看到了我的简历想做一次电话面试。所以我就放下手里的《深入理解Java虚拟机》,抄起一本草稿本就冲进了一间会议室。
面试官:你好,首先先做一下自我介绍吧。
我:好!@#¥%……&&%#¥%……
PS:因为第一次面试实在太紧张,所以自己瞎说了些神马东西自己都忘了。反正就是语无伦次精神错乱hhh
面试官:你简历上写着springboot的项目经历,那你跟我讲一下springboot是怎么用的吗?
我:??? what?其实那个项目我就写写里面的业务逻辑代码。。。
PS:我忘了面试官是不是真的这么问的,因为我不知道他想问什么 = =!
面试官:嗯,看了一下项目经历好像也没什么好问的,那来问问基础吧。工作中集合框架总用过吧?来说一说ArrayList和LinkedList吧。
我:ArrayList底层是数组,查询快增删慢,线程不安全。扩容时运用位运算,每次1.5倍扩容,originalSize + originalSize>>1,扩容时使用的是System.arraycopy 效率比较低。LinkedList底层是链表,查询慢,增删快,线程不安全。
面试官:嗯,那接着来讲讲Hashmap吧。
我:hashmap 底层是数组加链表,以键值对形式储存,键必须唯一 键值都可以为空,初始容量是16, 负载因子是0.75,容量都是2的幂次方,因为有利于储存的元素均匀分布。拿length是16举例子 hash(key) & (length - 1) 当length > 16时 hash(key) & 15 == length % 16 当length < 16时 hash(key) & 15 == hash(key) 结果为hash的后几位的值,只要hash本身均匀分布,得到的结果就是均匀的。
执行put方法时先根据hash(key)计算在数组中的位置 使用头插法插入元素,get方法类似。线程不安全,高并发情况下,可能出现环状链表而导致死锁。
PS:其实我只答出前半部分,这里的答案是我整理过的。 嘿嘿嘿
面试官:你刚才提到了线程不安全,那你知道有哪些线程安全的hashmap么?
我:可以用Hashtable,Collections.synchronziedMap。不过效率比较低。可以用concurrentHashmap。
面试官:那你讲一讲concurrentHashmap。
我:。。。(半分钟沉默后我选择了放弃,之前有看过这部分东西,但是一紧张真的全忘了。)
concurrentHashmap底层是Segment数组,每个节点都是一个Hashmap,所以它是一个二级hash表。concurrentHashmap需要通过两次hash才能定位一个元素。采用锁分段技术,只有在同一个segment中进行操作的时候才会发生锁竞争。每个segment都持有一把锁。多线程下,线程A执行tryLock()获得锁,把HashEntry插到相应的位置。线程B获得锁失败,执行scanAndLockForPut,重复执行tryLock(),若尝试次数超过上限执行lock()将线程B挂起。当线程A执行完通过unlock()释放锁,线程B继续执行。
当然上面这部分都是1.7的版本。1.8用的红黑树,表示农村人营养跟不上啊。
面试官:Java内存模型知道吗?
我:就是那个JMM吗?Java Memory Model还是运行时数据区。(我发现很多人都把这两个东西搞混了)
主内存被所有线程所共享。工作内存相当于是高速缓存。对共享变量来说,工作内存有着共享变量的副本。线程对共享变量的操作都在工作内存中完成,不能直接读写主内存中的共享变量。
因为工作内存修改的变量不会立即刷入主内存,从而线程B可能拿到的值可能与主内存中的值不同。
而运行时数据区分为这么几个部分。堆、方法区、虚拟机栈、本地方法栈和程序计数器。堆和方法区被线程共享。
面试官:那你说说这里面每一块区域都是干什么用的。
我:堆用于存放对象实例,几乎所有的对象都在这块区域分配。方法区用于储存被虚拟机加载的类信息、常量、静态变量。虚拟机栈用于存放方法调用是创建的栈帧,每个方法的执行对应着栈帧入栈和出栈的行为。本地方法栈为虚拟机使用到的Native方法服务。程序计数器用于代码运行时指令的跳转。
面试官:那来聊聊垃圾回收吧。
我:JVM提供4种垃圾回收算法。1.标记清除。2.复制算法。3.标记整理。4.分代收集。@#¥%……&*
详情见垃圾回收笔记。
面试官:那如何确定一个对象可以被回收?
我:目前有2种方法。1.应用计数器。2.可达性分析。@#¥%……&*
详情见垃圾回收笔记。
面试官:你刚才讲到JMM,如何才能保证线程安全。
我:可以添加同步锁吧,或者用volatile关键字修饰。
面试官:那你说说volatile关键字。
我:volatile修饰保证了可见性和有序性,不保证原子性。
可见性:当一个线程修改了共享变量的值,新值会立刻刷到主内存去,其他线程读取这个变量时也要去主内存去拉最新的值。这个过程得益于happens-before原则,前一个指令必须对后一个指令可见。
有序性:虚拟机在不改变程序执行结果的前提下优化程序的效率而进行指令重排。其原理是使用内存屏障来解决指令重排问题。如果一个变量是volatile修饰时,JMM会在写入这个字段之后插入一个Write-Barrier指令,在读取这个字段之前插入一个Read-Barrier指令。
PS:其实我也就只聊了个大概,后面的内容也是整理过补上去的。
面试官:平常接触过多线程编程吗?来聊聊线程池吧。
我:平常工作中是没怎么接触过,不过看过线程池相关的源码。然后大概介绍了一下线程池工作原理。
详情见线程池原理解析笔记。哈哈哈哈,正好在面试之前整理了一下笔记。
面试官:你刚才说你平常会看些书也讲到设计模式。那你跟我说说模板模式。
我:。。。呵呵呵(尴尬笑) 这个我不会。
面试官:那换个简单的,单例模式总会吧?
我:单例模式确保一个类只能有一个实例。有恶汉模式和懒汉模式两种实现方式。
PS:当时叫我手写一个。我想我们这是电话面试,怎么写给你看。 = =!
面试官:那最后问一下。平常开发的时候用过泛型吧?
我:泛型???用肯定用啊,但是都是直接用IDEA写的代码,没去深入理解过。
PS:从此挖下一个坑要去写一篇关于泛型的总结。
面试官:好吧,那今天问的差不多了。@#¥%……&*
我:@#¥%……&*
PS:后来都是聊一些其他的东西就不往上贴了。
总结:这是第一次面试,难免紧张。其实问的问题大多数我都看到过,平常会去看看公众号大神的各种文章,耳濡目染所以大多数也都知道,不过没有理解的很深所以表达的有点乱可能有些地方还讲错了。还好这个面试官没有问的非常非常的细,毕竟这时候我只是个毕业一年半的小菜鸟。但是有一点,平常看过的东西应该好好的做总结,不管是去抄过来还是其他什么方法,自己写过怎么样记忆力都会深刻一点。所以后来想着应该好好去写文章来对自己学过的东西整理笔记做总结,这也是写这篇文章的目的。虽然这次电面答的没想象中那么好,不过自己也是挺满意的,至少很多学过的东西还是记起来一些。这篇文章中如果有哪里总结错的地方,谢谢批评指正。