一、Java 基础篇
1. Object 有哪些常用方法?大致说一下每个方法的含义
java.lang.Object
下面是对应方法的含义。
clone 方法
保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出
CloneNotSupportedException 异常,深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现 Cloneable,然后重写 clone 方法。
finalize 方法
该方法和垃圾收集器有关系,判断一个对象是否可以被回收的最后一步就是判断是否重写了此方法。
equals 方法
该方法使用频率非常高。一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。子类一般都要重写这个方法。
hashCode 方法
该方法用于哈希查找,重写了 equals 方法一般都要重写 hashCode 方法,这个方法在一些具有哈希功能的 Collection 中用到。
一般必须满足 obj1.equals(obj2)==true。可以推出 obj1.hashCode()==obj2.hashCode(),但是 hashCode 相等不一定就满足 equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
- JDK 1.6、1.7 默认是返回随机数;
- JDK 1.8 默认是通过和当前线程有关的一个随机数 + 三个确定值,运用 Marsaglia’s xorshift scheme 随机数算法得到的一个随机数。
wait 方法
配合 synchronized 使用,wait 方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 方法一直等待,直到获得锁或者被中断。wait(long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
- 其他线程调用了该对象的 notify 方法;
- 其他线程调用了该对象的 notifyAll 方法;
- 其他线程调用了 interrupt 中断该线程;
- 时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个 InterruptedException 异常。
notify 方法
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程)。
notifyAll 方法
配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。
总结
只要把上面几个方法熟悉就可以了,toString 和 getClass 方法可以不用去讨论它们。该题目考察的是对 Object 的熟悉程度,平时用的很多方法并没看其定义但是也在用,比如说:wait() 方法,equals() 方法等。
Class Object is the root of the class hierarchy.Every class has Object as a superclass. All objects, including arrays, implement the methods of this class.运行项目并下载源码
大致意思:Object 是所有类的根,是所有类的父类,所有对象包括数组都实现了 Object 的方法。
面试扩散
上面提到了 wait、notify、notifyAll 方法,或许面试官会问你为什么 sleep 方法不属于 Object 的方法呢?因为提到 wait 等方法,所以最好把 synchronized 都说清楚,把线程状态也都说清楚,尝试让面试官跟着你的节奏走。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
2. Java 创建对象有几种方式?
这题目看似简单,要好好回答起来还是有点小复杂的,我们来看看,到底有哪些方式可以创建对象?
1. 使用 new 关键字,这也是我们平时使用的最多的创建对象的方式,示例:
User user=new User();运行项目并下载源码
2. 反射方式创建对象,使用 newInstance(),但是得处理两个异常 InstantiationException、IllegalAccessException:
User user=User.class.newInstance();Object object=(Object)Class.forName("java.lang.Object").newInstance()
运行项目并下载源码
3.使用 clone 方法,前面题目中 clone 是 Object 的方法,所以所有对象都有这个方法。
4.使用反序列化创建对象,调用 ObjectInputStream 类的 readObject() 方法。
我们反序列化一个对象,JVM 会给我们创建一个单独的对象。JVM 创建对象并不会调用任何构造函数。一个对象实现了 Serializable 接口,就可以把对象写入到文件中,并通过读取文件来创建对象。
总结
创建对象的方式关键字:new、反射、clone 拷贝、反序列化。
3. 获取一个类对象的方式有哪些?
搞清楚类对象和实例对象,但都是对象。
第一种:通过类对象的 getClass() 方法获取,细心点的都知道,这个 getClass 是 Object 类里面的方法。
User user=new User();//clazz就是一个User的类对象Class<?> clazz=user.getClass();
运行项目并下载源码
第二种:通过类的静态成员表示,每个类都有隐含的静态成员 class。
//clazz就是一个User的类对象Class<?> clazz=User.class;
运行项目并下载源码
第三种:通过 Class 类的静态方法 forName() 方法获取。
Class<?> clazz = Class.forName("com.tian.User");运行项目并下载源码
面试扩散
可能面试官会问相关的题目,比如:
Class.forName 和 ClassLoader.loadClass 的区别是什么?
参考:
反射中 Class.forName() 和 ClassLoader.loadClass() 的区别
4. ArrayList 和 LinkedList 的区别有哪些?
ArrayList
- 优点:ArrayList 是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的)。
- 缺点:因为地址连续,ArrayList 要移动数据,所以插入和删除操作效率比较低。
LinkedList
- 优点:LinkedList 基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等一个连续的地址。对于新增和删除操作,LinkedList 比较占优势。LinkedList 适用于要头尾操作或插入指定位置的场景。
- 缺点:因为 LinkedList 要移动指针,所以查询操作性能比较低。
适用场景分析
- 当需要对数据进行对随机访问的时候,选用 ArrayList。
- 当需要对数据进行多次增加删除修改时,采用 LinkedList。
如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用 ArrayList。
当然,绝大数业务的场景下,使用 ArrayList 就够了,但需要注意避免 ArrayList 的扩容,以及非顺序的插入。
5. 用过 ArrayList 吗?说一下它有什么特点?
只要是搞 Java 的肯定都会回答“用过”。所以,回答题目的后半部分——ArrayList 的特点。可以从这几个方面去回答:
Java 集合框架中的一种存放相同类型的元素数据,是一种变长的集合类,基于定长数组实现,当加入数据达到一定程度后,会实行自动扩容,即扩大数组大小。
底层是使用数组实现,添加元素。
- 如果 add(o),添加到的是数组的尾部,如果要增加的数据量很大,应该使用 ensureCapacity() 方法,该方法的作用是预先设置 ArrayList 的大小,这样可以大大提高初始化速度。
- 如果使用 add(int,o),添加到某个位置,那么可能会挪动大量的数组元素,并且可能会触发扩容机制。
高并发的情况下,线程不安全。多个线程同时操作 ArrayList,会引发不可预知的异常或错误。
ArrayList 实现了 Cloneable 接口,标识着它可以被复制。注意:ArrayList 里面的 clone() 复制其实是浅复制。
6. 有数组了为什么还要搞个 ArrayList 呢?
通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不知道需要初始化数组大小为多少,而 ArrayList 可以使用默认的大小,当元素个数到达一定程度后,会自动扩容。
可以这么来理解:我们常说的数组是定死的数组,ArrayList 却是动态数组。
7. 说说什么是 fail-fast?
fail-fast 机制是 Java 集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生 fail-fast 事件。
例如:当某一个线程 A 通过 iterator 去遍历某集合的过程中,若该集合的内容被其他线程所改变了,那么线程 A 访问集合时,就会抛出
ConcurrentModificationException 异常,产生 fail-fast 事件。这里的操作主要是指 add、remove 和 clear,对集合元素个数进行修改。
解决办法:建议使用“java.util.concurrent 包下的类”去取代“java.util 包下的类”。
可以这么理解:在遍历之前,把 modCount 记下来 expectModCount,后面 expectModCount 去和 modCount 进行比较,如果不相等了,证明已并发了,被修改了,于是抛出
ConcurrentModificationException 异常。
8. Hashtable 与 HashMap 的区别
本来不想这么写标题的,但是无奈,面试官都喜欢这么问 HashMap。
- 出生的版本不一样,Hashtable 出生于 Java 发布的第一版本 JDK 1.0,HashMap 出生于 JDK 1.2。
- 都实现了 Map、Cloneable、Serializable(当前 JDK 版本 1.8)。
- HashMap 继承的是 AbstractMap,并且 AbstractMap 也实现了 Map 接口。Hashtable 继承 Dictionary。
- Hashtable 中大部分 public 修饰普通方法都是 synchronized 字段修饰的,是线程安全的,HashMap 是非线程安全的。
- Hashtable 的 key 不能为 null,value 也不能为 null,这个可以从 Hashtable 源码中的 put 方法看到,判断如果 value 为 null 就直接抛出空指针异常,在 put 方法中计算 key 的 hash 值之前并没有判断 key 为 null 的情况,那说明,这时候如果 key 为空,照样会抛出空指针异常。
- HashMap 的 key 和 value 都可以为 null。在计算 hash 值的时候,有判断,如果 key==null,则其 hash=0;至于 value 是否为 null,根本没有判断过。
- Hashtable 直接使用对象的 hash 值。hash 值是 JDK 根据对象的地址或者字符串或者数字算出来的 int 类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时间的,效率很低。HashMap 为了提高计算效率,将哈希表的大小固定为了 2 的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
- Hashtable、HashMap 都使用了 Iterator。而由于历史原因,Hashtable 还使用了 Enumeration 的方式。
- 默认情况下,初始容量不同,Hashtable 的初始长度是 11,之后每次扩充容量变为之前的 2n+1(n 为上一次的长度)而 HashMap 的初始长度为 16,之后每次扩充变为原来的两倍。
另外在 Hashtable 源码注释中有这么一句话:
Hashtable is synchronized. If a thread-safe implementation is not needed, it is recommended to use HashMap in place of Hashtable . If a thread-safe highly-concurrent implementation is desired, then it is recommended to use ConcurrentHashMap in place of Hashtable.运行项目并下载源码
大致意思:Hashtable 是线程安全,推荐使用 HashMap 代替 Hashtable;如果需要线程安全高并发的话,推荐使用 ConcurrentHashMap 代替 Hashtable。
这个回答完了,面试官可能会继续问:HashMap 是线程不安全的,那么在需要线程安全的情况下还要考虑性能,有什么解决方式?
这里最好的选择就是 ConcurrentHashMap 了,但面试官肯定会叫你继续说一下 ConcurrentHashMap 数据结构以及底层原理等。
9. HashMap 中的 key 我们可以使用任何类作为 key 吗?
平时可能大家使用的最多的就是使用 String 作为 HashMap 的 key,但是现在我们想使用某个自定义类作为 HashMap 的 key,那就需要注意以下几点:
- 如果类重写了 equals 方法,它也应该重写 hashCode 方法。
- 类的所有实例需要遵循与 equals 和 hashCode 相关的规则。
- 如果一个类没有使用 equals,你不应该在 hashCode 中使用它。
- 咱们自定义 key 类的最佳实践是使之为不可变的,这样,hashCode 值可以被缓存起来,拥有更好的性能。不可变的类也可以确保 hashCode 和 equals 在未来不会改变,这样就会解决与可变相关的问题了。
10. HashMap 的长度为什么是 2 的 N 次方呢?
为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数据能均匀的分配,每个链表或者红黑树长度尽量相等。
我们首先可能会想到 % 取模的操作来实现。
下面是回答的重点哟:
取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作(也就是说 hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方)。并且,采用二进制位操作 &,相对于 % 能够提高运算效率。
这就是为什么 HashMap 的长度需要 2 的 N 次方了。
11. HashMap 与 ConcurrentHashMap 的异同
- 都是 key-value 形式的存储数据;
- HashMap 是线程不安全的,ConcurrentHashMap 是 JUC 下的线程安全的;
- HashMap 底层数据结构是数组 + 链表(JDK 1.8 之前)。JDK 1.8 之后是数组 + 链表 + 红黑树。当链表中元素个数达到 8 的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红黑树查询速度快;
- HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 * 数组大小的方式进行扩容;
- ConcurrentHashMap 在 JDK 1.8 之前是采用分段锁来现实的 Segment + HashEntry,Segment 数组大小默认是 16,2 的 n 次方;JDK 1.8 之后,采用 Node + CAS + Synchronized 来保证并发安全进行实现。
13. 红黑树有哪几个特征?
紧接上个问题,面试官很有可能会问红黑树,下面把红黑树的几个特征列出来:
如果面试官还要继续问红黑树具体是怎么添加节点和删除节点的,推荐看:
30 张图带你彻底理解红黑树
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
14. 说说你平时是怎么处理 Java 异常的
try-catch-finally
- try 块负责监控可能出现异常的代码
- catch 块负责捕获可能出现的异常,并进行处理
- finally 块负责清理各种资源,不管是否出现异常都会执行
- 其中 try 块是必须的,catch 和 finally 至少存在一个标准异常处理流程
抛出异常→捕获异常→捕获成功(当 catch 的异常类型与抛出的异常类型匹配时,捕获成功)→异常被处理,程序继续运行 抛出异常→捕获异常→捕获失败(当 catch 的异常类型与抛出异常类型不匹配时,捕获失败)→异常未被处理,程序中断运行
在开发过程中会使用到自定义异常,在通常情况下,程序很少会自己抛出异常,因为异常的类名通常也包含了该异常的有用信息,所以在选择抛出异常的时候,应该选择合适的异常类,从而可以明确地描述该异常情况,所以这时候往往都是自定义异常。
自定义异常通常是通过继承 java.lang.Exception 类,如果想自定义 Runtime 异常的话,可以继承
java.lang.RuntimeException 类,实现一个无参构造和一个带字符串参数的有参构造方法。
在业务代码里,可以针对性的使用自定义异常。比如说:该用户不具备某某权限、余额不足等。
15. finally 模块执行了吗?是先执行 return 还是先执行 finally 模块?返回什么?
public class FinallyDemo {public String method111() {String ret = "hello";try {return ret;} finally {ret = "world";}}}
运行项目并下载源码
把 FinallyDemo.java 编译成 class 文件后,找到该 class 文件的当前目录,执行 cmd 命令:
javap -verbose FinallyDemo.class >>test.txt
运行项目并下载源码
然后打开 test.txt,关键部分内容如下:
发现在字节码指令中,将 hello 保存在本地变量 2 中,然后直到把本地变量 2 加载到操作数栈中,然后就直接出栈,return 回去了,所以本题的返回去的是 hello,但是 finally 代码块也执行了,执行完 finally 模块后再返回一个临时变量 2。
二、JVM 篇
16. Java 类加载器有几种?
17. 说一下有哪些类加载场景?
18. 说说 Java 类加载机制是什么?说说 new 创建一个普通对象的过程?
类加载的过程包括了:
加载、验证、准备、解析、初始化。
new 创建一个普通对象的过程如下:
- 检测类是否被加载过
- 为对象分配内存
- 为分配的内存空间初始化为 0
- 对对象进行其他相关设置
- 执行 init 方法
下面用一张图来描述:
19. 说说类的生命周期?
注意类生命周期和对象声明周期,类生命周期主要有以下几个阶段:
20. 什么是双亲委派模型?
21. 如何破坏双亲委派模型?
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
22. 能不能自己也写一个 java.lang.String 类?
可以写,能编译,但是不能 run。禁止使用包名:java.开头的包名。
定义一个普通类:
package java.lang;public class MyTest {public MyTest() {}public MyTest(String str, int a) {}public int length(){return 10;}public static void main(String[] args) {MyTest myTest =new MyTest("lang",1);myTest.length();}}
运行项目并下载源码
运行:
具体校验的源码地方:
结论就是定义包目录的时候,不能以 java. 开头。
23. 说一下 JVM 运行时数据区有哪些?分别说一下它们的功能
此题我想用我的方法说,不像网上一堆一堆抄书上的,希望能对大家有所帮助,如果没多大帮助,那可以网上找个看看,只能说抱歉了。
下面我们直入主题:
- 每个线程单独拥有的:程序计数器、Java 虚拟机栈、本地方法栈
- 所有线程都共同有的:方法区、堆
如果你在 Java 代码里创建一个线程,相应 JVM 虚拟机中就创建与之对应的程序计数器、Java 虚拟机栈、本地方法栈,同时方法区和堆是在虚拟机启动就已经有了。
程序计数器
可以简单理解为:程序计数器是记录执行到你代码的的第几行了,每个线程各自对应自己的程序计数器。
Java 虚拟机栈
虚拟机会为每个线程分配一个虚拟机栈,每个虚拟机栈中都有若干个栈帧,每个栈帧中存储了局部变量表、操作数栈、动态链接、返回地址等。一个栈帧就对应 Java 代码中的一个方法,当线程执行到一个方法时,就代表这个方法对应的栈帧已经进入虚拟机栈并且处于栈顶的位置,每一个 Java 方法从被调用到执行结束,就对应了一个栈帧从入栈到出栈的过程。一个线程的生命周期和与之对应的 Java 虚拟机栈的生命周期相同。
一个线程进来就创建虚拟机栈,该线程调用的方法就是栈帧,进入方法,栈帧就入栈(虚拟机栈),出方法就是出虚拟机栈。可以通过下面两张图进行理解:
本地方法栈
和 Java 虚拟机栈类似,Java 虚拟机栈针对的是 Java 方法,而本地方法栈针对的 native 修饰的方法。
堆
JVM 几乎所有的对象的内存分配都在堆里。由于对象是有生命周期的,所以把堆又分成了新生代和老年代。
新生代和老年代大小比例 = 1:2(默认)。新生代又分为 Eden、S0、S1 区域,Ede:S0:S1=8:1:1。
大多数对象在 Eden 区出生和死亡。Eden 区存活下来的对象进入 S0 区,S0 区活下来的对象放到 S1,S1 区活下来的对象放到 S0 区,这过程中 S0 和 S1 至少有一个区域是空着的。并且对象每次倒腾一次自己的年龄就加 1,直达加到 15 岁的时候,就直接入老年代了。有的大对象可以直接进入老年代,条件是把该对象的大小以及达到了能直接进入老年代的条件了(阈值可以设置)。
方法区
先按照图中的关键字回答。但是方法区由于 JDK 版本有所变动。
回答的时候,一定要说一下方法区由于 JDK 版本有所变动。版本变动情况如下:
24. 方法区和永久代有什么区别?
之前有小伙伴也问过我,方法区和永久代到底是什么区别?
这么说吧:永久代又叫 Perm 区,只存在于 HotSpot JVM 中,并且只存在于 JDK 1.7 和之前的版本中,JDK 1.8 中已经彻底移除了永久代,JDK 1.8 中引入了一个新的内存区域叫 metaspace。
- 并不是所有的 JVM 中都有永久代,IBM 的 9,Oracle 的 JRocket 都没有永久代。
- 永久代是实现层面的东西。
- 永久代里面存的东西基本上就是方法区规定的那些东西。
因此,我们可以说,在 JDK 1.7 中永久代是方法区的一种实现,当然,在 HotSpot JDK 1.8 中 metaspace 可以看成是方法区的一种实现。
24. JVM 运行时数据区哪些地方会产生内存溢出?
简单回答就行:
Java 虚拟机栈、本地方法栈、Java 堆、方法区,其实就是除了程序计数器以外的几个都会发生 OOM。
建议把阈值对应的几个区也简要的说一下:
25. 为什么要用 metaspace 替换 permspace 呢?
在 JDK 1.8 之前的 HotSpot 实现中,类的元数据如方法数据、方法信息(字节码、栈和变量大小)、运行时常量池、已确定的符号引用和虚方法表等被保存在永久代中,32 位默认永久代的大小为 64M,64 位默认为 85M,可以通过参数 -XX:MaxPermSize 进行设置,一旦类的元数据超过了永久代大小,就会抛出 OOM 异常。
虚拟机团队在 JDK 1.8 的 HotSpot 中,把永久代从 Java 堆中移除了,并把类的元数据直接保存在本地内存区域(堆外内存),称之为元空间 metaspace(本地内存)。即就是说 JDK 1.8 之前,永久代是在虚拟机中的,而 JDK 1.8 引入的元空间是系统的内存的一部分。理论上取决于 32 位/64 位系统可虚拟的内存大小,可见也不是无限制的,需要配置参数。
另外一方面,咱们在对永久代进行调优的时候是相当费劲,因为永久代的大小不好控制,涉及到很多因素,比如:类的总数、常量池的大小、方法数量等,最无语的是永久代的内存不够了可能会伴随着一次 Full GC。
下面是 JDK 几个版本中方法区和堆存储的信息的关系:
总结
- 字符串存在永久代中,容易出现性能问题和内存溢出。
- 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
- 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
- 移除永久代是为融合 HotSpot JVM 与 JRockit VM 而做出的努力,因为 JRockit 没有永久代,不需要配置永久代。
26. 熟悉哪些 JVM 调优参数?
整个堆内存大小
-Xms(初始堆大小)、-Xmx(最大堆大小),为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值。
新生代空间大小
NewRadio:年轻代和年老代将根据默认的比例(1:2)分配堆内存,建议设置为 2 到 4,可以通过调整二者之间的比率 NewRadio 来调整二者之间的大小。也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize 来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把 -XX:newSize -XX:MaxNewSize 设置一样大小。
方法区(元空间)
JDK 1.8:-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m,根据实际情况调整, 可以使用命令 jstat -gcutil pid 查看当前使用率,M 对应的列,根据使用率来定制一个具体的值,建议两个值设置成同样大小。
JDK 1.7:-XX:MaxPermSize=256m -XX:MaxPermSize=256m 永久带。
GC 日志
-Xloggc:$CATALINA_BASE/logs/gc.log-XX:+PrintGCDetails-XX:+PrintGCDateStamps
运行项目并下载源码
记录 GC 日志并不会特别地影响 Java 程序性能,推荐你尽可能记录日志。
GC 算法
-XX:+UseParNewGC-XX:+CMSParallelRemarkEnabled-XX:+UseConcMarkSweepGC-XX:CMSInitiatingOccupancyFraction=75
运行项目并下载源码
一般来说推荐使用这些配置,但是根据程序不同的特性,其他的也有可能更好。
任何一个 JVM 参数的默认值可以通过
java -XX:+PrintFlagsFinal -version |grep JVMParamName
运行项目并下载源码
获取,例如:
java -XX:+PrintFlagsFinal -version |grep MetaspaceSize
运行项目并下载源码
27. Java 对象的引用类型有哪些?
对象引用类型有四类:强引用、软引用、弱引用、虚引用。
28. JVM 垃圾回收算法有哪些?
垃圾回收算法共四种:其实我更愿意说成三种,因为分代回收其实不是算法。
29. 垃圾收集器有哪些?
目前常见的有如下几种:
Serial 收集器
ParNew 收集器
Parallel scavenge 收集器
Serial Old 收集器
CMS=Concurrent Mark Sweep 收集器
Parallel Old 收集器
G1=Garbage-First 收集器
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案【点击此处即可/免费获取】
https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho
垃圾收集器整合
- G1 是新生代和老年代一起搞,不和别人合伙。
- Serial:CMS 或者 Serial Old
- ParNew:CMS 或者 Serial Old
- Parallel Scavenge:Parallel Old 或者 Serial Old
分代收集器对应
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:Serial Old、Parallel Old、CMS
- 整堆收集器:G1
30. 说说 JVM 中内存的分配与回收策略
三、Dubbo 篇
其实关于 Dubbo 的面试题,我觉得最好的文档应该还是官网,因为官网有中文版,照顾了很多阅读英文文档吃力的小伙伴。但是官网内容挺多的,于是这里就结合官网和平时面试被问的相对较多的题目整理了一下。
31. 说说一次 Dubbo 服务请求流程?
基本工作流程:
上图中角色说明:
32. 说说 Dubbo 工作原理
工作原理分 10 层:
- 第一层:service 层,接口层,给服务提供者和消费者来实现的(留给开发人员来实现);
- 第二层:config 层,配置层,主要是对 Dubbo 进行各种配置的,Dubbo 相关配置;
- 第三层:proxy 层,服务代理层,透明生成客户端的 stub 和服务单的 skeleton,调用的是接口,实现类没有,所以得生成代理,代理之间再进行网络通讯、负责均衡等;
- 第四层:registry 层,服务注册层,负责服务的注册与发现;
- 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务;
- 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控;
- 第七层:protocol 层,远程调用层,封装 rpc 调用;
- 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步;
- 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口;
- 第十层:serialize 层,数据序列化层。
这是个很坑爹的面试题,但是很多面试官又喜欢问,你真的要背么?你能背那还是不错的,我建议不要背,你就想想 Dubbo 服务调用过程中应该会涉及到哪些技术,把这些技术串起来就 OK 了。
面试扩散
如果让你设计一个 RPC 框架,你会怎么做?其实你就把上面这个工作原理中涉及的到技术点总结一下就行了。
33. Dubbo 支持哪些协议?
还有三种,混个眼熟就行:Memcached 协议、Redis 协议、Rest 协议。
上图基本上把序列化的方式也罗列出来了。
详细请参考:Dubbo 官网。
34. 注册中心挂了,consumer 还能不能调用 provider?
可以。因为刚开始初始化的时候,consumer 会将需要的所有提供者的地址等信息拉取到本地缓存,所以注册中心挂了可以继续通信。但是 provider 挂了,那就没法调用了。
关键字:consumer 本地缓存服务列表。
35. 怎么实现动态感知服务下线的呢?
服务订阅通常有 pull 和 push 两种方式:
- pull 模式需要客户端定时向注册中心拉取配置;
- push 模式采用注册中心主动推送数据给客户端。
Dubbo ZooKeeper 注册中心采用是事件通知与客户端拉取方式。服务第一次订阅的时候将会拉取对应目录下全量数据,然后在订阅的节点注册一个 watcher。一旦目录节点下发生任何数据变化,ZooKeeper 将会通过 watcher 通知客户端。客户端接到通知,将会重新拉取该目录下全量数据,并重新注册 watcher。利用这个模式,Dubbo 服务就可以做到服务的动态发现。
注意:ZooKeeper 提供了“心跳检测”功能,它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其剔除。
36. Dubbo 负载均衡策略?
- 随机(默认):随机来
- 轮训:一个一个来
- 活跃度:机器活跃度来负载
- 一致性 hash:落到同一台机器上
37. Dubbo 容错策略
failover cluster 模式
provider 宕机重试以后,请求会分到其他的 provider 上,默认两次,可以手动设置重试次数,建议把写操作重试次数设置成 0。
failback 模式
失败自动恢复会在调用失败后,返回一个空结果给服务消费者。并通过定时任务对失败的调用进行重试,适合执行消息通知等操作。
failfast cluster 模式
快速失败只会进行一次调用,失败后立即抛出异常。适用于幂等操作、写操作,类似于 failover cluster 模式中重试次数设置为 0 的情况。
failsafe cluster 模式
失败安全是指,当调用过程中出现异常时,仅会打印异常,而不会抛出异常。适用于写入审计日志等操作。
forking cluster 模式
并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
broadcacst cluster 模式
广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。
38. Dubbo 动态代理策略有哪些?
默认使用 javassist 动态字节码生成,创建代理类,但是可以通过 SPI 扩展机制配置自己的动态代理策略。
39. 说说 Dubbo 与 Spring Cloud 的区别?
这是很多面试官喜欢问的问题,本人认为其实他们没什么关联之处,但是硬是要问区别,那就说说吧。
回答的时候主要围绕着四个关键点来说:通信方式、注册中心、监控、断路器,其余像 Spring 分布式配置、服务网关肯定得知道。
通信方式
Dubbo 使用的是 RPC 通信;Spring Cloud 使用的是 HTTP RestFul 方式。
注册中心
Dubbo 使用 ZooKeeper(官方推荐),还有 Redis、Multicast、Simple 注册中心,但不推荐。;
Spring Cloud 使用的是 Spring Cloud Netflix Eureka。
监控
Dubbo 使用的是 Dubbo-monitor;Spring Cloud 使用的是 Spring Boot admin。
断路器
Dubbo 在断路器这方面还不完善,Spring Cloud 使用的是 Spring Cloud Netflix Hystrix。
分布式配置、网关服务、服务跟踪、消息总线、批量任务等。
Dubbo 目前可以说还是空白,而 Spring Cloud 都有相应的组件来支撑。
40. 说说 TCP 与 UDP 的区别,以及各自的优缺点
41. 说一下 HTTP 和 HTTPS 的区别
- 端口不同:HTTP 和 HTTPS 的连接方式不同没用的端口也不一样,HTTP 是 80,HTTPS 用的是 443;
- 消耗资源:和 HTTP 相比,HTTPS 通信会因为加解密的处理消耗更多的 CPU 和内存资源;
- 开销:HTTPS 通信需要证书,这类证书通常需要向认证机构申请或者付费购买。
42. 说说 HTTP、TCP、Socket 的关系是什么?
- TCP/IP 代表传输控制协议/网际协议,指的是一系列协议族。
- HTTP 本身就是一个协议,是从 Web 服务器传输超文本到本地浏览器的传送协议。
- Socket 是 TCP/IP 网络的 API,其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面。对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议。
综上所述:
- 需要 IP 协议来连接网络。
- TCP 是一种允许我们安全传输数据的机制,使用 TCP 协议来传输数据的 HTTP 是 Web 服务器和客户端使用的特殊协议。
- HTTP 基于 TCP 协议,所以可以使用 Socket 去建立一个 TCP 连接。
43. 说一下 HTTP 的长连接与短连接的区别
HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。
短连接
在 HTTP/1.0 中默认使用短链接,也就是说,浏览器和服务器每进行一次 HTTP 操作,就建立一次连接,但任务结束就中断连接。如果客户端访问的某个 HTML 或其他类型的 Web 资源,如 JavaScript 文件、图像文件、CSS 文件等。当浏览器每遇到这样一个 Web 资源,就会建立一个 HTTP 会话。
长连接
从 HTTP/1.1 起,默认使用长连接,用以保持连接特性。在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭。如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如 Apache)中设定这个时间。
四、MyBatis 篇
44. 说说 MyBatis 的缓存
一级缓存
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的 SQL,MyBatis 提供了一级缓存的方案优化这部分场景,如果是相同的 SQL 语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。
每个 SqlSession 中持有了 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时,MyBatis 根据当前执行的语句生成 MappedStatement,在 Local Cache 进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入 Local Cache,最后返回结果给用户。具体实现类的类关系图如下图所示:
- MyBatis 一级缓存的生命周期和 SqlSession 一致。
- MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺。
- MyBatis 的一级缓存最大范围是 SqlSession 内部,有多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为 Statement。
二级缓存
在上文中提到的一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示。
二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,即二级缓存被多个 SqlSession 共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程为:
二级缓存 -> 一级缓存 -> 数据库
- MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时粒度更加细,能够到 namespace 级别,通过 Cache 接口实现类不同的组合,对 Cache 的可控性也更强。
- MyBatis 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
- 在分布式环境下,由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将 MyBatis 的 Cache 接口实现,有一定的开发成本,直接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。
45. JDBC 编程有哪些步骤?
1. 装载相应的数据库的 JDBC 驱动并进行初始化:
Class.forName("com.mysql.jdbc.Driver");运行项目并下载源码
2. 建立 JDBC 和数据库之间的 Connection 连接:
Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8", "root", "123456");运行项目并下载源码
3. 创建 Statement 或者 PreparedStatement 接口,执行 SQL 语句:
//查询用户信息public List<User> findUserList(){String sql = "select * from t_user order by user_id";Connection conn = null;PreparedStatement pstmt = null;ResultSet rs = null;//创建一个List用于存放查询到的User对象List<User> userList = new ArrayList<>();try {conn = DbUtil.getConnection();pstmt =(PreparedStatement) conn.prepareStatement(sql);rs =(ResultSet) pstmt.executeQuery();while(rs.next()){int courseId = rs.getInt("user_id");String courseName = rs.getString("user_name");//每个记录对应一个User对象User user = new User();user.setUserId(courseId);user.setUserName(courseName);//将对象放到集合中userList.add(course);}} catch(SQLException e) {e.printStackTrace();}finally{//资源关闭DbUtil.close(pstmt);DbUtil.close(conn);}return userList;}
运行项目并下载源码
4. 处理和显示结果。
5. 释放资源。