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框架:
业务相关
- 做的项目所使用的技术栈以及其中的优缺点?
- 如果从零开始,能不能重新将其实现?
- 当前系统的使用方是谁? 用户量多大? 用户集中使用的时间点?
- 系统落下了哪些数据? 这些数据的使用方是谁? 系统的依赖方是谁?
- 从技术,产品,业务的角度去画下相关的流程图
工作交接
- 和产品,业务,运营,技术之间的工作交接:
- 知道自己的职责边界
- 弄清楚哪些是自己需要做的
- 哪些是其余的人应该做的
- 交流起来不卑不亢
面试准备
- 面试最好的准备方式:
- 一定是平时多多积累
- 遇到问题不要逃避
- 深入去思考并解决
- 在解决一个个问题的过程中积累解决问题的能力,形成自己的知识体系
- 如何提前准备将自己平时积累展现出来:
- 针对面试考察内容知识点思考答案以及扩展.如果能知道大部分,就要更加深入一部分.这个过程主要是整理自己的知识体系
- 回忆整理简历和过往项目中的"难点","亮点",因为这是用来区分候选人的重要的点.面试一定会问"在项目中经历最大的技术难点是什么?" 整理一下思路,不至于在面试时因为时间久远而回忆不起来细节影响面试效果
- 沟通过程要做到有理有据,不卑不亢.在技术问题上坚持自己的客观和原则,根据共同认可的事实进行逻辑判断得出观点
面试内容
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:
@SPI("javassist") public interface ProxyFactory { @Adaptive({Constants.PROXY_KEY}) <T> T getProxy(Invoker<T> invoker) throws RpcException; @Adaptive({Constants.PROXY_KEY}) <T> invoker<T> getInvoker(T proxy, Class<T> type, URL url) throws RpcException; }
- 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 value
value保证唯一性,避免线程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基本介绍