一、集合相关问题(必问):
- List和Set的区别?
是否可放置重复元素 - HashSet是如何保证不重复的
HashSet里面有一个HashMap,值是存在Map的key里面,以HashMap的算法使元素不重复。HashMap放入key时算法为,key的hashcode与其移位后的异或,(h = key.hashCode()) ^ (h >>> 16) - ArrayList、LinkedList、Vector的区别。
数组、链表、线程安全数组 - HashMap是线程安全的吗?为什么不安全?怎么解决?
不安全,多线程put或解决哈希冲突时会出错,Collections.synchronizedMap(map) - ConcurrentHashMap是怎么实现线程安全的?1.8中为什么要用红黑树?
1.7用Segment数组将一个大的table分割成多个小的table来进行加锁保证线程安全
1.8用Node数组+单链表+红黑树的数据结构来实现,红黑树是为了查询效率 - HashMap、LinkedHashMap、ConcurrentHashMap、ArrayList、LinkedList的底层实现。
ArrayList:数组,默认10,扩容乘1.5
LinkedList:双向链表
HashMap:数组和单向链表结合,默认16,因子0.75,扩容1.5倍
LinkedHashMap:数组和双向链表结合
ConcurrentHashMap:Node数组+单链表+红黑树 - HashMap和Hashtable的区别。
线程安全与否,Hashtable继承Dictionary,HashMap继承AbstractMap - HashMap 1.7 与 1.8 的 区别,说明 1.8 做了哪些优化,如何优化的?
1.8Node数组的一个结点链表长度超过8个会转换为红黑树,方便查询 - final、finally、finalize
final可修饰类、变量、方法 - 强引用、软引用、弱引用和虚引用
垃圾回收机制的优先处理,软引用可实现内存缓存。软引用内存不足回收,弱引用下次GC回收。 - Java反射机制
动态、运行时的,根据对象或名称来获取类的相关信息,属性、方法等,可以操作对象 - Arrays.sort实现原理和Collection实现原理
底层都是一个排序算法,根据数组长度采取不同排序
小于47插入排序
大于或等于47或少于286会进入快速排序
大于286进入归并排序 - cloneable接口实现原理?深浅拷贝?
接口为可拷贝标识,默认是浅,深需自己去实现clone方法 - 异常分类及处理机制
Throwable下面有Error和Exception,Exception下有IOException和RunTimeException。
Error属于运行环境错误,比如内存溢出、堆栈溢出
除了Error 和 RuntimeException的其它异常均为检查异常,需显式处理 - wait和sleep的区别
sleep是Thread线程类的方法,而wait是Object顶级类的方法
sleep可以在任何地方使用,而wait只能在同步方法或者同步块中使用
sleep,wait调用后都会暂停当前线程并让出cpu的执行时间,但不同的是sleep不会释放当前持有的对象的锁资源,到时间后会继续执行,而wait会放弃所有锁并需要notify/notifyAll后重新获取到对象锁资源后才能继续执行
sleep需要捕获或者抛出异常,而wait/notify/notifyAll不需要 - 数组在内存中如何分配
堆栈 - hash一致性原理
哈希环,新增和删除节点时自行变动
二、线程相关问题(必问):
- 什么是线程安全?创建线程的3种方式?
数据是否为预期结果,Thread、Runnable、Callable、线程池 - 线程的5种状态
新建、就绪、运行、阻塞、死亡 - 如何检测死锁?怎么预防死锁?
资源互斥、占有和等待、资源不可剥夺、环路等待,jstack工具 - Runnable接口和Callable接口的区别
Runnable可抛异常可返回,获取返回时阻塞主线程 - volatile 的实现原理?
保证可见性,一个有修改,其他都知道。保证有序性,禁止指令重排 - Java 的信号灯?
线程信号量,类似锁 - 怎么实现所有线程在等待某个事件的发生才会去执行?
CountDownLatch、CyclicBarrier - synchronized的实现原理以及锁优化?
对象加锁,同时只能一个线程使用 - synchronizedz在静态方法和普通方法的区别?
静态方法不同对象互斥,普通方法没有 - synchronized和lock有什么区别?
synchronized是在JVM层面上实现的,自动释放;lock在代码层面,必须保证释放,容易死锁
sync必须一直等待,lock可以进行中断 - synchronized、Lock、ReentrantLock、ReadWriteLock
synchronized为java内置实现锁的关键字、Lock为接口
ReentrantLock锁实现,可重入,锁互斥
ReadWriteLock为接口,读锁和写锁,读读共享,写写互斥,读写互斥 - 介绍下CAS(无锁技术)?有什么缺陷?怎么解决?
C++底层代码实现CPU级的原子操作,有ABA问题,用AtomicStampedReference/AtomicMarkableReference工具类处理 - AQS?
抽象队列同步器框架,自带一个FIFO队列和一个状态量,定义两种资源共享方式:独占如ReentrantLock和共享Semaphore/CountDownLatch。其子类主要来实现自己的获取和释放字段的方式。 - 什么是ThreadLocal。用的时候需要注意什么?
用于自身线程的参数传递,不支持并发,本线程内数据共享 - 如何保证多线程下 i++ 结果正确?
CAS、锁、synchronize - Hashtable 是怎么加锁的 ?
方法被synchronized加锁 - 线程池的种类,区别和使用场景?
newCachedThreadPool,可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool,定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool,定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor,创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 - 分析线程池的实现原理和线程的调度过程?
维护一个Runnable队列,省去频繁新建线程操作,直接在某几个线程里循环执行队列方法,也就是在线程的run()里面去循环加载队列里的run() - 线程池如何调优,最大数目如何确认?
机器CPU核数、自己任务的使用场景和时长分析、CPU密集型还是IO密集型、考虑队列满(内存溢出)还是CPU满 - CountDownLatch 和 CyclicBarrier 的用法,以及相互之间的差别?
CountDownLatch | CyclicBarrier |
---|---|
减计数方式 | 加计数方式 |
计算为0时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
计数为0时,无法重置 | 计数达到指定值时,计数置为0重新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可重复利用 |
- LockSupport工具
LockSupport不需要在同步代码块中使用,底层用CAS实现,处理的是线程,wait方法处理的是对象 - Condition接口及其实现原理
AQS内部类,自己有维护信号等待队列,在唤醒时把结点加入AQS的同步队列 - Fork/Join框架的理解
对任务进行拆分,放入维护的双端队列中,同步执行,像是递归线程 - 分段锁的原理,锁力度减小的思考
把对象的数据进行分段维护,每段有自己的锁,段一加锁不影响段二的数据。ConcurrentHashMap类使用 - 八种阻塞队列以及各个阻塞队列的特性
ArrayBlockingQueue:基于数组,写入消费公用一个锁对象
LinkedBlockingQueue:基于链表,有内存泄露危险,可以配置大小来阻塞生产者,写入和消费独立锁可并发
DelayQueue:中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素
PriorityBlockingQueue:基于优先级的阻塞队列,不会阻塞生产者,当为空时阻塞消费者
SynchronousQueue:一种无缓冲的等待队列(不存储元素)
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
LinkedBlockingDeque:一个由链表结构组成的双阻塞队列 - 分布式环境下,怎么保证线程安全。
数据库、缓存、Zookeeper
三、JVM相关问题:
- JVM内存模型?
程序计数器:当前线程指令内存地址,保证线程切换不影响
虚拟机栈:每个线程都有一个栈帧,保存本线程方法数据和过程结果,线程执行完栈帧销毁
本地方法栈:保存本地方法(native)
方法区:用来存储类的信息,例如:方法,方法名,返回值,常量。又被称为永久代,java8取消,相关数据迁移至native和堆还有Metaspace中
堆:存放new出来的对象信息, 全局变量 - JVM堆分区
JVM中共享数据空间可以分成三个大区,新生代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation),其中JVM堆分为新生代和老年代
新生代可以划分为三个区,Eden区(存放新生对象),两个幸存区(From Survivor和To Survivor)(存放每次垃圾回收后存活的对象) - 介绍下垃圾收集机制(在什么时候,对什么,做了什么)。
发生时间:新生代或老年代内存不够时
新生代:Minor GC,复制算法,存活对象存放入Survivor中其中一个,下次回收,再放入另一个Survivor
老年代:Major GC,标记-清除-压缩算法,先遍历标记,再遍历删除,会存在碎片化问题,压缩解决碎片化
判断方法:当对象不被任何引用时则被回收,连通图思路;根据对象的四种引用状态 - 垃圾收集有哪些算法,各自的特点?重点CMS、G1
Serial收集器:复制算法的单线程,必须暂停其他线程的所有工作
ParNew收集器:Serial收集器的多线程版本
Parallel Scavenge收集器:复制算法、多线程,主要考虑吞吐量 - CMS(Conrrurent Mark Sweep)
该收集器是以获取最短回收停顿时间为目标的收集器。使用标记 - 清除算法,收集过程分为如下四步:
(1). 初始标记,标记GCRoots能直接关联到的对象,时间很短。
(2). 并发标记,进行GCRoots Tracing(可达性分析)过程,时间很长。
(3). 重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,时间较长。
(4). 并发清除,回收内存空间,时间很长。
存在碎片化问题,触发FullGc - 7、G1收集器
(1). 并行和并发。使用多个CPU来缩短Stop The World停顿时间,与用户线程并发执行。
(2). 分代收集。独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。
(3). 空间整合。基于标记 - 整理算法,无内存碎片产生。
(4). 可预测的停顿。能简历可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
G1变化较大的是它将整个Java堆划分为多个大小相等的独立区域,分代收集,对于新老对象有不同处理方法,复制算法,没有碎片化问题。 - 什么情况下回出现内存溢出,内存泄漏?
内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出
预防:static妥善运用,弱引用和软引用的使用,方法调用的拆分 - 说说Java线程栈
用jstack命令可查看所有线程的相关信息,可dump出堆栈文件进行分析 - JVM 年轻代到年老代的晋升过程的判断条件是什么呢?
根据设置MaxTenuringThreshold中要求的年龄晋升
在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代 - JVM 出现 fullGC 很频繁,怎么去线上排查问题?
是否有主动调用gc、看配置参数、dump下堆栈文件,一般都是堆里放了大文件引起的,大对象会跳过新生直接到老年代 - JVM垃圾回收机制,何时触发MinorGC等操作
Eden 区不够当前对象存放则触发,触发前会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,还会(需根据配置)检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果不满足则FullGc - JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的
edn空间不足,执行Minor GC,Survivor晋升Old时可能触发Major GC,当Old和Permanent不足时触发FullGc - 类加载的过程?双亲委派模型?为什么要使用双亲委派模型?
BootstrapLoader负责加载系统类(jre/lib/rt.jar),加载JVM基础核心类库,C++语言编写,不存在Java语言中,不可打印
ExtClassLoader负责加载扩展类(java.ext.dirs),父类为BootstrapLoader
AppClassLoader从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器,父类为ExtClassLoader
注意:Launcher E x t C l a s s L o a d e r . c l a s s 与 L a u n c h e r ExtClassLoader.class 与 Launcher ExtClassLoader.class与LauncherAppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系
使用双亲委派模型原因:java类随着其类加载器一起具备了一种带有优先级的层次关系.例如 java.lang.Object,无论哪一个类加载器要加载该类,最终都是委托给处于顶端的启动类加载器,用户若自定义类放在classPath中则也是优先加载自带的类 - 类的实例化顺序
java.exe -> jvm.dll激活虚拟机 -> BootstrapLoader -> ExtClassLoader -> AppClassLoader - OOM错误,stackoverflow错误,permgen space错误
内存溢出:看线程,dump内存,死循环,大对象等
栈溢出:线程方法太多,变量太多
永久代错误:类加载太多 - 能不能自己写一个类叫java.lang.String。
系统器加载器不会加载我们自定义的类,我们自定义的加载器不能加载“java.”开头的包类。原理就是双亲委派模型。