常见面试题及解答
1Java 基础
1、HashMap的源码,实现原理,JDK8中对HashMap做了怎样的优化。
1) HashMap实现Map接口,元素以键值对的方式存储,并且允许使用null 作为key和value,因为HashMap的key不允许重复,所以只能有一个键是 null,并不推荐使用,因为容易出问题并且很难排查。
2)HashMap不能保证放入元素的顺序,所以是无序的,HashMap初始长度16,每次扩充翻倍。
JDK1.7的HashMap是使用数组加链表进行存储,每一个键值对用一个Entry来存储,Entry中还有hash和next属性,hash是key的hash值,next则是指向下一个Entry的指针。HashMap的初始容量是16,即有一个长度为16的数组,用来存储每个Entry链表的头结点。每一个Entry存储的位置是由key.hashCode()%len获得,假设有A,B,C三个键值对并且每个key的hash值对16取模都是1,A先进来就存储在Entry[1],再进来B则B.next=A、Entry[1]=B。C相同。也就是说数组中存储的是最后插入的元素。所以说放入元素是无序的。
JDK1.8的HashMap加入了红黑树,当每个单链表的长度大于阈值(8)时,则将链表转化为红黑树。并且1.8之后,新插入的元素都放在了链表的尾部。
3) HashMap是线程不安全的,可以使用Collections.synchronizedMap()获 取一个线程安全的集合,Collections.synchronizedMap()实际上是定义一个synchronizedMap的内部类,这个内部类实现了Map接口并在方法上加上synchronized关键字。就是操作的HashMap的时候自动添synchronized。
2、HaspMap扩容是怎样扩容的,为什么都是2的N次幂的大小。
1) 如果超过阈值,会自动扩充两倍,然后重新计算HashMap中各个Entry的位置。
2) 都是2的N次幂是因为在新插入操作的时候,位置计算是由(n-1)&hash决定的,(n-1)&hash实际上相当于hash%(n-1),只不过&运算更快速。HashMap中的hash=((h = key.hashCode()) ^ (h >>> 16),hashCode是9位十进制也就是36位二进制。右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。这样大多数的hashCode的分布已经很不错了。
3、HashMap,HashTable,ConcurrentHashMap的区别。
HashMap的key和value都可以为空,线程不安全,初始size16,每次扩容翻倍,先插入后扩容,插入后长度达到总数的75%则扩容,计算index(n-1)&hash,迭代器是fail-fast的(在遍历一个集合时,当集合结构被修改,会抛出Concurrent Modification Exception)
HashTable的key和value都不能为空,线程不安全,初始size11,每次扩容size=oldSize*2+1,计算index方法 (hash & 0x7FFFFFFF) % tab.length,迭代器不是fail-fast
ConcurrentHashMap线程安全,分段加锁,段内扩容。需锁定整个map时按顺序锁定各个分段,然后按顺序释放锁。
4、极高并发下HashTable和ConcurrentHashMap哪个性能更好,为什么,如何实现的。
ConcurrentHashMap更好,分段锁,HashTable锁整个table。
5、HashMap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么。
1)put是会见检查Map容量是否足够,不够则扩充,扩充后会把数组从老的hash表移到新的hash表,多个线程同时同时操作可能会形成循环链表,所以get()时会出现无限循环
2)造成迭代的fail-fast
6、java中四种修饰符的限制范围。
7、Object类中的方法。
hash,finalize,notify,notifyAll,wait....
8、接口和抽象类的区别,注意JDK8的接口可以有实现。
(1)抽象类可以有构造方法,接口中不能有构造方法。
(2)抽象类中可以有普通成员变量,接口中没有普通成员变量
(3)抽象类中可以包含静态方法,接口中不能包含静态方法
(4) 一个类可以实现多个接口,但只能继承一个抽象类。
(5)接口可以被多重实现,抽象类只能被单一继承
(6)如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法
9、动态代理的两种方式,以及区别。
(1)JDK代理使用的是反射机制实现aop的动态代理,CGLIB代理使用字节码处理框架asm,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,cglib创建效率较低,执行效率高;
(2)JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLIB则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口
10、Java序列化的方式。
11、传值和传引用的区别,Java是怎么样的,有没有传值引用。
12、一个ArrayList在循环过程中删除,会不会出问题,为什么。
会遗漏元素,例如删除了a[2],删除后a[3]就到了a[2]的位置上,继续遍历是从a[3]开始,那么如果之前的a[3]符合删除条件也不会被删除。反向遍历删除是可以的,用迭代器删除单线程可以多线程有可能触发fail-fast。
2JVM
1、JVM的内存结构。
程序计数器:当前线程所执行的字节码的行号指示器,是一块较小的内存空间
Java虚拟机栈:线程私有的,生命周期同线程相同。虚拟机栈描述的是java方法执行的内存模型--每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈,动态链接、方法出口等信息。
本地方法栈:与虚拟机栈类似,只是本地方法栈只服务于Native方法。
java堆:线程共享,占用内存最大,GC的主要区域。所有的对象实例都要在这里分配内存
方法区:线程共享,Java虚拟机规范把方法区描述为堆的的一个逻辑部分,但别名是非堆,与堆区分。方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译 后的代码等数据。
2、JVM方法栈的工作过程,方法栈和本地方法栈有什么区别。
见1
3、JVM的栈中引用如何和堆中的对象产生关联。
对象的实例保存在堆上,对象的元数据(instantKlass)保存在方法区,对象的引用保存在栈上。
4、可以了解一下逃逸分析技术。
5、GC的常见算法,CMS以及G1的垃圾回收过程,CMS的各个阶段哪两个是Stop the world的,CMS会不会产生碎片,G1的优势。
(1)判断对象已死:可达性分析算法,从root对象开始到当前对象没有任何调用链,则证明当前对象不可用。不可用不一定“非死不可”,需要至少经历两次标记过程,然后才会被虚拟机自动建立的、低优先级的Finalizer线程去执行(只是执行并不等待执行结束)。
(2)清除算法:
标记-清除算法:标记所有需要回收的对象,统一回收。效率不高并且会产生大量不连续的内存碎片
复制算法:将内存分为两块,每次只使用一块,将当前使用块中的存活对象复制到另一块中,然后在把当前块清理掉。可用内存缩小严重
标记-整理算法:标记存活的对象都向一端移动,然后清理掉端边界以外的内存。
(3)堆一般分为年轻代和老年代:
年轻代一般采用复制算法,有一个eden区和两个survior区(From和To),大小比例通常是8:1(年轻代中80%的对象都是朝生夕死)。每次只使eden区和一个survior(From)区。每个GC周 期都会将eden区的所有存活对象复制到To中,而From中仍然存活的并且年龄未到达阈值的对象会被复制到To中,超龄的复制到老年代。这时eden和From是空的,From就会转换成“To”,To"则当做“From”。知道To中区域满了则会移动所有存活对象到老年代。
(4)垃圾收集器
CMS和G1
6、标记清除和标记整理算法的理解以及优缺点。
见上
7、eden survivor区的比例,为什么是这个比例,eden survivor的工作过程。
见上
8、JVM如何判断一个对象是否该被GC,可以视为root的都有哪几种类型。
被启动类(bootstrap 加载器)加载的类和创建的对象;
JavaStack 中的引用的对象 (栈内存中引用的对象);
方法区中静态引用指向的对象;
方法区中常量引用指向的对象;
Native 方法中 JNI 引用的对象。
9、强软弱虚引用的区别以及GC对他们执行怎样的操作。
不想打字了,见《深入理解java虚拟机》第2版第65页3.2.3再谈引用
10、Java是否可以GC直接内存。
直接内存其实是jvm自定义的空间,直接内存本身不受gc的影响,但是由于有对象在堆引用这这块内存,那么受到gc的间接影响,典型的是java的代码里有system.gc去回收。
堆外内存不同于直接内存,堆外内存会溢出,并且其垃圾回收依赖于代码显式调用System.gc()
11、Java类加载的过程。
加载-验证-准备-解析-初始化
12、双亲委派模型的过程以及优势。
13、常用的JVM调优参数。
14、dump文件的分析。
15、Java有没有主动触发GC的方式(没有)。
3数据结构与算法
1、B+树
2、快速排序,堆排序,插入排序(八大排序算法)
3、一致性Hash算法,一致性Hash算法的应用
4多线程()
1、Java实现多线程有哪几种方式。
继承Thread或者实现Runnable接口
2、Callable和Future的了解。
Callable是跟Runnable类似的接口。不过Callable接口需要实现call方法,是有返回值的并且可以抛出异常。而实现Runnable接口需要实现run方法是没有返回值的并无法抛出异常。
Future是控制Callable的运行过程,参数是Callable的一个实例。
3、线程池的参数有哪些,在线程池创建一个线程的过程。
核心线程数,最大线程数,空闲时间,时间单位,阻塞队列,拒绝机制
4、volitile关键字的作用,原理。
(1)Lock前缀的指令会引起处理器缓存写回内存;
(2)一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
(3)当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值
5、synchronized关键字的用法,优缺点。
6、Lock接口有哪些实现类,使用场景是什么。
7、可重入锁的用处及实现原理,写时复制的过程,读写锁,分段锁(ConcurrentHashMap中的segment)。
8、悲观锁,乐观锁,优缺点,CAS有什么缺陷,该如何解决。
9、ABC三个线程如何保证顺序执行。
10、线程的状态都有哪些。
11、sleep和wait的区别。
12、notify和notifyall的区别。
13、ThreadLocal的了解,实现原理。
5分布式
1、分布式事务的控制。分布式锁如何设计。
2、分布式session如何设计。
3、dubbo的组件有哪些,各有什么作用。
4、zookeeper的负载均衡算法有哪些。
5、dubbo是如何利用接口就可以通信的。
6框架相关
1、SpringMVC的Controller是如何将参数和前端传来的数据一一对应的。
2、Mybatis如何找到指定的Mapper的,如何完成查询的。
3、Quartz是如何完成定时任务的。自定义注解的实现。
4、Spring使用了哪些设计模式。Spring的IOC有什么优势。
5、Spring如何维护它拥有的bean。
6、一些较新的东西JDK8的新特性,流的概念及优势,为什么有这种优势。
7、区块链了解如何设计双11交易总额面板,要做到高并发高可用