2020年--JAVA面试题汇总

文章目录

基础部分

1.集合继承体系

在这里插入图片描述

2.List与Set区别

相同点
都是可以用来存放任意类型数据的集合
不同点
List集合可以存放:有序可重复数据(存入顺序与取出顺序一致)
Set集合可以存放:无序不可重复数据 (存入顺序与取出顺序不一致),可以通过hash方法和equals方法来进行去重。

3.ArrayList与LinkedList区别

相同点
都是List接口的实现类,可以存放任意类型的数据
不同点
ArrayList是基于数组实现的,数据具有索引值可以直接定位到数组中的位置所以查找快,增加需要查看是否达到了设定的容量,如果超过了就会进行数组的扩容所以增加慢。
LinkedList是基于双向链表实现的,双向链表可以直接增加节点,所以增加快;但查询需要遍历链表才能得到值,所以查询慢。

4.HashSet与TreeSet区别

相同点
都是Set接口的实现类,可以存放任意类型的数据
不同点
HashSet的去重标准是通过:hashcode和equals方法共同决定
TreeSet的去重标准:自然排序或定制排序
自然排序:实体类实现Comparable接口
定制排序:set容器中传入一个Comparator接口的实现类(匿名内部类)

5.HashMap、HashTable、TreeMap、ConcurrentHashMap区别

相同点:
1.都是map实现类,都是键值对
2.键都不能重复,可以存储任意类型任意多个数据
3.遍历要通过keySet和entrySet方法来进行遍历
不同点
HashMap线程不安全 可以以null作为key或value 效率高,适用于在Map中插入、删除和定位元素。 (最常用)
TreeMap 线程不安全 key的值不能为null,保存的记录是默认使用自然排序—升序(Comprable),也可以用比较器(Comparator)进行指定排序,所以是有序的。底层基于红黑树(一种自平衡二叉查找树)。
Hashtable线程安全 不能以null作为key或value 效率较低 同步的方法
ConcurrentHashMap 线程安全 不能以null作为key或value 效率较高 同步的代码快,提高了并发效率。

6.HashMap put get过程

这篇博客说的很详细,多看多理解。
https://segmentfault.com/a/1190000015726870
put方式
1.我们在new一个新的HashMap时会创建一个空的HashMap容器,其中会有一些准备好的常量字段:数组默认长度:16,默认加载因子:0.75,链表转化红黑树阀值:8,红黑树转化为链表的阀值:6
2.在put方法的时候根据key的hash值去找到具体的桶,先判断桶中是否有内容(长度是否为0,是否为null);如果没有就创建一个默认长度为16的数组;
3.根据key值得hashcode定位到一个桶中,如果没有值就创建一个新桶,直接加进去。
4.如果桶中有内容,就循环遍历,依次比较两个key值的hashcodeequals是否相等;如果相等就进行覆盖并在该方法末尾返回一个原value值
5.如果两个key值的hashcodeequals不相等,那么就判断当前是以什么方式进行存储的。如果是链表就以链表的形式存入,如果为红黑树就以红黑树的形式存入。
6.如果以链表的方式存入,就判断存入后链表的长度是否小于8,如果不小于8就转化为红黑树
7.存入判断当前map是否需要扩容(直接当前数组长度的两倍)
get方式
1.根据key来计算hash值,查看hash中是否有数据。如果为空直接返回null
2.取桶中的第一个数据是否为想要的key值,如果成功直接返回
3.不成功判断该桶中的内容是为链表还是红黑树
4.如果为链表就以链表的形式返回,如果为红黑树就以红黑树的方式返回

7.线程的创建方式

1.继承Thread
2.实现Runable接口
3.实现Callable接口,具有返回值

推荐使用实现,JAVA支持类的单继承多重继承,实现的拓展性更强。

8.线程的状态有哪些 线程中的方法有哪些

新建状态—>就绪状态—>(有可能出现阻塞状态)—>运行—>死亡
new—>start—>(sleep/wait)—>获取到cup—>完成或者其它原因终止

9.线程安全问题 如何解决

1.同步代码—同步监听对象
2.同步方法—静态方法(监听当前字节码对象) 非静态方法(监听this对象)
3.Lock上锁

10.ThreadLocal有什么作用 原理

作用
通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题,线程间的数据隔离
原理
初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

11.wait与sleep区别

相同点
运行状态中的线程执行,都可以使线程进入到等待阻塞状态。
不同点
wait() :1.通过 wait() 使线程挂起,直到线程得到 notify() 或 notifyAll() 消息,不然会一直等待。2.是属于Object中方法 会放弃锁资源
sleep(): 1.当过了等待时间,或者 I/O 处理完毕,线程重新转入就绪状态。 2.调用 Thread.sleep() 方法进入休眠状态,不会放弃锁资源

12.synchronize与lock的区别

相同点
不同点
synchronize同步代码块:
监听对象:特点 唯一性
1.this
2.字节码
3.其他对象(匿名对象不能 普通对象要static修饰 String有常量池的)
同步方法:
默认监听对象是this
Lock加锁 :
加锁的功能更强大
但需要进行手动释放,不然会造成死锁
总结:
同步是由jvm来维护 加锁是代码级别 需要释放锁 效率要更高一些

13.Spring的理解

IO与NIO的区别

线程池:

1.线程池的作用?

1.提高线程复用性,减少线程创建与销毁时的内存消耗
2.控制并发数量,通过闲时时间来控制线程的最大数量与最小数量
3.管理线程的生命周期

2.创建方式有哪些?有什么区别?

创建方式 创建内容 作用 默认值
CachedThreadPool 创建一个可缓存的线程池 执行很多短期异步的小程序或者负载较轻的服务器 如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程可以重复利用执行任务。默认为60s未使用就被终止和移除
FixedThreadPool 创建一个定长的线程池 执行长期的任务 如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务。如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务
SingleThreadPool 创建一个单线程的线程池 一个任务一个任务执行的场景 有且仅有一个工作线程执行任务,所有任务按照指定顺序执行,即遵循队列的入队出队规则
ScheduledThreadPool 创建一个预定时间的线程池,支持定时及周期性任务执行 周期性执行任务的场景(定期的同步数据)
ThreadPoolExecutor 创建一个自定义的线程池 控制性高

3.创建线程池的核心参数有哪些?每一代表什么意思?

参数 作用
corePoolSize 核心线程数(最小存活的工作线程数量)
maxPoolSize 最大线程数
keepAliveTime 线程存活时间(在corePoreSize<maxPoolSize情况下有用,线程的空闲时间超过了keepAliveTime就会销毁)
timeUnit 存活时间的时间单位
workQueue 阻塞队列,用来保存等待被执行的任务(①synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务;②LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;③ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小)
threadFactory 线程工厂,主要用来创建线程
handler 表示当拒绝处理任务时的策略(①丢弃任务并抛出RejectedExecutionException异常;②丢弃任务,但是不抛出异常;③丢弃队列最前面的任务,然后重新尝试执行任务;④由调用线程处理该任务)

4.线程池的拒绝策略有哪些?每一种有什么特点?

前提
当线程池中的数量到达了maximumPoolSize(最大线程数量)就会采用拒绝策略。
四种方式

方式 作用
AbortPolicy(默认) 直接抛出RejectedExecutionException异常阻止系统正常运行。
CallerRunsPolicy “调用者运行”一种调节机制,该策略既不会丢弃任务,也不会抛出异常,而是将某些任务回退给调用者,从而降低新任务的流量
DiscardOldestPolicy 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
DiscardPolicy 直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

JVM优化?

1.什么是JVM?JVM有哪些组成?每一部分的作用?

定义
JVM是虚拟机(Java Virtual Machine)的英文简称
JAVA跨平台原理:凭借的就是JVM.而对于不同的平台,Windows,Linux,Mac OS等,有具体不同的JVM版本.这些JVM屏蔽了平台的不同,提供了统一的运行环境,让Java代码无需考虑平台的差异,运行在相同的环境中
组成图
在这里插入图片描述
组件

  • 类加载器子系统
  • 本地方法库
  • 执行引擎
  • 运行数据区

作用

1.类加载器子系统

1.1 类加载的过程有哪些?每一个阶段做什么事情?

包含了加载、验证、准备、解析和初始化5个步骤
1.加载:找到相应的字节码文件,加载至内存中。分为显示加载与隐式加载。显示加载:拿到完全限定名,通过反射获得加载对象 隐式加载:通过new的方式来加载对象
2.验证:判断当前文件夹是不是一个java的class文件
3.准备:将static修饰的内容进行初始化创建,要么为0或者为null。如果还有finally修饰就给它赋予后面的初始值
4.解析:将java代码中的符号引用转化为直接引用,会直接找到该方法内存中的地址值。
5.初始化:为刚才准备好的参数进行初始化赋值

1.2双亲委派机制指的是什么?

在这里插入图片描述
原理
在我们自定义类加载器中需要加载一个类的时候,不会直接进行加载,而会委托上级的应用程序类加载器进行加载,而一直会委托到启动类加载器
启动类加载器会看自己是否具有这个类,如果没有就再次返回给下级继续寻找该类。如果找到了就进行加载,没有找到就抛出异常。

2. 执行引擎

执行引擎包含即时编译器(JIT)和垃圾回收器(GC)

3. JDK1.8以后运行时数据区有哪些组成?每一部分有什么作用?

JDK1.7与1.8最大的区别就是,使用元数据区取代了永久代。元数据区不再存在虚拟机中,而是存于本地内存
在这里插入图片描述

4.1 程序计数器

每个线程所私有的一块很小的内存空间,当前线程执行字节码的行号指示器,唯一一块不会内存溢出的区域(OOM),如果当前线程执行的是native方法的话,其值为null

4.2 JAVA虚拟机栈

在这里插入图片描述
每个线程私有的,每一个方法执行的时候都会创建一个栈帧,其生命周期与线程同进同退,并入栈。一旦完成调用,则出栈。所有的的栈帧都出栈后,线程也就完成了使命。

4.3 本地方法栈

JAVA虚拟机栈的类似,只对Native method的方法起作用

4.4 堆(重点)

内存模型
在这里插入图片描述
堆是java虚拟机管理内存最大的一块内存区域,所有线程共享,用来存放对象的区域,也是GC的主要区域。其中分为老年代与新生代,其比例为2:3.

新生代:一个Eden区,两个Survivor区。

Eden区:
存放的是通过new 或者newInstance方法创建出来的对象,绝大多数都是很短命的.正常情况下经历一次gc之后,存活的对象会转入到其中一个Survivor区,然后再经历默认15次的gc,就转入到老年代.这是常规状态下,在Survivor区已经满了的情况下,JVM会依据担保机制将一些对象直接放入老年代。

4.5 直接内存

jdk1.4引入了NIO,它可以使用Native函数库直接分配堆外内存。

4.6 元数据区

存放虚拟机加载的类信息,静态变量,常量等数据。

垃圾回收

1.什么是垃圾即如何判断对象已死?

什么垃圾
对象失去引用,不会再次用到的内容。
如何判断
(1)引用计数算法
引用了就添加一个计数,但互相引用也会添加。会导致失败
(2)可达性分析算法
当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可达)时,就说明此对象是不可用的,是死对象
在这里插入图片描述
(3)方法区回收
方法区中主要回收的是废弃的常量和无用的类,
判断常量是否废弃可以判断是否有地方引用这个常量,如果没有引用则为废弃的常量。
判断类是否废弃需要同时满足如下条件:

  • 该类所有的实例已经被回收(堆中不存在任何该类的实例)
  • 加载该类的ClassLoader已经被回收、
  • 该类对应的java.lang.Class对象在任何地方没有被引用(无法通过反射访问该类的方法)

2.垃圾回收算法有哪些?每种的特点是什么样的?

1.标记-清除算法
该算法先标记,后清除,将所有需要回收的算法进行标记,然后清除;这种算法的缺点是:效率比较低;标记清除后会出现大量不连续的内存碎片,这些碎片太多可能会使存储大对象会触发GC回收,造成内存浪费以及时间的消耗。
2.复制算法
复制算法将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。这种算法运行简单,解决了标记-清除算法的碎片问题,但是这种算法代价过高,需要将可用内存缩小一半,对象存活率较高时,需要持续的复制工作,效率比较低。
3.标记整理算法
标记整理算法是针对复制算法在对象存活率较高时持续复制导致效率较低的缺点进行改进的,该算法是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续空间出现,还可以避免对象存活率较高时的持续复制。这种算法可以避免100%对象存活的极端状况,因此老年代不能直接使用该算法。
4.分代收集算法
分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率搞,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。

3.什么时间回收垃圾?

当程序运行到安全点安全区的时候才会进行垃圾回收

4.垃圾回收器有哪些?G1垃圾回收器有什么特点?

新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old、CMS、Parallel Old
堆内存垃圾收集器:G1

G1垃圾回收的特点

1.G1进行垃圾收集的范围是整个堆内存,其它收集器收集的范围都是新生代或者老年代。
在这里插入图片描述
2.采用”化整为零“的思路,堆内存被划分为多个大小相等的内存块,每个Region被标记了E、S、O和H,说明每个Region在运行时都充当了一种角色,其中H是以往算法中没有的,它代表Humongous,这表示这些Region存储的是巨型对象(humongous object,H-obj),当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。

5.新生代和老年代的垃圾回收有什么不同?

young gc
发生在年轻代的GC算法,一般对象(除了巨型对象)都是在eden region中分配内存,当所有eden region被耗尽无法申请内存时,就会触发一次young gc,这种触发机制和之前的young gc差不多,执行完一次young gc,活跃对象会被拷贝到survivor region或者晋升到old region中,空闲的region会被放入空闲列表中,等待下次被使用。
mixed gc
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。
full gc
如果对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免full gc.

6.JVM优化用的工具有哪些?常用的参数?

前提

JVM调优目标
使用较小的内存占用来获得较高的吞吐量或者较低的延迟
指标

  1. 内存占用:程序正常运行需要的内存大小。
  2. 延迟:由于垃圾收集而引起的程序停顿时间。
  3. 吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。

常用工具

  1. 用 jps(JVM process Status)可以查看虚拟机启动的所有进程、执行主类的全名、JVM启动参数
  2. 用jstat(JVM Statistics Monitoring Tool)监视虚拟机信息

jstat -gc pid 500 10 :每500毫秒打印一次Java堆状况(各个区的容量、使用容量、gc时间等信息),打印10次

3.用jmap(Memory Map for Java)查看堆内存信息
4.利用jvisualvm分析内存信息(各个区如Eden、Survivor、Old等内存变化情况)

常用参数

参数 说明 实例
-Xms 初始堆大小,默认物理内存的1/64 -Xms512M
-Xmx 最大堆大小,默认物理内存的1/4 -Xms2G
-Xmn 新生代内存大小,官方推荐为整个堆的3/8 -Xmn512M
-Xss 线程堆栈大小,jdk1.5及之后默认1M,之前默认256k -Xss512k
-XX:NewRatio=n 设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 -XX:NewRatio=3
-XX:SurvivorRatio=n 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:1:1,一个Survivor区占整个年轻代的1/8 -XX:SurvivorRatio=8

Redis

1.Redis为什么这么快

1.数据是存在内存中
2.数据结构简单,查询和操作的时间复杂的都是O(1);
3.单线程架构,防止了多线程间的问题。如:不存在线程间的切换。

2.常用类型,以及使用场景

常用类型 使用场景
String:字符串 (最大不超过512M)缓存、共享用户Session、计数、限流、点赞
Hash:键值对结构 储存用户信息、简单直观、减少内存使用空间
List:多个有序字符串 消息队列:Redis的lpush+brpop命令组合即可实现阻塞队列
Set:无序不可重复,可以求交集与并集 全局去重、给用户增加标签,抽奖
ZSet:有序不可重复 排行榜

3.缓存击穿、缓存穿透、缓存雪崩

缓存穿透

定义

用户一直访问Redis和数据库都没有的数据(如id值为-1),攻击会造成数据库压力过大

解决
1.会在接口层增加校验,比如用户鉴权校验,参数做校验,不合法的参数直接代码Return,比如:id 做基础校验,id <=0的直接拦截等。
2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将对应Key的Value对写为null、位置错误、稍后重试这样的值具体取啥问产品,或者看具体的场景,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。
3.绝活:避免缓存穿透的利器之BloomFilter(布隆过滤器)(站在布隆后面?)

BloomFilter(布隆过滤器):当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

作者:敖丙
链接:https://juejin.im/post/5db69365518825645656c0de
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

缓存击穿

定义

在程序中Redis的一个热点数据过期,这个热点数据一直承受着高并发请求,而这个数据过期时会造成大量请求直接访问到数据库中引起数据库崩溃。就像水桶突然被凿开了一个洞,大量水从洞中涌出

解决
热点数据设置为永不过期,或者增加一个互斥锁

缓存雪崩

定义

在Redis中大量数据同时过期,而用户在这个时候访问这些过期数据会直接请求到数据库中,造成数据库崩溃的情况

解决
在设置过期时间的时候加上一个随机值,或者设置为永不过期

总结

Redis就是作为用户和数据库的一个中间件,用来缓存数据在中间起一个缓冲作用。而就是会出现总总原因导致了Redis失效,从而大量操作涌进数据库中造成数据库崩溃。而我们解决的就是在Redis失效的时候找到一个方案来重新建立一个新的缓存区或者在那一段时间现在用户的访问来保护数据库。

4.AOF和RDB的区别

redis提供了两种持久化的方式:AOF和RDB,可以通过修改配置文件redis.conf来进行更改

RDB模式:

持久化可以在指定的时间间隔内(五分钟)生成数据集的时间点快照,默认开启该模式.(保存内容)

优点
适合做冷备(定时备份)
相同内容比AOF文件更小,数据恢复的时候速度更快
缺点
是快照文件,只会在指定时间间隔生成。会丢失一部分数据内容。
在生成数据快照的时候,如果文件内容很大,客户端会暂停下来去进行备份

AOF模式:

持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集,默认关闭该模式(保存命令,一秒一次)

优点
在AOF进行备份的时候是以追加的形式进行,对磁盘开销小,效率性能高
缺点
AOF开启的时候,Redis支持写的QPS会比RDB的更低,因为会一秒进行备份一次

选择

在这里插入图片描述
RDB进行快速恢复,然后AOF做细节的补全。

原创文章 25 获赞 70 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ChengHuanHuaning/article/details/103703444