经历了两周的面试,终于收到了几个满意的offer。换工作的过程是痛苦的,除了一天马不停蹄地跑好几家公司面试,剩下的时间基本就是背面试题了。想找到一份适合自己的面试题并不简单,比如我找的是高级Java开发的职位。出于之前公司系统架构的设计,需要准备Java、spring、springboot、mysql、mybatis、mycat、zookeeper、dubbo、kafka、redis、网络等面试题。我结合之前面试的20多家公司,以及从CSDN/简书/掘金/公众号等相关渠道搜集到的面试题,从中整理出一些高频的面试题库,帮助大家更加省力从容的应付面试。更全的整理题库,可以通过搜索微信小程序【Java职场范儿】或扫描最下方小程序二维码。轻轻松松地刷题。
Java面试题
反射篇
一、Java获取Class文件的几种方式?
//1.通过Object类中的getClass()方法的。
public static void getClassObject_1() {
Person p = new Person();
Class clazz = p.getClass();
}
//2.任何数据类型都具备一个静态的属性.class来获取其对应的Class对象
public static void getClassObject_1() {
Class clazz = Person.class;
}
//3.只要通过给定的类的字符串名称就可以获取该类
public static void getClassObject_1() {
String className = "Person";
Class clazz = Class.forName(className);
}
二、如何获取Class中的私有方法?
Method method = e.getClass().getDeclaredMethod("私有方法名",String.class);
集合+并发篇
一、 HashMap和ConcurrentHashMap的原理(并发必问)
HashMap:
1.7版本,使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode相同,或者hashcode取模后的结果相同(hash collision),那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。在hashcode特别差的情况下,比方说所有key的hashcode都相同,这个链表可能会很长,那么put/get操作都可能需要遍历这个链表。也就是说时间复杂度在最差情况下会退化到O(n)
1.8版本,使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构,如果插入的key的hashcode相同,那么这些key也会被定位到Node数组的同一个格子里。如果同一个格子里的key不超过8个,使用链表结构存储。如果超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。那么即使hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销。也就是说put/get的操作的时间复杂度最差只有O(log n)。
ConcurrentHashMap:
在多线程环境下,使用HashMap进行put操作时存在丢失数据的情况,为了避免这种bug的隐患,强烈建议使用ConcurrentHashMap代替HashMap。
1.7版本,使用Segment + HashEntry方式进行实现。
1.8版本,1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,结构如下:
更详细的内容请参考《Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析》
二、 常见的线程池及应用场景
- newCacheThreadPool 无大小,适合时间段速度快的
- newFixedThreadPool 有大小,负载比较重的
- newSingleThreadExecutor 单个有序
- newScheduleThreadPool 周期性的任务
三、 阻塞队列
- arrayblockingqueue:有界,不公平的阻塞(可以通过构造器设置成公平的)
- linkedblockingqueue:有界,先进先出
- priorityblockingqueue:按照特定字段排序
- delayqueue:延迟(缓存是否过期,定时调度)
- synchronousqueue:不存储元素,充当传球手
- linkedtransferqueue:×××,如果有消费者等待,就直接传给消费者,否则放入队列
- linkedblockingdeque:双向阻塞
四、 线程池的处理流程
五、Lock与synchronized 的区别
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。JVM
一、 JVM内存分哪几个区,每个区的作用是什么
Java虚拟机主要分为以下一个区:
- 方法区:
- 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的卸载
- 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。
- 该区域是被线程共享的。
- 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。
- 虚拟机栈:
- 虚拟机栈也就是我们平常所称的栈内存,它为java方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
- 虚拟机栈是线程私有的,它的生命周期与线程相同。
- 局部变量表里存储的是基本数据类型、returnAddress类型(指向一条字节码指令的地址)和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定
- 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式
- 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。
- 本地方法栈
- 本地方法栈和虚拟机栈类似,只不过本地方法栈为Native方法服务。
- 堆
- java堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。
- 程序计数器
- 内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java虚拟机规范没有规定任何OOM情况的区域。
二、java中垃圾收集的方法有哪些?
- 标记-清除:
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记哪些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:1.效率不高,标记和清除的效率都很低;2.会产生大量不连续的内存碎片,导致以后程序在分配较大的对象时,由于没有充足的连续内存而提前触发一次GC动作。 - 复制算法:
为了解决效率问题,复制算法将可用内存按容量划分为相等的两部分,然后每次只使用其中的一块,当一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清楚完第一块内存,再将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一般的内存。
于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大那份内存交Eden区,其余是两块较小的内存区叫Survior区。每次都会优先使用Eden区,若Eden区满,就将对象复制到第二块内存区上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制复制到老年代中。(java堆又分为新生代和老年代) - 标记-整理
该算法主要是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。它的不同之处就是在清除对象的时候现将可回收对象移动到一端,然后清除掉端边界以外的对象,这样就不会产生内存碎片了。 - 分代收集
现在的虚拟机垃圾收集大多采用这种方式,它根据对象的生存周期,将堆分为新生代和老年代。在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记-整理 或者 标记-清除。
三、java内存分配与回收策率以及Minor GC和Major GC
- 对象优先在堆的Eden区分配。
- 大对象直接进入老年代.
- 长期存活的对象将直接进入老年代.
- 当Eden区没有足够的空间进行分配时,虚拟机会执行一次Minor GC.Minor Gc通常发生在新生代的Eden区,在这个区的对象生存期短,往往发生Gc的频率较高,回收速度比较快;Full Gc/Major GC 发生在老年代,一般情况下,触发老年代GC的时候不会触发Minor GC,但是通过配置,可以在Full GC之前进行一次Minor GC这样可以加快老年代的回收速度。
原文地址:http://blog.51cto.com/12133258/2325519