写点什么

Java 高质量面试总结

发布于: 刚刚
Java高质量面试总结

面试

  • 一般都是由浅到深去问,思路是:

  • 先考察基础是否过关,因为基础知识决定了一个技术人员发展的上限

  • 再通过深度考察是否有技术热情和深度以及技术的广度

  • 同时可能会提出一些质疑和挑战来考察候选人能否与有不同意见的人沟通

考察内容和方式

基础知识

  • 技术上深度与广度兼顾

  • 基础知识: 考察基础的时候一般都不会深入地去问,主要目的是考察知识面

  • 计算机理论基础:

  • 计算机网络

  • 数据结构

  • 计算机组成原理

  • 计算机操作系统

  • JDK:

  • 源码

  • 集合

  • BIO 或者 NIO

  • annotation 等

  • JVM:

  • 内存模型

  • 类加载原理

  • 数据库:

  • 索引

  • 事务

  • 死锁等

  • 并发:

  • 并发的优缺点

  • 内存可见性 - volatile

  • 同步

  • 线程池框架

  • 网络:

  • TCP

  • HTTP

  • 常见设计模式

深入考察

  • 深入考察:

  • 深入考察不会像考察基础时面面俱到,而是会在某个点上深入去聊

  • 这个点可以是让候选人自己选一个点,也可能面试官根据简历内容去选

  • 主要目的是考察候选人对某个技术点的深入掌握程度

  • 技术是相通的,如果一个人能在某个技术点上达到很深入的程度,其他点上也不会有太大问题,如果某个人在声称很了解的技术点上都支支吾吾,一知半解,多半可以判断此人要么技术有限,要么遇到问题不愿深入考察

  • 考查工程师的工程能力,比如: 做过哪些项目? 遇到最难的问题是什么? 怎么解决的? 说说最有成就感的一项任务

  • 深入考察的技术点:

  • Java 框架:

  • Spring 源码的 AOP IOC

  • JDK:

  • ConcurrentHashMap 如何提高并发度

  • NIO 的原理,包括零拷贝,堆外内存以及优缺点

  • 虚拟机:

  • 包冲突,类冲突的形成原理以及解办法,可以引申到 JDK 9 的模块化设计

  • TCCL 的存在价值

  • 服务器:

  • Tomcat 源码

  • Netty 源码

  • 数据结构:

  • 数组

  • 链表

  • 排序

  • 算法

  • 分布式:

  • 缓存应用

  • 一致性哈希

  • RPC 原理和设计 - 通信协议,序列化方式,超时机制等

  • 负载均衡

  • 分布式缓存架构设计

  • 分布式消息

  • 消息队列设计与使用

  • 分布式环境下中间件部署

  • 分布式事务

  • paxos

  • 中间件:

  • MQ

  • ES

  • 数据库:

  • 数据库性能优化 - 慢 SQL,索引优化,大事务,内核参数调优

  • MySQL 数据库底层原理

  • 工作中遇到的数据库问题以及解决办法

  • Redis

  • 并发:

  • 非阻塞锁 CAS

  • 并发对编译器优化的影响

  • 线程池调优

  • 工作中遇到的并发问题以及解决办法

  • 技术趋势:

  • Docker

  • 微服务

业务相关

  • 做的项目所使用的技术栈以及其中的优缺点?

  • 如果从零开始,能不能重新将其实现?

  • 当前系统的使用方是谁? 用户量多大? 用户集中使用的时间点?

  • 系统落下了哪些数据? 这些数据的使用方是谁? 系统的依赖方是谁?

  • 从技术,产品,业务的角度去画下相关的流程图

工作交接

  • 和产品,业务,运营,技术之间的工作交接:

  • 知道自己的职责边界

  • 弄清楚哪些是自己需要做的

  • 哪些是其余的人应该做的

  • 交流起来不卑不亢

面试准备

  • 面试最好的准备方式:

  • 一定是平时多多积累

  • 遇到问题不要逃避

  • 深入去思考并解决

  • 在解决一个个问题的过程中积累解决问题的能力,形成自己的知识体系

  • 如何提前准备将自己平时积累展现出来:

  • 针对面试考察内容知识点思考答案以及扩展.如果能知道大部分,就要更加深入一部分.这个过程主要是整理自己的知识体系

  • 回忆整理简历和过往项目中的"难点","亮点",因为这是用来区分候选人的重要的点.面试一定会问"在项目中经历最大的技术难点是什么?" 整理一下思路,不至于在面试时因为时间久远而回忆不起来细节影响面试效果

  • 沟通过程要做到有理有据,不卑不亢.在技术问题上坚持自己的客观和原则,根据共同认可的事实进行逻辑判断得出观点

面试内容

Java 基础

  • private 修饰的方法可以通过反射访问,那么 private 意义是什么?


  • 考查对 Java 设计的掌握程度

  • Java 的 private 修饰符并不是为了绝对安全性设计的,更多的是对用户常规使用 Java 的一种约束

  • 从外部对对象进行常规调用时,可以清晰了解类结构


  • Java 中如何利用反射获取一个类的字段?

  • 常见的类加载

  • Java 类的初始化顺序?


  • Java 类的初始化顺序:

  • 基类静态代码块,基类静态成员变量(并列优先级,按照代码中出现的先后顺序执行,并且只有第一次加载时执行)

  • 派生类静态代码块,派生类静态成员变量(并列优先级,按照代码中出现的先后顺序,并且只有第一次加载时执行)

  • 基类普通代码块,基类普通成员变量(并列优先级,按照代码中出现的先后顺序执行)

  • 基类构造函数

  • 派生类普通代码块,派生类普通成员变量(并列优先级,按照代码中出现的先后顺序执行)

  • 派生类构造函数


  • 局部变量使用前需要显式赋值,否则编译通过不了,为什么需要这么设计?


  • 成员变量:

  • 可以不经初始化,在类的加载过程中的准备阶段可以赋予默认值

  • 赋值和取值访问的先后顺序具有不确定性

  • 成员变量可以在一个方法调用前赋值,也可以在方法调用后进行赋值. 这是在运行时发生的,编译器确定不了,所有交给 JVM 来赋值

  • 局部变量:

  • 在使用之前需要显式赋予初始值

  • 局部变量的赋值和访问顺序是确定的

  • 这样设计是一种约束,尽最大可能减少使用者犯错:

  • 假使局部变量可以使用默认值,可能总会无意间忘记赋值,进而导致不可预期的情况发生


  • ArrayList,LinkList 的区别?插入和查找哪个更快?

  • ArrayList 的随机插入?

  • LinkList 是单向链表还是双向链表?

  • 单向链表如何实现一个栈?

  • HashMap 的 put 操作底层是如何实现的?

  • HashTable,ConcurrentHashMap 的区别?为什么 ConcurrentHashMap 并发度更高?

  • ConcurrentHashMap 中的 get 和 set 什么时候会加锁?如果不加锁会产生什么问题?什么时候是 CAS 操作?

  • HashMap 的原理,在 Java 8 中做了哪些改变?


  • 从结构实现上来讲:

  • HashMap 实现是数组+链表+红黑树(红黑树部分是 JDK 1.8 之后增加的)

  • HashMap 最多允许一条记录的键为 null,允许多条记录的值为 null

  • HashMap 是非线程安全的


  • String 与 StringBuilder 的区别?


  • 可变与不可变:

  • String 不可变,每一次执行 "+" 都会新生成一个新对象,所以频繁改变字符串的情况下不用 String,以节省内存

  • 是否多线程安全:

  • StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的.StringBuffer 和 String 都是线程安全的


  • Vector 和 Array 的区别?


  • ArrayList 在内存不够时默认扩展是 50%+1 个,Vector 默认是扩展 1

  • Vector 是属于线程安全级别的,但是大多数情况下不使用 Vector,因为线程安全需要更大的系统开销


  • HashMap 和 HashTable 的区别?


  • HashTable 继承 Dictionary 类,HashMap 继承 AbstrctMap 类

  • HashTable 不允许空键值对,而 HashMap 允许空键值对,但最多只有一个空对象

  • HashTable 同步,而 HashMap 不同步,效率上比 HashTable 要高


  • ConcurrentHashMap 和 HashTable 比较,两个线程并发访问 Map 中同一条链,一个线程在尾部删除,一个线程在前面遍历查找.为什么前面的线程还能正确的查找到后面被另一个线程删除的节点?


  • ConcurrentHashMap 融合了 HashTable 和 HashMap 二者的优势:

  • HashTable 是做了同步的,是线程安全的,而 HashMap 未考虑同步,所以 HashMap 在单线程情况下效率比较高

  • HashTable 在多线程的情况下,同步操作能保证程序执行的正确性

  • 但是 HashTable 是阻塞的,每次同步执行的时候都要锁住整个结构

  • ConcurrentHashMap 正好解决了效率和阻塞问题:

  • ConcurrentHashMap 允许多个修改操作并发进行,技术的关键是使用了锁分离,即一个 Array 保存多个 Object,使用这些对象的锁作为分离锁,get 或者 put 的时候随机使用任意一个

  • ConcurrentHashMap 使用了多个锁来控制对 Hash 表的不同部分进行的修改

  • 从 JDK 1.6 开始,在 HashEntry 结构中,每次插入将新添加节点作为链的头节点,这与 HashMap 实现相同.

  • 每次删除一个节点时,会将删除节点之前的所有节点拷贝一份组成一个新的链,而将当前节点的上一个节点的 next 指向当前节点的下一个节点.从而在删除以后会有两条链存在

  • 因此可以保证即使在同一条链中,有一个线程在删除,而另一个线程在遍历,都能工作良好.因为遍历的线程能继续使用原有的链

  • 在 Java 8 中,使用 volatile HashEntry 保存数据,table 元素作为锁.从 Table 数组+单向链表又加上了红黑树

  • 红黑树是一种特别的二叉查找树,红黑树的特性:

  • 节点为红或黑

  • 根节点为黑

  • 叶节点为黑

  • 一节点为红,则一节点为黑

  • 一节点到其子孙节点所有路径上的黑节点数目相同


  • ArrayList 和 LinkedList 的区别?


  • ArrayList 底层的数据结构是数组,支持随机访问.LinkedList 的底层数据结构是链表,不支持随机访问

  • 使用下表访问一个元素:

  • ArrayList 的时间复杂度是 O(1)

  • LinkedList 的时间复杂度是 O(n). LinkedList 是双向链表


  • HashMap 中 put()元素产生冲突,为什么使用 LinkedList 拉链法解决而不用 ArrayList 解决?产生冲突时 key 值不等,新元素怎么样加入链表?为什么这么设计?

  • Java 中的 Comparator 和 Comparable 有什么不同?


  • Comparable 接口用于定义对象的自然顺序,是排序接口

  • Comparator 通常用于定义用户定制的顺序,是比较接口

  • 如果需要控制某个类的次序,而该类本身不支持排序,即没有实现 Comparable 接口,就可以建立一个"该类的比较器"来进行排序

  • Comparable 总是只有一个,但是可以有多个 Comparator 来定义对象的顺序


  • 抽象类是什么?与接口有什么区别?为什么要使用抽象类?


  • 抽象类是不允许被实例化的类,一个类只能使用一次继承关系,但是一个类可以实现多个接口

  • 抽象类和接口所反映出的设计理念不同:

  • 抽象类表示的是 "is - a"

  • 接口表示的是 "like - a"

  • 实现抽象类和接口的类必须实现其中的所有方法.抽象类可以有非抽象方法,接口中则不能有实现方法,但是在 Java 8 中允许接口中有静态默认方法

  • 接口中定义的变量默认是 public static final 型,且必须给出初值,所以实现类中不能重新定义,也不能改变这个值

  • 抽象类中定义的变量默认是 friendly 型,这个变量的值可以在子类中重新定义,也可以重新赋值

  • 子类中实现父类中的抽象方法时.可见性可以大于等于父类中的

  • 接口实现类类中的接口方法的可见性只能与接口中的相同,即为 public

  • 使用抽象类是为了重用,减少编码量,降低耦合性


  • Java 中的重载和重写?


  • 重载和重写都是使用相同的名称实现不同的功能,但是重载是编译时活动,重写是运行时活动

  • 可以在同一个类中重载方法,但只能在子类中重写方法,重写必须要有继承

  • 重载:

  • 重载的时候,方法名要一样,但是参数类型和参数个数不一样,返回值类型可以相同也可以不同

  • 无法以返回型别作为重载函数的区分标准

  • 重写:

  • 在子类中可以根据需要对从基类中继承的方法进行重写

  • 重写的方法和被重写的方法必须具有相同的方法名称,参数列表和返回类型

  • 重写方法不能使用比被重写方法更严格的访问权限


  • Collection 和 Collections 的区别是什么?


  • Collection< E >是 Java 集合框架中的基本接口

  • Collections 是 Java 集合框架提供的一个工具类,其中包含了大量用于操作和返回集合的静态方法


  • Java 中多态的实现原理?


  • 多态指的是父类引用指向子类的对象,调用方法时会调用子类的实现而不是父类的实现

  • 多态的实现关键在于动态绑定


  • Object 中定义了哪些方法?


  • clone()

  • equals()

  • hashCode()

  • toString()

  • notify()

  • notifyAll()

  • wait()

  • finalize()

  • getClass()


  • Java 中的泛型和类型擦除?


  • 泛型即参数化类型,在创建集合时,指定集合元素的类型,此集合只能传入该类型的参数

  • 类型擦除:Java 编译器生成的字节码不包括泛型信息,所以在编译时擦除

  • 泛型用最顶级的父类替换

  • 移除


  • JDK 1.8 引入的新特性?


  • Lambda 表达式

  • 允许像对象一样传递匿名函数 Stream API,充分利用现代多核 CPU,可以写出很简洁的代码

  • Date 与 Time API,有一个稳定简单的日期和时间库可供使用

  • 接口中可以有静态,默认方法

  • 重复注解,可以将相同的注解在同一类型上使用多次


  • Java 中 public,private,protected 以及默认关键字的访问范围?


  • protected 可在包内及包外子类访问

  • default 只能在同一包内访问

  • private 只能在同一个类中访问


  • 常用的数据结构?


  • 集合

  • 线性结构

  • 数组

  • 队列

  • 链表

  • 树形结构

  • 图状结构


  • Java 中的 TreeMap 是采用什么树实现的?


Java 中的 TreeMap 是使用红黑树实现的


  • 匿名内部类是什么?如何访问在匿名内部类外面定义的变量?


  • 匿名内部类就是没有名字的内部类,匿名内部类只能使用一次,通常用来简化代码编写

  • 匿名内部类只能访问外部类的 final 变量

  • 在 Java 8 中,如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了 final 修饰


  • 如何高效地创建一个线程安全的单例模式?


  • 通过枚举

  • 通过静态内部类

  • 也可以通过双重检查创建单例模式,但是这种单例模式是线程不安全的


  • poll()方法和 remove()方法的区别?


  • poll()和 remove 都是从队列中取出一个元素

  • poll()在获取元素失败时会返回空

  • remove()在获取元素失败时会抛出异常


  • 写一段代码在遍历 ArrayList 时移除一个元素?


  • 使用迭代器

Iterator it = list.iterator();
while (it.hasNext()) {
  if (...) {
    it.remove();
  }
}


  • 注解的原理

  • 开源协议哪些?


  • GPL: GNU General Public License,GNU 通用公共许可协议

  • LGPL: GNU Lesser General Public License, GNU 宽通用公共许可协议

  • BSD: Berkeley Software Distribution, 伯克利软件分发许可协议

  • MIT: Massachusetts Institute of Technology

  • Apache: Apache Licence, Apache 许可协议

  • MPL: Mozilla Public Licence, Mozilla 公共许可协议

线程

  • 线程同步与阻塞关系?同步一定阻塞吗?阻塞一定同步吗?


  • 线程同步与否和阻塞非阻塞没有关系

  • 同步是一个过程,阻塞是线程的一种状态

  • 多个线程操作共享变量时会出现竞争

  • 需要使用同步来防止两个以上的线程同时进入临界区内,在这个过程中,后进入临界区的线程将阻塞,等待先进入的线程走出临界区


  • 同步和异步有什么区别?


  • 同步和异步最大的区别是: 一个需要等待,一个不需要等待

  • 同步可以避免出现死锁,读脏数据的发生,一般共享某一资源的时候使用

  • 如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错

  • 同步就会按照顺序来修改


  • 线程池?


  • 线程池的作用是根据系统自身的情况,有效的限制执行线程的数量,使得运行效果达到最佳

  • 线程池主要执行的是:

  • 控制执行线程的数量

  • 超出数量的线程排队等候

  • 等待有任务执行完毕

  • 再从队列中最前面取出任务执行


  • 如何调用 wait()方法?使用 if 块还是循环?为什么?


  • wait()方法应该在循环中调用:

  • 因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足

  • 所以在处理前,循环检测条件是否满足更好

  • wait(),notify()和 notifyAll()方法是 java.lang.Object 类为线程提供的用于实现线程间通信的同步控制的等待和唤醒方法


  • 实现线程的几种方法?


  • 实现线程的方法:

  • 继承 Thread 类,重写 run 函数

  • 实现 Runnable 接口,重写 run 函数

  • 实现 Callable 接口,重写 call 函数


  • 什么是多线程环境下的伪共享 - false sharing?


  • 伪共享是多线程系统(这个系统的每隔处理器都有自己的局部缓存)中一个普遍存在的性能问题

  • 缓存系统中是以缓存行(cache line)为单位存储的

  • 缓存行是 2 的整数幂个连续字节,一般为 32 - 256 字节

  • 最常见的缓存行是 64 个字节

  • 当多线程修改相互独立的变量时,如果这些变量共享同一个缓存行,就会影响彼此的性能,这就是伪共享


  • 线程的状态?

  • concurrent 包下面,都用过哪些包?


  • java.util.concurrent

  • java.util.concurrent.atomic

  • java.util.concurrent.lock


  • ReadWriteLock 读写之间互斥吗?


  • ReadWriteRock 读写锁的使用场景:

  • 读 - 读

  • 读 - 写

  • 写 - 写

  • 除了读 - 读之间是共享的,其余都是互斥的


  • 怎样实现互斥锁和同步锁


  • 考查对 AQS, CAS 的掌握程度


  • Java 并发编程中的辅助类?


  • Semaphore

  • CountDownLatch

  • CyclicBarrier

  • Exchanger


  • CountDownLatch 和 CyclicBarrier 之间的区别?

  • ReadWriteLock 之间互斥吗?


  • ReadWriteLock 读写锁的使用场景:

  • 读,读

  • 读,写

  • 写,写

  • 除了读和读之间是共享的,其他都是互斥的这样之后会讨论怎样实现互斥锁和同步锁的,了解对 AQS,CAS 的掌握程度,技术学习深度


  • Semaphore 拿到执行权的线程之间是否互斥?


  • Semaphore 拿到执行权的线程之间是互斥的

  • Semaphore, CountDownLatch, CyclicBarrier, Exchanger 是 Java 并发编程中的 4 个辅助类,了解 CountDownLatch 和 CyclicBarrier 之间的区别

  • Semaphore 可能有多把锁,可以允许多个线程同时拥有执行权,这些有执行权的线程如果并发访问同一对象,会产生线程安全问题

  • Semaphore:

  • 可以有多把锁,允许多个线程同时拥有执行权

  • 这些有执行权的线程如果并发访问同一对象,会产生线程安全问题


  • 线程是怎样按照顺序执行的?

  • 线程执行过程中遇到异常会发生什么,怎样处理?

  • 写出一个单例模式?


  • 单例模式是最常遇到的设计模式之一,考查对经常碰到的问题的理解的深度

  • 单例一共有 5 种实现方式:

  • 饿汉

  • 懒汉

  • 静态内部类

  • 双检锁

  • 枚举

  • 要是写了简单的懒汉式可能会问: 要是多线程情况下怎样保证线程安全呢?

  • 使用双检锁可以保证线程安全.

  • 为什么要两次校验?光是双检锁还会有什么问题?

  • 对象在定义的时候加上 volatile 关键字

  • 引申讨论原子性和可见性,Java 内存模型,类的加载过程

  • 枚举方式,静态内部类,双检锁都可以实现单例模式. 双检锁的单例模式:

public Class Singleton {
  private Singleton() {
}

private volatile static Singleton instance;

public static Singleton getInstance() {
  if (null == instance) {
    synchronized (Singleton.class) {
      if (null == instance) {
        instance = new Singleton();
      }
    }
  }
  return instance;
}
} 


  • 双检锁写一个单例模式,为什么要使用 volatile 修饰对象?

  • Object object = new Object();这里的 object 为 null 吗?为什么?

  • Object object = new Object();初始化的顺序是什么?在 JVM 各个区域做了什么?

  • 什么情况下会发生死锁?写一个死锁?


  • 死锁的四个条件:

  • 示例: 定义两个 ArrayList,都加上锁 A,B.线程 1,2. 线程 1 获取到锁 A,请求锁 B. 线程 2 获取到锁 B,请求锁 A. 在等待对方释放锁的过程中都不会让出已获得的锁

public class DeadLock {
  public static void main(String[] args) {
    final List<Integer> list1 = Arrays.asList(1, 2, 3);
    final List<Integer> list2 = Arrays.asList(4, 5 ,6);
    new Thread(new Runnable() {
      @Override
      public void run() {
        synchronized (list1) {
          for (Integer i : list1) {
            System.out.println(i);
          } 
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          synchronized (list2) {
            for (Integer i : list2) {
              System.out.println(i);
            }
          }
        }
      }
    }).start();
 
     new Thread(new Runnable() {
       @Override
       public void run() {
         synchronized (list2) {
           for (Integer i : list2) {
             System.out.println(i);
           }
           try {
             Thread.sleep(1000);
           } catch (InterruptedException e) {
             e.printStackTrace();
           }
           synchronized (list1) {
             for (Integer i : list1) {
               System.out.println(i);
             }
           }
         }
       }
     }).start();
    }
}


  • String a = "ab"; String b = "a" + "b"; a == b; 是否相等,为什么?


  • 相等

  • new 一个对象赋给变量

  • 这行表达式创建了几个对象


  • int a = 1; 是原子性操作吗?



  • 可以使用 for 循环直接删除 ArrayList 的特定元素吗?可能会出现什么问题?怎样解决?


  • 不可以使用 for 循环直接删除 ArrayList 中的特定元素:

  • 不同的 for 循环会发生不同的异常

  • 泛型 for 会抛出 ConcurrentModificationException

  • 普通的 for 想要删除集合中重复且连续的元素,只能删除第一个

  • 原因:

  • JDK 中的 ArrayList 源码

  • ArrayList 中的 remove 有两个同名方法,只是入参不同:

  • 入参为 Object 的实现:

  • 一般情况下程序的执行路径走到 else 路径下最终调用 faseRemove() 方法,会执行 System.arraycopy() 方法,导致删除元素时涉及到数组元素的移动

  • 普通 for 循环,在 遍历第一个符合删除条件的字符串时将该元素从数组中删除,并且将后一个元素即第二个元素移动到当前位置,导致下一次遍历时后一个字符串并没有遍历成功,所以无法删除. 这种可以使用倒序删除的方式来避免

  • 解决方法: 使用迭代器 Iterator

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


  • 新的任务提交到线程池,线程池是怎么处理的?


  • 第一步: 线程池判断核心线程池里的线程是否都在执行任务. 如果不是,则创建一个新的工作线程来执行任务. 如果核心线程池里的线程都在执行任务,则执行第二步

  • 第二步: 线程池判断工作队列是否已经满了. 如果工作队列没有满,则将新提交的任务存储在这个工作队列中等待. 如果工作队列满了,则执行第三步

  • 第三步: 线程池判断线程池的线程是否都处于工作状态. 如果没有,则创建一个新的工作线程来执行任务. 如果已经满了,则交给饱和策略来处理这个任务


  • AQS 和 CAS 原理?


  • 抽象队列同步器 AQS: AbstractQueuedSychronizer

  • 如果说 java.util.concurrent 的基础是 CAS 的话,那么 AQS 就是整个 Java 并发包的核心

  • ReentrantLock, CountDownLatch, Semaphore 都用到了 AQS

  • AQS 实际上以双向队列的形式连接所有的 Entry:

  • ReentrantLock: 所有等待的线程都被放在一个 Entry 中并连成双向队列,前面一个线程使用 ReentrantLock 好了,则双向队列实际上的第一个 Entry 开始运行

  • AQS 定义了对双向队列所有的操作,并且只开放了 tryLock 和 tryRelease 方法给开发者使用.开发者可以根据自己的实现重写 tryLock 和 tryRelease 方法来实现自己的并发功能

  • 比较并替换 CAS: Compare and Swap

  • 假设有三个操作数:

  • 内存之 V

  • 旧的预期值 A

  • 要修改的值 B

  • 当且仅当预期值 A 和内存值 V 相同时,才会将内存值修改为 B 并返回 true. 否则什么都不做并返回 false.

  • 整个比较并替换的操作是一个原子操作

  • CAS 必须要 volatile 变量配合,这样才能保证每次拿到的变量是主内存中最新的响应值. 否则旧的预期值 A 对某条线程来说,永远是一个不会变的值 A. 只要某次 CAS 操作失败,则 CAS 操作永远不会成功

  • CAS 高效地解决了原子操作的问题,但仍然存在三大问题:

  • 循环时间长开销很大

  • 只能保证一个共享变量的原子操作

  • ABA 问题


  • AtomicLong 的底层实现原理?

  • ReentrantLock 是可重入锁,什么是可重入锁?

  • CAS 底层是怎么样实现原子性的?

  • synchronized 底层实现原理?


  • synchronized(this)原理:

  • 两条指令: monitorenter 和 monitorexit

  • 同步方法: 从同步方法的反编译的结果中可以看出 - 方法的同步并没有通过指令 monitorenter 和 monitorexit 来实现,相对于普通方法,在常量池中多了 ACC_SYNCHRONIZED 标识符

  • JVM 就是根据 ACC_SYNCHRONIZED 标识符来实现方法同步的:

  • 当方法被调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置

  • 如果设置了,执行线程将先获取 monitor,获取成功之后才能执行方法体,方法执行完之后再释放 monitor

  • 在方法执行期间,其余任何线程都无法再获得同一个 monitor 对象


  • lock 和 synchronized 的区别?

  • Java 对象头信息,偏向锁,轻量锁,重量级锁及各自相互间的转化?

  • 修饰类的锁和修饰方法的锁的区别?

  • volatile 作用,指令重排相关?


  • 理解 volatile 关键字的作用的前提是要理解 Java 的内存模型

  • volatile 关键字的作用主要有两点:

  • 多线程主要围绕可见性和原子性两个特性展开.使用 volatile 关键字修饰的变量,保证了在多线程之间的可见性.即每次读取到 volatile 变量,一定是最新的数据

  • 底层代码的执行: Java 代码 -> 字节码 -> 根据字节码执行对应的 C/C++代码 -> C/C++代码被编译成汇编语言 -> 和硬件电路交互.现实中,为了获取更好的性能,JVM 可能会对指令进行重排序,多线程下可能会出现意想不到的问题.使用 volatile 则会禁止对语义重排序,不过也会一定程度上降低代码的执行效率

  • 从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性. 比如 AtomicInteger

Java 线程池

  • 线程池的工作原理以及核心参数

  • 线程池的构造参数?

  • 如何中断一个线程,具体实现?正在 running 的线程能够被中断吗?

  • 线程中的线程个数,如何来设置?计算性的个数是 CPU 的个数还是 CPU 个数的 2 倍?

  • CPU 100%怎样定位?

数据结构

  • B 树和 B+树是解决什么样的问题的?怎么演化过来的?两者之间的区别是什么?


  • B 树和 B+树,既考查 MySQL 索引的实现原理,也考查数据结构基础

  • 首先从二叉树说起:

  • 因为会产生退化现象,提出平衡二叉树

  • 再提出怎么样让每一层放的节点多一些来减少遍历高度,引申出 m 叉树

  • m 叉搜索树同样会有退化现象,引出 m 叉平衡树,即 B 树

  • 这个时候每个节点既放了 key 又放了 value.怎样使每个节点放尽可能多的 key 值,以减少遍历高度也就是访问磁盘的次数

  • 可以将每个节点只放 key 值,将 value 值放在叶子节点,在叶子节点的 value 值增加指向相邻节点的指针,这就是优化后的 B+树

  • 然后谈谈数据库索引失效的情况:

  • 为什么给离散度低的字段,比如性别建立索引是不可取的?查询数据反而更慢

  • 如果将离散度高的字段和离散度低的字段,比如性别建立联合索引会怎样,有什么需要注意的?

Spring

  • 看过哪些框架的源码?

  • 什么是 Spring 框架? Spring 框架有哪些模块?

  • Spring 中使用了哪些设计模式?

  • Spring 框架的好处?

  • 什么是 IOC 控制反转?

  • 什么是依赖注入以及原理?

  • Spring 中 @Autowired 和 @Resource 的区别?

  • BeanFactory 和 ApplicationContext 有什么区别?

  • Spring Bean 的生命周期?

  • Spring Bean 的作用域有哪些以及各种作用域之间有什么区别?

  • Spring 框架中的单例 Beans 是线程安全的吗?

  • BeanFactory 和 FactoryBean 的区别和应用场景?

  • Spring 的代理如何实现?

  • JDK 代理机制?

  • AOP 和 IOC 原理?


  • AOP 和 IOC 是 Spring 的精华部分

  • AOP:

  • AOP 可以看作是对 OOP 的补充,对代码进行横向扩展

  • 通过代理模式实现.代理模式有静态代理和动态代理.

  • Spring 利用的是动态代理,在程序运行过程中将增强代码织入源代码中

  • IOC: 控制反转

  • 将对象的控制权交给 Spring 框架,用户使用对象无需创建,直接使用即可

  • AOP 和 IOC 重点要了解设计思想


  • Spring 怎样解决循环依赖的问题


  • Spring 的循环依赖问题:

  • 什么是循环依赖?

  • 怎样检测出循环依赖?

  • Spring 循环依赖有几种方式,使用基于 setter 属性的循环依赖为什么不会出现问题?


  • Bean 的生命周期?



  • dispatchServlet 怎样分发任务的?


  • 1. 用户发送请求 -> DispatcherServlet: 前端控制器收到请求后自己不进行处理,而是委托给其余解析器进行处理,作为统一的访问点,进行全局的流程控制

  • 2. DispatcherServlet -> HandlerMapping: HandlerMapping 将会把请求映射为 HandlerExecutionChain 对象.HandlerExecutionChain 包含一个 Hander 处理器,多个 HandlerInterceptor 拦截器

  • 3. DispatcherServlet -> HandlerAdapter: HandlerAdapter 将会将处理器包装为适配器,从而支持多种类型的处理器

  • 4. HandlerAdapter -> 处理器功能方法的调用: HandlerAdapter 将会根据适配的结果调用真正的处理器的功能处理方法,完成功能处理,并返回一个 ModelAndView 对象. ModelAndView 对象包含模型数据.逻辑视图名

  • 5. ModelAndView 的逻辑视图名 -> ViewResolver: ViewResolver 将逻辑的视图名解析为具体的 View

  • 6. View -> 渲染: View 会根据传进来的 Model 模型数据进行渲染,这里的 Model 是一个 Map 数据结构

  • 7. 返回控制权给 DispatcherServlet,由 DispatcherServlet 返回响应给用户

数据库

  • 数据库中的范式有哪些?


  • 第一范式: 数据库中的表的所有字段值都是不可分割的原子数据项

  • 第二范式: 数据库表中的每一列都和主键相关,而不能只和主键的某一部分相关

  • 第三范式: 数据库表中每一列数据都和主键直接相关,不能间接相关

  • 范式是为了减少数据冗余


  • MySQL 给离散度低的字段建立索引会出现什么问题?


  • 重复性强的字段,不适合添加索引

  • MySQL 给离散度低的字段,比如性别设置索引,再以性别作为条件查询反而会更慢

  • 一个表可能会涉及两个数据结构:

  • 数据表: 存放表中的数据

  • 索引

  • 索引:

  • 将一个或几个字段(组合索引)按规律排列起来,再附加上该字段所在行数据的物理地址(位于表中)

  • 比如有个字段是年龄,如果需要选取某个年龄段的所有行,那么一般情况下可能需要进行一次全表扫描

  • 但是如果以这个年龄段建立一个索引,那么索引会按照年龄值根据特定的数据结构建一个排列,这样在索引中就能迅速定位,不需要进行全表扫描

  • 为什么性别不适合建立索引呢?

  • 因为访问索引需要有额外的 IO 开销,从索引中拿到的只是地址,要想真正访问到数据还是要对表进行一次 IO

  • 如果要从表中的 100 万行数据中取几个数据,那么利用索引迅速定位,访问索引的 IO 开销就可以忽略不计

  • 如果要从标中的 100 万行数据取 50 万行数据,再访问 50 万次表,加起来的开销并不会比对表进行一次完整的扫描小

  • 如果将性别字段设为聚焦索引,那么肯定能加快大约一半该字段的查询速度

  • 聚焦索引:

  • 指的是表本身数据按照哪个字段的值来进行排序

  • 聚焦索引不会付出额外 IO 开销

  • 聚焦索引只能有一个-因此聚焦索引要用到搜索最频繁的字段上

  • 可以根据业务场景需要,将性别和其余的字段建立联合索引. 比如时间戳,要将时间戳字段放在性别前面


  • MySQL 的最左匹配原则?

  • MySQL 的隔离级别?

  • B+,B 树的区别?

  • MySQL 的常见优化和注意事项?

  • 数据库慢查询优化思路

  • MySQL 中的 log 有哪些?分别有什么作用?


  • undo log:

  • redo log:

  • binlog:


  • 数据库 ACID?

  • 数据库事务的隔离级别?

  • 数据库的分库分表?

  • 分库分表的全局唯一 ID 如何实现?

  • 数据库中的索引的结构?什么情况下适合建索引?


  • 数据库中的索引的结构是一种排序的数据结构,数据库的索引是通过 B 树和变形的 B+树实现的

  • 什么情况下不适合建立索引:

  • 对于在查询过程中很少使用或者参考的列

  • 对于只有很少数据值的列

  • 对于定义为 image,text bit 数据类型的列

  • 当修改性能远远大于检索性能时

  • 根据系统自身的环境情况,有效限制线程数量,使得运行效果达到最佳

  • 线程主要是通过控制执行线程的数量,超出数量的线程排队等候,等待有任务执行完毕,再从队列最前面取出任务执行


  • MySQL 常用优化?


  • SQL 优化

  • 表结构优化

  • 索引优化

  • 缓存参数优化


  • Redis 的数据类型有哪些?

分布式

  • CAP 理论?

  • 写一个生产者消费者模式?


  • 生产者消费者模式:

  • synchronized 锁住一个 LinkedList:

  • 生产者: 只要队列不满,生产后往里存

  • 消费者: 只要队列不空,消费后往外取

  • 两者通过 wait()notify() 进行协调

  • 要考虑怎么样提高效率

  • 熟悉消息队列设计精要思想及使用


  • 为什么要使用消息队列 MQ?


  • 异步处理: 相对于传统的串行,并行方式,提高了系统的吞吐量

  • 应用解耦: 系统间通过消息通信,不用关心其他系统的处理

  • 流量削峰: 可以通过消息队列长度控制请求量,可以缓解短时间内高并发请求

  • 日志处理: 解决大量日志传输

  • 消息通讯: 消息队列一般都内置了高效的通信机制,因此可以用在纯的消息通讯. 比如实现点对点消息队列,聊天室等


  • 如何保证消息队列 MQ 的高可用?


  • 将所有 Broker 和待分配的 Partition 排序

  • 将第 i Partion 分配到第 (i mod n)Broker

  • 将第 i Partion 的第 j Replica 分配到第 ((i+j) mod n)Broker


  • MQ 有哪些常见问题?如何解决这些问题?


  • 消息队列的顺序问题

  • 消息有序指的是可以按照消息的发送顺序来消费

  • 假定生产者产生了 2 条消息:M1,M2.假定 M1 发送到 S1,M2 发送到 S2.如果要保证 M1 优先于 M2 被消费,如何保证:

  • 解决方案:

  • 保证生产者 - MQSever - 消费者是一对一对一的关系

  • 缺陷:

  • 并行度会成为系统的瓶颈,吞吐量不够

  • 会出现更多的异常处理问题: 只要消费者出现问题,就会导致整个流程堵塞,不得不解决阻塞的问题

  • 可以通过合理的设计或者将问题分解来规避:

  • 不关注乱序的应用实际大量存在

  • 队列无序并不意味着消息无序

  • 消息的重复问题:

  • 造成消息重复的根本原因: 网络不可达

  • 所以解决这个问题的方法就是绕过这个问题.也就是: 如果消费端收到两条一样的消息,应该怎样处理?

  • 解决方案:

  • 消费端处理消息的业务逻辑保持幂等性

  • 只要保持幂等性,不管来多少条重复消息,最后处理的结果都一样

  • 保证每条消息都有唯一编号且保证消息处理成功与去重表的日志同时出现

  • 利用一张日志表来记录已经处理成功的消息的 ID,如果新到的消息 ID 已经在日志表中,那么就不再处理这条消息


  • Kafka,ActiveMQ,RabbitMQ,RocketMQ 各有什么优缺点?



  • Dubbo 是什么?主要应用场景?核心功能?

  • Dubbo 的实现过程?



  • Dubbo 节点角色?



  • Dubbo 中的调用关系?


  • 服务容器负责启动,加载,运行服务提供者

  • 服务提供者在启动时,向注册中心注册自己提供的服务

  • 服务消费者在启动时,向注册中心订阅自己所需的服务

  • 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

  • 服务消费者,从提供者地址列表中,基于软负载均衡算法,选择一台提供者进行调用.如果调用失败,再选择另一台进行调用

  • 服务消费者和服务提供者,在内存中累计调用次数和调用时间,定时每分钟发送统计数据到监控中心


  • Dubbo 的注册中心集群宕机,发布者和订阅者之间还能够通信吗?

  • Dubbo 支持哪些协议,每种协议的应用场景,优缺点?

  • Dubbo 的超时时间怎样设置?

  • Dubbo 的负载均衡策略有哪些?


  • Random:

  • 随机负载均衡策略,按权重设置随机概率

  • 在一个截面上的碰撞概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重

  • RoundRobin:

  • 轮循负载均衡策略,按公约后的权重设置轮循比率

  • 存在慢的提供者累积请求的问题

  • 比如: 第二台机器很慢,但没有宕机,当请求到第二台机器就会卡住,久而久之,所有的请求都会卡在 调到第二台机器的时候

  • LeastActive:

  • 最少活跃调用数负载均衡策略,相同活跃数的随机调用.活跃数指的是调用前后计数差

  • 使慢的提供者收到更少的请求,因为越慢的提供者的调用前后计数差会越大

  • ConsistentHash:

  • 一致性 Hash 负载均衡策略,相同的参数请求总是发到同一提供者

  • 当某台提供者宕机时,原本发往该提供者的请求,基于虚拟节点,平摊到其他提供者,不会引起剧烈变动

  • 缺省只对第一个参数 Hash,如果要修改,需要修改 < dubbo:parameter key="hash.arguments" value="0,1" />

  • 缺省使用 160 份虚拟节点,如果要修改,需要修改< dubbo:parameter key="hash.nodes" value="320" >


  • Dubbo 集群容错策略?


  • Failover: 失败自动切换,当出现失败,重试其他服务器. 通常用于读操作,但重试会带来更长延迟. 可以通过设置 retries="2" 来设置重试次数,不包含第一次

  • Failfast: 快速失败,只发起一次调用,失败立即报错. 通常用于非幂等性的写操作,比如新增记录

  • Failsafe: 失败安全,出现异常时,直接忽略. 通常用于写入审计日志等操作

  • Failback: 失败自动恢复,后台记录失败请求,定时重发. 通常用于消息通知操作

  • Forking: 并行调用多个服务器,只要一个成功即返回. 通常用于实时性要求比较高的读操作,但需要浪费更多服务资源,可以通过设置 forks="2"来设置最大并行数

  • Broadcast: 广播调用所有提供者,逐个调用,任意一台报错即报错. 通常用于通知所有提供者更新缓存或日志等本地资源信息


  • Dubbo 的动态代理策略?


  • Dubbo 作为 RPC 框架,首先要完成的就是跨系统,跨网络的服务调用

  • 消费方和提供方遵循统一的接口定义

  • 消费方调用接口时,Dubbo 将其转换为统一格式的数据结构

  • 通过网络传输,提供方根据规则找到接口实现,通过反射完成调用

  • 消费方获取的是对远程服务的一个代理 Proxy, 提供方因为要支持不同的接口实现,需要一个包装层 Wrapper

  • 调用过程:

  • 消费方的 Proxy 和提供方的 Wrapper 得以让 Dubbo 构建出复杂,统一的体系

  • 这种动态代理与包装是通过 SPI 的插件方式实现的,接口就是 ProxyFactory:

  • ProxyFactor 有两种实现方式:

  • 基于 JDK 的代理实现

  • 基于 javassist 的实现

  • ProxyFactory 接口上定义了 @SPI("javassist"), 默认为 javassist 的实现


  • Dubbo 有哪些注册中心?

  • Dubbo 与 Spring 之间的关系?

  • Dubbo 使用的是什么通信框架?

  • Dubbo 的安全机制是如何实现的?

  • Dubbo 连接注册中心和直连的区别?

  • Dubbo 的通信协议 dubbo 协议为什么采用异步单一长连接的方式?

  • Dubbo 的通信协议 dubbo 协议适用范围和应用场景?

  • Dubbo 支持哪些序列化协议?Hessian?Hessian 的数据结构?


  • Dubbo 序列化: 阿里基于 Java 的序列化实现

  • Hessian2 序列化: Hessian 是一种跨语言的高效二进制的序列化方式. 这里实际不是原生的 Hessian2 序列化,而是阿里修改过的 Hessian Lite,是 Dubbo 默认启用的序列化方式

  • Json 序列化: 目前有两种实现:

  • 采用阿里的 fastjson

  • 采用 Dubbo 自身实现的简单 Json 库

  • 一般情况下,json 这种文本序列化性能不如二进制序列化

  • Kryo 和 FST: Kryo 和 FST 的性能普遍优于 Hessian 和 Dubbo 序列化


  • Hessian 序列化和 Java 默认的序列化区别?


  • Hessian 是一个轻量级的 remoting on http 工具,采用 Binary RPC 协议,很适合发送二进制数据,同时又具有防火墙穿透能力

  • Hessian 支持跨语言串行

  • Hessian 序列化比 Java 默认的序列化具有更好的性能和易用性

  • Hessian 序列化支持的语言比较多


  • Protoco Buffer 是什么?


  • Protoco Buffer 是谷歌出品的一种轻量并且高效的结构化数据存储格式,性能比 Json,XML 强大得多

  • Protoco 的序列化和反序列化简单并且速度快. 原因在于:

  • 编码和解码方式简单,只需要简单的数学运算=位移等等

  • 采用 Protoco Buffer 自身的框架代码和编译器共同完成

  • Protoco Buffer 的数据压缩效果好,即序列化后数据量的体积小. 原因在于:

  • 采用独特的编码方式,比如 Varint,Zigzag 编码方式等等

  • 采用 T - L - V 数据存储方式,减少了分隔符的使用并且数据存储得紧凑


  • 注册中心宕机了可以继续通信吗?


  • 可以

  • Dubbo 消费者在应用启动时会从注册中心拉取已注册的生产者的地址接口,并缓存在本地. 每次调用时,按照本地存储的地址进行调用


  • ZooKeeper 有什么用?ZooKeeper 原理是什么?


  • ZooKeeper 是一个分布式应用协调系统,已经应用到了许多分布式项目中,用来完成

  • 统一命名服务

  • 状态同步服务

  • 集群管理

  • 分布式应用配置项的管理

  • 每个 Server 在内存中存储了一份数据

  • ZooKeeper 启动时,将从实例中选举一个 leader(Paxo 协议)

  • Leader 负责处理数据更新等操作(Zab 协议)

  • 当且仅当大多数 Server 在内存中成功修改数据时,一个更新操作成功


  • Netty 有什么用?NIO,BIO,AIO 有什么用?有什么区别?


  • Netty 是一个网络通信框架

  • Netty 进行事件处理的流程:

  • Channel 是连接的通道,是 ChannelEvent 的产生者

  • ChannelPipeline 可以理解为 ChannelHandler 的集合

  • IO 的方式通常分为:

  • 同步阻塞的 BIO

  • 同步非阻塞的 NIO

  • 异步非阻塞的 AIO

  • 在使用同步阻塞的 BIO 的网络应用:

  • 如果要同时处理多个客户端请求,或者是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理

  • 同步非阻塞的 NIO 基于 Reactor:

  • socket 有流可读或者可写入 socket 时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或者写入操作系统

  • 这个时候,不是一个连接就要对应一个处理线程了.而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的

  • 异步非阻塞的 AIO 与 NIO 不同:

  • 当进行读写操作时,只需要直接调用 API read 或者 write 方法即可

  • 这两种方法均为异步的:

  • 对于读操作而言, 当有流可读取时,操作系统会将可读的流传入 read 方法的缓冲区,并通知应用程序

  • 对于写操作而言,当操作系统将 write 方法传递的流写入完毕时,操作系统主动通知应用程序

  • read 或者 write 方法都是异步的,完成后会主动调用回调函数


  • 为什么要进行系统拆分?拆分不用 Dubbo 可以吗?


  • 系统拆分的分类:

  • 从资源角度:

  • 应用拆分

  • 数据库拆分

  • 从采用的先后顺序:

  • 水平扩展

  • 垂直拆分

  • 业务拆分

  • 水平拆分

  • 是否使用 Dubbo 依据实际业务场景来决定:

  • 当垂直应用越来越多,应用之间的交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求. 此时,用于提高业务复用以及整合的分布式框架 RPC 是关键

  • 当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需要增加一个调度中心基于访问压力实时管理集群容量,提高集群的利用率. 此时,用于提高机器利用率的资源调度和治理中心 SOA 是关键


  • Dubbo 和 Thrift 有什么区别?


  • Dubbo 支持服务治理,而 Thrift 不支持

  • Thrift 是跨语言 RPC 框架


  • Redis 如何实现分布式锁?


setNX key valuevalue 保证唯一性,避免线程 A 释放线程 B 拿到的锁


  • 实现分布式锁如果使用 setNX 命令,那么如果锁的机器宕机了,其他服务怎么拿得到锁?


设置过期时间


  • 如何来设置过期时间?先 set key value,再设置过期时间吗?如果是两条命令,set key value 成功,设置过期时间失败,一样存在如上问题.那么如何来保证 set key value 和设置过期时间的原子操作?


set 命令提供了相应的原子操作命令来保证 set key value 和设置过期时间的原子操作


  • Redis 是使用的集群吗?如果是集群,当客户端执行 setNX 时 Redis 集群,如何做才认为 set 成功?一半集群 set 成功,就认为成功吗?还是全部 set 成功才认为成功?


Redis 集群使用的是多主多从,当一半以上的主节点 set 成功,才算成功


  • 一半成功就算成功,假设 Redis 集群有 a,b,c 三个主节点,各有一个从节点,线程 A 在 a,b 主节点 set 成功,而在 c 主节点 set 失败,此时线程 A 获取到锁,而此时刚好 b 主节点宕机,刚好数据还没有同步到其从节点,那么此时从节点 b'升级到主节点,那么线程 B 对相同的 key 执行 set 命令来获取锁,在 b'和 c 节点 set 成功,这样同样可以获取到锁,这个时候出现了多个线程获取到同一把锁?



  • Redis 缓存,如何完成更新?


先 Delete 缓存,再更新 DB,延时一段时间再 Delete 缓存或者先更新 DB,延时一段时间再 Delete 缓存


  • 为什么要延时一段时间?


因为如果线程 A 先 Delete 缓存,此时线程 B 发现缓存中没有数据,则从 DB 中读出老数据并 reload 到缓存,线程 A 更新数据库之后,则缓存与数据库数据库中的数据不一致,因此需要延时一段时间执行删除


  • 如果 Delete 失败?


重试机制


  • Redis 中的数据结构有哪些?跳表用于哪些场景?



  • volatile 关键字?Lock?


  • 并发编程中的问题:

  • 原子性问题

  • 可见性问题

  • 有序性问题

  • volatile:

  • volatile 关键字能保证可见性,只能禁止指令重排序,不能保证原子性

  • 可见性只能保证每次读取的是最新的值,但是 volatile 无法保证对变量的操作的原子性

  • 在生成的会变语句中加入 Lock 关键字和内存屏障

  • Lock:

  • Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,能够使用更优雅的方式解决线程同步问题

  • 用 synchronized 修饰的方法或者语句块在代码执行完之后锁自动释放,然而使用 Lock 修饰的方法或者语句需要手动释放锁


  • Java 每次修改都需要重新编译打包部署,有没有更好的额方法?


热部署


  • 进程间通信有哪几种方式?


  • 管道: Pipe

  • 命名管道: Named Pipe

  • 信号: Signal

  • 消息队列: Message

  • 共享内存

  • 内存映射: Mapped Memory

  • 信号量: Semaphore

  • 套接口: Socket


  • Synchronized 修饰的方法实例?


Synchronized 修饰静态方法,锁定本身不是实例.非静态方法锁定实例


  • 操作系统什么情况下会死锁?


  • 死锁: 指多个进程在运行过程中因争夺资源而造成的一种僵局

  • 产生原因: 竞争资源

  • 当系统中多个进程使用共享资源,并且资源不足以满足需要,会引起进程对资源的竞争而产生死锁

  • 进程间推进的顺序不当

  • 请求和释放资源的顺序不当,同样也会产生进程死锁


  • 产生死锁的四个条件


  • 互斥条件: 进程独占资源

  • 请求与保持: 进程因请求资源而阻塞时,对已获得的资源保持不放

  • 不剥夺条件: 进程已经获得资源,在未使用完之前,不能强行剥夺

  • 循环等待: 若干进程之间形成头尾相接的循环等待资源关系


  • 如何理解分布式锁?


线上服务器是分布式多台部署的,经常会面临解决分布式场景下数据一致性问题,这是就要利用分布式锁来解决这些问题


  • 分布式事务有哪些实现方式?

  • 微服务的架构设计?

JVM

  • Java 类的初始化顺序?


  • Java 类的初始化顺序:

  • 基类静态代码块,基类静态成员变量. 并列优先级,按照代码中出现的先后顺序执行,并且只有第一次加载时执行

  • 派生类静态代码块,派生类静态成员变量. 并列优先级,按照代码中出现的先后顺序执行,并且只有第一次加载时执行

  • 基类普通代码块,基类普通成员变量. 并列优先级,按照代码块中出现的先后顺序执行

  • 基类构造函数.

  • 派生类普通代码块,派生类普通成员变量. 并列优先级,按照代码块中出现的先后顺序执行

  • 派生类构造函数.


  • 为什么要进行分代?

  • 对方法区和永久区的理解以及两者的区别?


  • 方法区JVM 规范中要求的 ,永久区是 Hotspot 虚拟机对方法区的具体实现

  • 方法区是规范,永久区是实现方式(JDK 1.8 以后做了改变)


  • 一个 Java 类有 3 个文件,编译后有几个 class 文件?


  • 文件中有几个类,编译后就有几个 class 文件


  • 局部变量使用前需要显式的赋值,否则编译通过不了,为什么要这样设计?


  • 成员变量是可以不经初始化的,在类加载过程的准备阶段即可以给成员变量赋予默认值.

  • 局部变量在使用之前需要显式赋予初始值

  • javac 不是推断不出不可以这样做,对于成员变量而言,其赋值和取值访问的先后顺序具有不确定性,对于一个成员变量可以在一个方法调用前赋值,也可以在方法调用后进行赋值,这是运行时发生的,编译器确定不了,交给 JVM 做比较合适

  • 对于局部变量而言,局部变量的赋值和访问顺序是确定的,这样设计是一种约束,尽最大程度减少使用者犯错的可能性:

  • 假使局部变量可以使用默认值,可能总会无意间忘记赋值,进而导致不可预期的情况出现


  • JVM 的内存分区?

  • 堆内存的分区以及每个分区的垃圾回收算法?回收器 G1,CMS 有标记清除,标记整理法?

  • 如何排查 Full GC,OOM?

  • 线程个数太多会导致 OOM,但是这里的线程包括程序的所有线程吗?比如包括 pigeon 的线程池吗?

  • JVM 中类的加载过程,双亲委派模型中有哪些方法?


  • 类加载过程:

  • 加载

  • 验证: 验证阶段作用是保证 Class 文件的字节流包含的信息符合 JVM 规范,不会给 JVM 造成伤害

  • 准备: 准备阶段为变量分配内存并设置类变量的初始化

  • 解析: 解析过程是将常量池内的符号引用替换成直接引用

  • 初始化

  • 双亲委派模型中的方法: 双亲委派是指如果一个类收到类加载请求,不会自己先尝试加载,先找父类加载器完成.当顶层启动类加载器表示无法加载这个类的时候,子类才会自己去加载.当回到最开始的发起者加载器还无法加载时,并不会向下找,而是抛出 ClassNotFound 异常

  • 启动(Bootstrap)类加载器

  • 标准扩展(Extension)类加载器

  • 应用程序(Application)类加载器

  • 上下文(Custom)类加载器

  • 意义是防止内存中出现多份同样的字节码


  • 垃圾收集器了解哪些?

  • GC ROOTS 包括哪些?

  • GC 算法?什么样的对象算是可回收对象?可达性分析?CMS 收集器?


  • JVM 如何判断一个对象已经变成可回收的垃圾:

  • 引用计数器法: 引用计数器无法解决循环引用的问题

  • 根搜索算法: 从一系列的 GC Roots 对象开始向下搜索,搜索的路径称为引用链.当一个对象到 GC Roots 之间没有引用链时称为引用不可达.引用不可达的对象被认为是可回收对象

  • 几种垃圾回收器:

  • Serial New 或者 Serial Old: 串行

  • Parrallel New: 并行

  • Parrallel Scavenge

  • Parrallel Old

  • G1: 一款并行与并发收集器,并且可建立可预测的停顿时间模型,整体上是基于标记清理,局部采用复制

  • CMS

  • CMS 收集器是一个以获得最短回收停顿时间为目标的收集器,是一种并发收集器,采用的是 Mark - Sweep 算法


  • JVM 中的 GC 复制算法是怎样实现的?

  • JVM 分为哪些区?每一个区的作用?


  • 方法区(Method): 被所有线程共享,方法区包含所有的类信息和静态变量

  • 堆(Heap): 被所有的线程共享,存放对象实例以及数组,Java 堆是 GC 的主要区域

  • 栈(Stack): 每一个线程包含一栈区,栈中保存一些局部变量

  • 程序计数器: 当前线程执行的字节码行指示器


  • JVM 新生代,老年代,持久代.都存储些什么内容?


  • 新生代存放所有新生成的对象

  • 老年代存放的都是一些生命周期较长的对象

  • 持久代主要存放的是 Java 类的类信息,与垃圾收集要收集的 Java 对象关系不大


  • 内存溢出和内存泄露?


  • 内存溢出: out of memory,程序申请内存时,没有足够的内存

  • 内存泄露: 垃圾对象无法回收,可是使用 memory analyzer 工具查看泄露


  • 进程与线程?


  • 进程: 运行中的程序,具有独立性,动态性,并发性

  • 线程: 指进程中的顺序执行流

  • 进程与线程的区别:

  • 进程间不共享内存

  • 创建进程进行资源分配的代价要大得多,所以多线程在高并发的环境中效率高


  • 序列化和反序列化?


  • 序列化: 将 Java 对象转化为字节序列

  • 反序列化: 将字节序列转化为 Java 对象

  • 序列化和反序列化主要是为了 Java 线程间的通讯,实现对象传递.只有实现了 Serializable 或者 Externalizable 接口类对象才可被序列化


  • 64 位 JVM 中,int 的长度是多长?


在 JVM 中,int 类型的变量的长度是一个固定值,与平台无关,4 个字节,长度为 32


  • Java 中 WeakReference 与 SoftReference 的区别?


  • Java 中一共有四种类型的引用:

  • StrongReference

  • SoftReference

  • WeakReference

  • PhantomReference

  • StrongReference 是 Java 的默认引用实现,会尽可能长时间的存活于 JVM 内,当没有任何对象指向时将会被 GC 回收

  • SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收,通过虚拟机保证.这一特性使得 SofeReference 非常适合缓存应用

  • WeakReference 是一个弱引用,当所引用的对象在 JVM 内不再有强引用时,将被 GC 回收

  • WeakReference 和 SoftReference 的区别:

  • WeakReference 与 SoftReference 都有利于提高 GC 和内存的效率

  • WeakReference 一旦失去最后一个强引用,就会被 GC 回收

  • SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收,通过虚拟机保证


  • JVM 内存的划分?

  • Java 堆的划分?

  • Java 中堆和栈有什么区别?


  • Java 中的堆和栈属于不同的内存区域,使用目的也不同

  • 栈通常用于保存方法帧和局部变量.而对象总是在堆上分配

  • 栈通常比堆小,也不会在多个线程之间共享,而堆是被整个 JVM 所有线程共享


  • Java 堆空间及 GC?


  • Java 堆空间:

  • 当通过 Java 命令启动 Java 进程的时候,会分配内存,内存的一部分用于创建堆空间

  • 当程序中创建对象的时候,就从堆空间中分配内存

  • GC:

  • GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配


  • 哪些对象会被 JVM 回收?

网络

  • TCP 如何保证可靠性传输?三次握手过程?


  • 在 TCP 连接中,数据流必须以正确的顺序送达对方-TCP 可靠性:

  • 通过顺序编码确认(ACK) 来实现的

  • TCP 连接是通过三次握手进行初始化的,三次握手的目的是同步连接双方序列号和确认号并交换 TCP 窗口大小信息:

  • 第一次: 客户端发起连接

  • 第二次: 表示服务器收到了客户端请求

  • 第三次: 表示客户端收到了服务器反馈


  • 如何识别 session?


  • cookie 中存储的 session id


  • HTTP 报文结构?

  • HTTP 状态码?

  • Linux 下的常用命令:


  • cd: 用来改变所在目录. cd / - 转到根目录, cd ~ - 转到用户目录

  • ls: 用来查看目录的内容

  • cp: 用来拷贝文件. cp sourceFileName targetFileName

  • mv: 移动文件. mv t.txt Document


  • 常用的 Hash 算法有哪些?


  • 加法 Hash: 所谓的加法 Hash 就是把输入元素一个一个加起来构成最后的结果

  • 位运算 Hash: 这种类型的 Hash 函数通过利用各种位运算,比如移位或者异或来充分的混合输入元素

  • 乘法 Hash: 33*hash + key.charAt(i)


  • 什么是一致性 Hash?


  • 一致性 Hash 的设计目标是为了解决因特网中的热点(Hot spot)问题,一致性 Hash 算法提出了在动态变化的 Cache 环境中,判定 Hash 算法好坏的四个定义:

  • 平衡性 :Balance

  • 单调性 :Monotonicity

  • 分散性 :Spread

  • 负载 :Load


  • 表单提交中,get 和 post 的区别?


  • get 是从服务器获取信息, post 是向服务器传信息

  • get 传送的数据量比较小, post 传递的数据量可以比较大

  • get 的安全性比 post 低


  • TCP 协议和 UDP 协议有什么区别?


  • TCP: Tranfer Control Protocol, 是一种面向连接的保证传输协议,在传输数据流之前,双方会建立一条虚拟的通信道,可以极少差错传输数据

  • UDP: User DataGram Protocol,是一种无连接的协议,使用 UDP 时,每一个数据段都是一个独立的信息,包括完整的源地址和目的地,在网络上以任何可能的路径到达目的地.因此,能否到达目的地,以及到达目的地的时间和内容的完整性都不能保证

  • TCP 比 UDP 多了建立连接的时间.相对 UDP 而言,TCP 具有更高的安全性和可靠性

  • TCP 协议传输的大小不限制,一旦连接被建立,双方可以按照吧一定的格式传输大量的数据,而 UDP 是一个不可靠协议,大小有限制,每次不能超过 64K


  • Java IO 模型有哪几种?

  • 同步和异步,阻塞和非阻塞的区别?

  • Netty 基本介绍



发布于: 刚刚阅读数: 2
用户头像

一位攻城狮的自我修养 2021.04.06 加入

分享技术干货,面试题和攻城狮故事。 你的关注支持是我持续进步的最大动力! https://github.com/ChovaVea

评论

发布
暂无评论
Java高质量面试总结