经过前面两次的面试,这里就马上转到第三家公司了的面试了,这里简称 B,工作地点和上一篇所讲述的 W 公司在一个城市,是面完 W 之后第二天去的,说出名字的话大多小伙伴都听过或者用过其服务,具体是什么要靠你们的悟性了。
一面
B 公司的一面也是电面,主要是根据简历上的聊了一下,问了几个问题。挑两个比较有通用性的问题聊一下。
- 了解 Spring 框架多少?
博主说 SpringMVC+Spring 是比较常用的,看过一点 Spring 的源码,主要是关于 Xml 解析和 Bean 加载的,记得大致的步骤。面试官就让博主讲一下 Bean 的加载过程。其实这个是蛮长的一段,主要有一下一些步骤(博主个人理解,仅供参考):xml 解析之后存入一个 BeanDefinition 之中,然后主要是对其进行操作;先在 singletionObjects(是一个 ConcurrentHashMap 的对象)判断有没有 Bean 的实例,有就处理下返回,没有就继续;检测一下循坏依赖之类的;下面要进入主题了,如果是单例(Spring Bean 默认是单例)的话,就创建实例并存入 singletonObjects 中,如果不是则创建不保存(当然这里也有一个非常复杂的过程,这里就不论述了);实例创建完之后就开始属性注入(autowiredByType, autowiredByName); 初始化 Bean(激活 Aware 方法:BeanNameAware, BeanFactoryAware, ApplicationContextAware 等;BeanPostProcessor 接口;激活自定义 init 方法:init-method, InitializingBean 接口;);这里就可以使用 Bean 了;使用完之后就是销毁了(destory-method, DisposableBean 接口)。有关 Spring 的一些知识点可以参考《Spring 知识点提炼》。
之后又被问了个问题:大家都知道 BeanFactory 是什么,那么你了不了解 FactoryBean 呢?这个见过,但是真记不得干嘛用的,后来翻看了下资料:一般情况下,Spring 通过反射机制利用 bean 的 class 属性指定实现类来实例化 Bean。在某些情况下,实例化 bean 过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案,Spring 为此提供了一个 org.Springframework.bean.factory.FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 bean 的逻辑。
2.ArrayList 的问题。
有关 ArrayList 无非就是一下一些基本认知:非线程安全,初始容量为 10,按照 1.5 倍扩容,底层采用数组 elementData 实现,对于随机访问 get 和 set 效率比较高,对于 add 和 remove 效率比 LinkedList 要低。当要扩容时采用 System.arrayCopy() 这个 native 方法实现。
但是 ArrayList 有两个点需要注意的。
第一个是 ArrayList 有个 trimToSize() 方法用来缩小 elementData 数组的大小,这样可以节约内存。考虑这样一种情形,当某个应用需要,一个 ArrayList 扩容到比如 size=10000,之后经过一系列 remove 操作 size=15,在后面的很长一段时间内这个 ArrayList 的 size 一直保持在 < 100 以内,那么就造成了很大的空间浪费,这时候建议显式调用一下 trimToSize() 这个方法,以优化一下内存空间。或者在一个 ArrayList 中的容量已经固定,但是由于之前每次扩容都扩充 50%,所以有一定的空间浪费,可以调用 trimToSize() 消除这些空间上的浪费。
第二个是 ArrayList 中有个不合理之处,就是 ArrayList 是 implements RandomAccess 这个接口的。关于 RandomAccess 接口 JDK 中有明确定义:实现了 RandomAccess 接口的集合框架,采用迭代器遍历比较慢,不推荐。 原文是这么说的:
It is recognized that the distinction between random and sequential access is often fuzzy. For example, some List implementations provide asymptotically linear access times if they get huge, but constant access times in practice. Such a List implementation should generally implement this interface. As a rule of thumb, a List implementation should implement this interface if, for typical instances of the class, this loop:
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
runs faster than this loop:
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
但是 ArrayList 的 toString() 方法是继承了 AbstractCollection 的,这个方法用的是迭代器的遍历方式。详细可以参《RandomAccess?!》。
而且如果采用 Java 中的 foreach 语法糖进行遍历 ArrayList 的话,Javac 在解语法糖时是解释成迭代器的遍历方式而没有解释成普通 for() 循环 list.get(i) 的方式,详细可以参考《Java 语法糖之 foreach》。
- 流控怎么处理?
直接丢弃;本机内存暂存一些请求,比如用 Semaphore 实现;消息队列。最后面试官提示了一个令牌算法。
4.Linux 的常用命令?
这个在第一篇中也陈述过了:
cat tac head tail more less nl vim vi gvim
date cal man shutdown poweroff reboot echo
uname -r; mount; unmount; exit
cd ls pwd mkdir cp scp rm mv
touch file which whereis locate find tar unzip
grep df top free kill killall
ifconfig ping netstat telnet ftp
passwd umask chrown chmod chgrp sudo ps who
二面
二面本来是两个面试官,一个面试官(是一面的面试官)在纸上画了个数就接个电话出去了,剩下的是另一个面试官。整个过程持续 1 个半小时左右,过程是边面边在纸上写。这种方式有点 “虐”,不过还好问的比较全面,也不是特别的深。面试官人不错,面试过程也很和谐。题目还蛮多的,博主尽量回忆一下。
- 根据纸上画的树 (多叉树,并不是二叉树) 写出一个树的实现,用 Java。然后写出树的遍历方式,递归和非递归方式都要写。然后是写出树的前序,中序,后序遍历的结果。
class TreeNode{
String treeInfo;
TreeNode father;
List sons;
}
递归的遍历比较简单,非递归的方式是采用栈进行操作。至于前序,中序,后序遍历只要懂点数据结构和算法的人都能知道,在此不赘述了。
- JVM 调优。
主要是讲讲自己实际遇到的情况。比如调节一下参数,类似:-Xmx, -Xms 等;又比如采用 JConsole 或者 JVisualVm 进行操作;又比如用 jps, jmap, jstack 等命令工具。
接着再讲讲遇到 OOM 的时候是怎么处理的。
- 刚刚讲述了 OOM 也就是内存吃不住的时候怎么处理,如果某个线程 CPU 占用率 100% 怎么查看。
这个博主直说出了用资源管理工具查看,或者 linux 的 top 命令查看一个进程的 CPU 占用率,至于某个线程的占用率怎么查看还真不知道。
后面面试官说可以这么使用,先 jstack 查出线程堆的信息,在用 top H -p [pid] 的方式查看某个线程占用率(pid 通过 jps 命令获取),中间进过 16 进制转换下就对应起来了。回家试了下,还真是。还有一种方法也可以,在 linux 下:ps -H -eo pid,ppid,tid,%cpu –sort=%cpu | grep [pid] 也有同样的效果。
- 写一个单例
先写了一个内部类的,后来说了一个 enum 的,又说一个 synchronized 的,面试官接着问还有呢?我说:你指的是双检锁的那种方式吧,然后又写了一个双检锁的单例。后面被问了为什么双检锁中要进行两次 null 的判断?博主 DCL 的没有用过,这个问题也没有全答对。。详细可以参考《设计模式:单例模式(Singleton)》。
- 聊了聊多线程。CAS 等
多线程一般聊了聊 ThreadPoolExecutor 相关的,或者 ConcurrentHashMap, 或者 BlockingQueue 系列的。CAS 就写了一个 CAS 的实现方式,Java 中的 CAS 的底层实现只有三个:sun.misc.Unsafe.CompareAndSwapInt, CompareAndSwapLong, CompareAndSwapObject.
- 画出 volatile 的内存模型。
这个题目比较吓人,如果看过的人很容易就画出来了。volatile 就两点:内存可见性;原子性。
- 画出 TCP 三次握手,四次挥手。(略)
- 动态代理。
写一个动态代理,博主写了一个 JDK 的实现,写完补了一句这个还有 CGLib 的实现方式,然后就被要求写一个 CGLib 的实现方式,博主只记得用 Enhancer 干嘛干嘛的,剩下的不记得了。嘴贱打脸。后来又被问了 JDK 和 CGLib 的区别,开始没答出来,后来提示了一下,JDK 是通过接口实现的,CGLib 是通过继承实现的,如果所要代理的对象没有接口实现那只能采用 CGLib 进行动态代理了。详细可以参考《设计模式:代理模式(Proxy)》
后续
说实话,这家公司挺好的,也在稳步上升阶段。上班可以从 10 点开始,面试官人不错,感觉秒秒钟就能做好朋友的那种。只是博主这次出来转一转是有目的性的,需要找一家有点名气的互联网公司,有一个必须点是必须要有足够的流量接入。B 公司的互联网产品主要面对商户,所以流量上并没有那么的大。薪资其实给的也不错,如果 B 公司与博主同城,博主肯定就选择了。只是要换个城市,这份 offer 的诱惑力就没有那么大了,最终还是没有选择。最后在这里感谢下背后运作的猎头 Theresa, 虽然我也知道你不会看我博客,你真的比其他的猎人负责任多了,抱歉没能给你赚一单业绩~