Java面试的一些经验

自己当初找工作时参加过众多一线互联网公司的Java研发面试,这段时间处于寒冬,然而前几天跳槽找工作,两天面了3家,已经拿了两个offer,觉得可以和大家分享下:
下面为拼多多、饿了么、蚂蚁金服、哈啰出行等公司给我留下较深印象的一些java面试题

  1. 1 “. “private修饰的方法可以通过反射访问,那么private的意义是什么
  2. 2 “. “Java类初始化顺序
  3. 3 “. “对方法区和永久区的理解以及它们之间的关系
  4. 4 “. “一个java文件有3个类,编译后有几个class文件
  5. 5 “. “局部变量使用前需要显式地赋值,否则编译通过不了,为什么这么设计
  6. 6 “. “ReadWriteLock读写之间互斥吗
  7. 7 “. “Semaphore拿到执行权的线程之间是否互斥
  8. 8 “. “写一个你认为最好的单例模式
  9. 9 “. “B树和B+树是解决什么样的问题的,怎样演化过来,之间区别
  10. 10 “. “写一个生产者消费者模式
  11. 11 “. “写一个死锁
  12. 12 “. “cpu 100%怎样定位
  13. 13 “. “String a = “ab”; String b = “a” + “b”; a == b 是否相等,为什么
  14. 14 “. “int a = 1; 是原子性操作吗
  15. 15 “. “可以用for循环直接删除ArrayList的特定元素吗?可能会出现什么问题?怎样解决
  16. 16 “. “新的任务提交到线程池,线程池是怎样处理
  17. 17 “. “AQS和CAS原理
  18. 18 “. “synchronized底层实现原理
  19. 19 “. “volatile作用,指令重排相关
  20. 20 “. “AOP和IOC原理
  21. 21 “. “Spring怎样解决循环依赖的问题
  22. 22 “. “dispatchServlet怎样分发任务的
  23. 23 “. “mysql给离散度低的字段建立索引会出现什么问题,具体说下原因

其它经常问的HashMap底层实现原理,常规的多线程问题考的太多了,没什么新意就不写了
平时不能光抱着应用Java的目的去学习,要深入了解每个知识点背后底层实现原理,为什么这么设计,比如问烂的HashMap 既然有hash进行排位还需要equals()作用是什么?就这个问题照样能问倒一些人,所以一定要抠细节,真的把每个知识点搞懂
一时记起来的就是这23个吧,其它想起来后续补充,答案我这几天写个大纲吧


—2019.1.23更新(加上了:写在前面的话;问题解答大纲;一些新面试题;)
转载请注明出处,今天发现居然有人复制我的题目到其他网站,这本倒是没什么,关键是复制的题目当时我还没作答,看到里面存在明显错误的解答,这可能会给初学者带来困扰。
感谢各位知友的关注,尤其感谢有人在评论区还作出有心的回答!
先回答15个,剩下的回来解答,包括已回答的后续会进行优化。
写在前面的话:
1.面试主要分为两块:一块是考查工程师对基础知识(包括了技术广度、深度、对技术的热情度等)的掌握程度,因为基础知识决定了一个技术人员发展的上限;另一块是考察工程师的工程能力,比如:做过哪些项目?遇到最难的问题怎样解决的?说说最有成就感的一项任务?工程能力是考察工程师当下能为公司带来的利益。其它考核方面:抗压性、合作能力…暂且不说。
2.Java只是一门语言,即使是Java工程师也不能局限于Java,要从面向对象语言本身,甚至从整个计算机体系,从工程实际出发看Java。
3.很多知识在一般公司的开发中是用不到的,常有人戏称:“面试造火箭,工作拧螺丝”,但这只是通常情况下公司对程序员的标准——迅速产出,完成任务。个人观点:工程师为了自己职业的发展不能局限于公司对自己的要求,不能停留在应用层面,要能够很好地掌握基础知识,要多看源码,自己多实践,学成记得产出,比如多为开源社区贡献代码,帮助初学者指路等。
有没有发现一个有意思的事情:“面试造火箭,工作拧螺丝”的背后其实是考察者内心深处普遍都认可基础知识的重要性(这一点仅为个人观点,不展开讲哈)。
以下为解答大纲,部分作了扩展
1. 这题是一道思想题目,天天会碰到private,有没有想过这个问题?谈谈对java设计的认识程度,主要抓住两点:1.java的private修饰符并不是为了安全性设计的;2.从外部对对象进行常规调用时,能够看到清晰的类结构。
2. 先说结论: 基类静态代码块,基类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)——>派生类静态代码块,派生类静态成员字段(并列优先级,按照代码中出现的先后顺序执行,且只有第一次加载时执行)——>基类普通代码块,基类普通成员字段(并列优点级,按代码中出现先后顺序执行)——>基类构造函数——>派生类普通代码块,派生类普通成员字段(并列优点级,按代码中出现先后顺序执行)——>派生类构造函数
代码验证:

  1. class Log {
  2. public static String initLog(String log) { System.out.println(log);return null; }
  3. }
  4. /**
  5. * 基类
  6. */
  7. class Base {
  8. static { System.out.println("Base Static Block 1"); }
  9. private static String staticValue = Log.initLog("Base Static Fiels");
  10. static { System.out.println("Base Static Block 2"); }
  11. { System.out.println("Base Normal Block 1"); }
  12. private String value = Log.initLog("Base Normal Field");
  13. { System.out.println("Base Normal Block 2"); }
  14. Base() { System.out.println("Base Constructor"); }
  15. }
  16. /**
  17. * 派生类
  18. */
  19. public class Derived extends Base {
  20. static { System.out.println("Static Block 1"); }
  21. private static String staticValue = Log.initLog("Static Fiels");
  22. static { System.out.println("Static Block 2"); }
  23. { System.out.println("Normal Block 1"); }
  24. private String value = Log.initLog("Normal Field");
  25. { System.out.println("Normal Block 2"); }
  26. Derived() { System.out.println("Derived Constructor"); }
  27. /**
  28. * 主线程
  29. */
  30. public static void main(String[] args) {
  31. Derived derived = new Derived();
  32. }

控制台结果输出:

  1. Base Static Block 1
  2. Base Static Fiels
  3. Base Static Block 2
  4. Static Block 1
  5. Static Fiels
  6. Static Block 2
  7. Base Normal Block 1
  8. Base Normal Field
  9. Base Normal Block 2
  10. Base Constructor
  11. Normal Block 1
  12. Normal Field
  13. Normal Block 2
  14. Derived Constructor

第2题之前的回答欠妥,现已改正,在此非常感谢提出质疑的知友!
3. 方法区是jvm规范里要求的,永久区是Hotspot虚拟机对方法区的具体实现,前者是规范,后者是实现方式。jdk1.8作了改变。本题看看对方在思想层面对jvm的理解程度,很基础的一个题目。
4. 文件中有几个类编译后就有几个class文件。
5. 成员变量是可以不经初始化的,在类加载过程的准备阶段即可给它赋予默认值,但局部变量使用前需要显式赋予初始值,javac不是推断不出不可以这样做,而是没有这样做,对于成员变量而言,其赋值和取值访问的先后顺序具有不确定性,对于成员变量可以在一个方法调用前赋值,也可以在方法调用后进行,这是运行时发生的,编译器确定不了,交给jvm去做比较合适。而对于局部变量而言,其赋值和取值访问顺序是确定的。这样设计是一种约束,尽最大程度减少使用者犯错的可能(假使局部变量可以使用默认值,可能总会无意间忘记赋值,进而导致不可预期的情况出现)。
6. ReadWriteRock 读写锁,使用场景可分为读/读、读/写、写/写,除了读和读之间是共享的,其它都是互斥的,接着会讨论下怎样实现互斥锁和同步锁的, 想了解对方对AQS,CAS的掌握程度,技术学习的深度。
7. Semaphore拿到执行权的线程之间是否互斥,Semaphore、CountDownLatch、CyclicBarrier、Exchanger 为java并发编程的4个辅助类,面试中常问的 CountDownLatch CyclicBarrier之间的区别,面试者肯定是经常碰到的, 所以问起来意义不大,Semaphore问的相对少一些,有些知识点如果没有使用过还是会忽略,Semaphore可有多把锁,可允许多个线程同时拥有执行权,这些有执行权的线程如并发访问同一对象,会产生线程安全问题。
8. 写一个你认为最好的单例模式, 这题面试者都可能遇到过,也算是工作中最常遇到的设计模式之一,想考察面试者对经常碰到的题目的理解深度,单例一共有几种实现方式:饿汉、懒汉、静态内部类、枚举、双检锁,要是写了简单的懒汉式可能就会问:要是多线程情况下怎样保证线程安全呢,面试者可能说双检锁,那么聊聊为什么要两次校验,接着会问光是双检锁还会有什么问题,这时候基础好的面试者就会说了:对象在定义的时候加上volatile关键字,接下来会继续引申讨论下原子性和可见性、java内存模型、类的加载过程。
其实没有最好,枚举方式、静态内部类、双检锁都是可以的,就想听下对不同的单例写法认识程度,写个双检锁的方式吧:

  1. public class Singleton {
  2. private Singleton() {
  3. }
  4. private volatile static Singleton instance;
  5. public static Singleton getInstance() {
  6. if (null == instance) {
  7. synchronized (Singleton.class) {
  8. if (null == instance) {
  9. instance = new Singleton();
  10. }
  11. }
  12. }
  13. return instance;
  14. }
  15. }
  1. B树和B+树,这题既问mysql索引的实现原理,也问数据结构基础,首先从二叉树说起,因为会产生退化现象,提出了平衡二叉树,再提出怎样让每一层放的节点多一些来减少遍历高度,引申出m叉树,m叉搜索树同样会有退化现象,引出m叉平衡树,也就是B树,这时候每个节点既放了key也放了value,怎样使每个节点放尽可能多的key值,以减少遍历高度呢(访问磁盘次数),可以将每个节点只放key值,将value值放在叶子结点,在叶子结点的value值增加指向相邻节点指针,这就是优化后的B+树。然后谈谈数据库索引失效的情况,为什么给离散度低的字段(如性别)建立索引是不可取的,查询数据反而更慢,如果将离散度高的字段和性别建立联合索引会怎样,有什么需要注意的?
    10. 生产者消费者模式,synchronized锁住一个LinkedList,一个生产者,只要队列不满,生产后往里放,一个消费者只要队列不空,向外取,两者通过wait()和notify()进行协调,写好了会问怎样提高效率,最后会聊一聊消息队列设计精要思想及其使用。
    11. 写一个死锁,觉得这个问题真的很不错,经常说的死锁四个条件,背都能背上,那写一个看看,思想为:定义两个ArrayList,将他们都加上锁A,B,线程1,2,1拿住了锁A ,请求锁B,2拿住了锁B请求锁A,在等待对方释放锁的过程中谁也不让出已获得的锁。
    1. public class DeadLock {
    2. public static void main(String[] args) {
    3. final List<Integer> list1 = Arrays.asList(1, 2, 3);
    4. final List<Integer> list2 = Arrays.asList(4, 5, 6);
    5. new Thread(new Runnable() {
    6. @Override
    7. public void run() {
    8. synchronized (list1) {
    9. for (Integer i : list1) {
    10. System.out.println(i);
    11. }
    12. try {
    13. Thread.sleep(1000);
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. }
    17. synchronized (list2) {
    18. for (Integer i : list2) {
    19. System.out.println(i);
    20. }
    21. }
    22. }
    23. }
    24. }).start();
    25. new Thread(new Runnable() {
    26. @Override
    27. public void run() {
    28. synchronized (list2) {
    29. for (Integer i : list2) {
    30. System.out.println(i);
    31. }
    32. try {
    33. Thread.sleep(1000);
    34. } catch (InterruptedException e) {
    35. e.printStackTrace();
    36. }
    37. synchronized (list1) {
    38. for (Integer i : list1) {
    39. System.out.println(i);
    40. }
    41. }
    42. }
    43. }
    44. }).start();
    45. }
    46. }
  2. cpu 100%怎样定位,这题是一个应用性题目,网上搜一下即可,比较常见,说实话,把这题放进来有点后悔。
    13. String a = “ab”; String b = “a” + “b”; a ,b 是相等的(各位要写代码验证一下,我看到有人写了错误答案)。常规的问法是new一个对象赋给变量,问:这行表达式创建了几个对象,但这样的题目太常见。
    14. int a = 1; 是原子性操作。
    15. for循环直接删除ArrayList中的特定元素是错的,不同的for循环会发生不同的错误,泛型for会抛出 ConcurrentModificationException,普通的for想要删除集合中重复且连续的元素,只能删除第一个。
    错误原因:打开JDK的ArrayList源码,看下ArrayList中的remove方法(注意ArrayList中的remove有两个同名方法,只是入参不同,这里看的是入参为Object的remove方法)是怎么实现的,一般情况下程序的执行路径会走到else路径下最终调用faseRemove方法,会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。针对普通for循环的错误写法,在遍历第一个字符串b时因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也就是第二个字符串b)至当前位置,导致下一次循环遍历时后一个字符串b并没有遍历到,所以无法删除。针对这种情况可以倒序删除的方式来避免
    解决方案:用 Iterator。

    1. List<String> list = new ArrayList(Arrays.asList("a", "b", "b" , "c", "d"));
    2. Iterator<String> iterator = list.iterator();
    3. while(iterator.hasNext()) {
    4. String element = iterator.next();
    5. if(element.equals("b")) {
    6. iterator.remove();
    7. }

    将本问题扩展一下,下面的代码可能会出现什么问题?

    1. ArrayList<String> array = new ArrayList<String>();
    2. array.add(1,"hello world");

    后加

  3. 1 “. “jvm gc 复制算法是怎样实现的

  4. 2 “. “注解的原理
  5. 3 “. “进程间通信的方式有哪些
  6. 4 “. “ReentrantLock 是可重入锁,什么是可重入锁
  7. 5 “. “线程执行过程中遇到异常会发生什么,怎样处理
  8. 6 “. “HashMap put()元素产生冲突,为什么用LinkedList(拉链法)而不用ArrayList解决,产生冲突时key值不等,新元素怎样加入链表,为什么这么设计(jdk1.8之前)
  9. 7 “. “双检锁写一个单例模式,为什么要用volatile修饰对象,Object object = new Object(); object为null吗?为什么
  10. 8 “. “Object object = new Object(); 初始化的顺序是什么在jvm各区域做了什么