面试知识点大杂烩(java)

1、线程池7个配置属性

1.1、corePoolSize核心线程数

当有线程任务提交时,若线程池内没有空闲的核心线程,且核心线程数量未达到峰值,则新建一个核心线程,若存在空闲的核心线程,则直接由该线程去执行任务。当核心线程任务完成,该线程不会被销毁,而是进入空闲状态,等待下一个任务。

线程复用,根据源码分析,当核心线程没有任务时,进入循环状态,不断去执行空任务,直到任务提交到该线程,则去执行提交的任务。

1.2、maximumPoolSize最大线程数

最大线程数和核心线程数决定了非核心线程数的大小(即maximumPoolSize-corePoolSize=非核心)。只有在核心线程已满且无空闲核心线程,且等待队列也满,则新建一个非核心线程去处理新提交的任务。非核心线程不会复用,执行完任务后,直到达到存活时间,则该线程被销毁

1.3、keepAliveTime空闲线程存活时间

当线程数大于corePoolSize但小于maximumPoolSize时,只要有空闲线程,线程池则根据keepAliveTime去销毁多出corePoolSize个数的空闲线程。

1.4、unit时间单位

keepAliveTime过期时间的时间单位,默认是毫秒级

1.5、workQueue阻塞队列

当corePoolSize满了后,新提交的任务会先进入阻塞队列,等待空闲的核心线程执行任务

1.6、threadFactory线程工厂

生产线程来执行任务,可自定义县城工厂模式

1.6.1、newCachedThreadPool可缓存的线程池

1、该线程池的核心线程数量是0,线程的数量最高可以达到Integer 类型最大值;

2、创建ThreadPoolExecutor实例时传过去的参数是一个SynchronousQueue实例,说明在创建任务时,若存在空闲线程就复用它,没有的话再新建线程。

3、线程处于闲置状态超过60s的话,就会被销毁。

1.6.2、newFixedThreadPool定长线程池

1、线程池的最大线程数等于核心线程数,并且线程池的线程不会因为闲置超时被销毁。

2、使用的列队是LinkedBlockingQueue,表示如果当前线程数小于核心线程数,那么即使有空闲线程也不会复用线程去执行任务,而是创建新的线程去执行任务。如果当前执行任务数量大于核心线程数,此时再提交任务就在队列中等待,直到有可用线程。

1.6.3、newSingleThreadExecutor单线程线程池

该线程池基本就是只有一个线程数的newFixedThreadPool,它只有一个线程在工作,所有任务按照指定顺序执行。

1.6.4、newScheduledThreadPool支持定时的定长线程池

与newCachedThreadPool相似,但支持延迟执行及定时执行任务

1.7、handler拒绝策略

当workQueue已满且maximumPoolSize已达最大值时,执行设置的拒绝策略

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(默认) ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

2、redis持久化机制

2.1、RDB(Redis DataBase快照,默认)

产生文件为dump.rdb,以一定时间将内存数据以快照的方式保存到硬盘。

优点:

1、方便持久化

2、容灾性好

3、性能最大化:fork子进程来完成写操作,主进程继续处理命令,IO最大化,主进程不会有任何IO操作

4、数据集大时,启动效率更高

缺点:

1、数据安全性低:RDB是隔一段时间进行持久化。如果在这期间发生故障,会丢失数据

2.2、AOF(Append Only File持久化)

将redis每次操作记录写到日志文件中,当redis重启时,会优先执行日志文件中的操作记录恢复数据。

优点:

1、数据安全

缺点:

1、AOF文件大,恢复速度慢

2、启动效率低

2.2.1、三种策略

always:每条写指令都同步到硬盘

everysec:每秒执行一次同步

no:由操作系统决定同步时间

2.2.2、AOF重写

由于AOF会随着redis的运行不断增大,为解决AOF文件过大问题,redis会对AOF中的指令进行重写以减小文件大小。

3、HashMap

3.1、数据结构

数组+链表+红黑树

3.2、put过程

1、判断数组是否为空,为空则初始化

2、不为空,计算hash值,计算数组下标Index

3、查看index下是否有数据,没有数据则构建一个node节点放在index中

4、存在数据,则发生hash冲突,判断key值是否相等,相等则替换

5、不相等,判断节点是否为树结构,是的话构建树节点插入红黑树,不是的话创建普通节点,用尾插法插入链表

6、判断链表长度,大于8则转为红黑树

7、判断节点数是否大于阈值,大于则扩容为原数组两倍

3.3、hash函数(扰动函数)

先拿到key的hashcode,32位int值,然后将高16位和低16位进行异或操作

3.4、jdk1.8的优化

1、数组+链表优化为数组+链表+红黑树

2、头插法改为尾插法(头插法扩容时会反转链表,多线程下可能成环)

3、扩容时1.7需要重新计算hash,1.8采用逻辑判断,位置不变或索引+旧容量大小

4、插入时,1.7先判断扩容再插入,1.8先插入后判断扩容

3.5、ConcurrentHashMap分段锁

jdk1.8之前是采用数组+Segment的实现,Segment是类似hashMap的结构,同时也是一个ReentrantLock。

jdk1.8采用synchronized+CAS,synchronized锁住的是数组下的链表的第一个节点。

4、synchronized锁原理

1.6之前单纯是重量级锁,1.6之后加入了偏向锁、轻量级锁和重量级锁,采用锁升级原理

4.1、对象头

在hotspot虚拟机中,对象分为三块区域:对象头、实例数据和对齐填充。synchronized是根据对象头实现的,对象头记录了对象的锁状态。

4.2、monitor

可理解为同步工具,java中所有的对象都是monitor

4.3、锁类型

synchronized通过多种锁实现加锁逻辑

4.3.1、乐观锁

读数据的时候不会上锁,写数据的时候先读出当前版本号,然后进行加锁操作,如果发现版本号不一致,则失败并重复操作。

如CAS,也叫自旋锁

4.3.2、悲观锁

每次读写操作都会加锁,其他线程要读写这个对象,则需要等待获取锁。

如synchronized

AQS框架的锁是先尝试CAS乐观锁,如果获取不到则转为悲观锁,如retreenLock

4.3.3、偏向锁

大多数情况下,锁竞争并不激烈,获取到锁的线程总是同一个,于是引入偏向锁。

当一个线程获取锁时,会在对象头和栈帧中的锁记录里,存储偏向锁的线程ID,之后线程在加解锁都不需要进行CAS。

4.3.4、轻量级锁

当线程2尝试获取锁,锁会检测当前持有锁的线程1是否存活,如果不存活则线程2加锁(轻量级锁)成功,如果线程1存活,则暂停线程1并升级锁为轻量级锁。

轻量级锁会在线程1的栈帧中建立一个锁记录空间,存储锁对象当前的Mark Work,此时JVM会采用CAS尝试将Mark Work更新为指向栈帧中锁记录的空间指针,并把锁的标志位置为00(轻量级锁标志),此时线程2会采用CAS多次尝试获取锁(即修改Mark Work)。

4.3.5、重量级锁

线程2多次CAS获取锁失败后,锁升级为重量级锁。

重量级锁通过对象内部的监视器(monitor)实现,所有获取锁失败的线程都会进行阻塞,阻塞的线程不消耗cpu。

5、RetreenLock原理

CAS+AQS,公平锁

AQS是一个FIFO(先入先出)的同步队列

支持独占锁和共享锁

维护一个state,记录锁状态和资源数量

线程首先采用CAS尝试获取锁(设置state为1),如果失败则加入同步队列,并进入等待状态,等待锁释放。如果同步队列中获取锁的不是第一个节点,则会唤醒第一个节点的线程获取锁,以此实现公平锁。

6、redis相关

String、List、Set、Zset、Hash

6.1、为什么redis快

1、完全基于内存

2、数据结构简单

3、采用单线程,避免上下文切换带来的损耗,无需考虑多线程引起的加解锁

4、使用多路IO复用模型,非阻塞IO

6.2、持久化策略(见第2大点)

6.3、缓存雪崩

6.3.1、原因

大量缓存集中在一个时间段失效,给系统带来很大压力

6.3.2、解决

设置随机过期时间

6.4、缓存穿透

6.4.1、原因

针对一个缓存和数据库都不存在的值进行不断地访问

6.4.2、解决

1、设置空值缓存

2、布隆过滤器

6.5、缓存击穿

6.5.1、原因

数据库存在但缓存过期,此时大量请求到来

6.5.2、解决

互斥锁,加锁成功才从数据库获取,失败则直接从缓存获取

6.6、过期策略

默认同时采用了惰性删除和定期删除

6.6.1、定时过期

每个设置过期时间的key都要创建一个定时器,到过期时间会立即删除。会占用大量的cpu资源去处理过期数据。

6.6.2、惰性删除

只有当访问一个key时,才会去判断key是否过期,过期则删除。到达过期时间但没有进行访问的key会占用大量内存。

6.6.3、定期删除

每隔一段时间,会从设置了过期时间的key中随机抽取一部分进行过期处理

6.7、淘汰策略

内存不足时会执行淘汰策略

1、noeviction:直接报错

2、allkeys-lru:在键空间中,移除最近最少使用的key(最常用)

3、allkeys-random:在键空间中,随机移除

4、volatile-lru:在设置了过期时间的键空间中,移除最近最少使用的key

5、volatile-random:在设置了过期时间的键空间中,随机移除key

6、volatile-ttl:在设置了过期时间的键空间中,有更早过期时间的key优先移除

7、三握四挥

7.1、三握

客户端(C)和服务端(S)开始都处于CLOSED状态

S首先创建TCB(传输控制块),进入LISTEN状态,等待客户端连接

第一次握手:

C的TCP进程创建TCB,然后向S发出连接请求报文段。段首同步位SYN=1,初始序列号seq=x,此时C进入SYN-SENT状态。

第二次握手:

S收到连接请求报文段,如果同意建立连接,则向C发送确认报文段。报文段中SYN=1,确认位ACK=1、确认号ack=x+1,初始序列号seq=y,S进入SYN-RCVID状态。

第三次握手:

C收到S的确认报文段后,再向S发送确认报文段。报文段ACK=1、确认号ack=y+1。C进入ESTAB-LISHED状态。当S收到C的确认报文段后,也进入该状态,连接建立。

7.1.1、为什么不是两次握手

两次握手会发生如下情况:

当C发出第一次握手,但由于网络延迟等因素,导致本已超时的连接请求发送到了S,S接收到请求后,并无法感知该连接时无效的超时连接,于是向C发送了连接确认并建立连接,C收到S的连接确认后无法处理该超时连接,于是该无效连接将占用资源。

7.2、四挥

ESTAB-LISHED:已建立连接

FIN-WAIT-1:终止等待1

CLOSE-WAIT:关闭等待

FIN-WAIT-2:终止等待2

LAST-ACK:最后确认

TIME-WAIT:时间等待

CLOSED:关闭

第一次挥手:

C发送连接释放报文段,首部终止控制位FIN=1,序号seq=u,C进入FIN-WAIT-1状态,等待S确认。

第二次挥手:

S收到C的连接释放报文段后,立刻发出确认报文段,确认号ack=u+1、序号seq=v,S进入CLOSE-WAIT状态

(此时,C到S这个方向的连接就断开了,TCP处于半关闭状态,但S到C的连接还没有断开,B可以继续向C发送数据。)

第三次挥手:

C收到S的确认报文段后,进入FIN-WAIT-2状态,继续等待S发送连接释放报文段。当S没有其他数据要发送时,会发送连接释放报文段到C,报文段终止控制位FIN=1,序号seq=w,确认号ack=u+1,S进入LAST-ACK状态,等待C的确认报文段。

第四次挥手:

C收到S的连接释放报文段并发送确认报文段,ACK=1,确认信号ack=w+1,序号seq=u+1,C进入TIME-WAIT状态,S收到确认报文段后进入CLOSED状态,C等待一段时间后进入CLOSED状态。

7.2.1、为什么第四次挥手,C要等待一段时间后才关闭

保证最后的确认报文段能发送到S。

保证该连接滞留的报文段从网络中消失。

8、虚拟机内存结构

8.1、线程私有

本地方法栈:登记本地方法,执行引擎加载时加载本地方法(jna方法存放于此)

程序计数器:记录指向下一条指令的地址

java栈(虚拟机栈):一个线程对应一个栈帧,每执行一个方法都会建立一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等

8.2、线程共享

方法区:静态变量,常量,方法信息,运行时常量

堆:对象实例,GC主要位置

8.2.1、堆的结构详解

堆被划分为三大模块:永久代、老年代和新生代

新生代:新生代又分为Eden、survival from 、survival to。新创建的对象都会首先在Eden区,survival from 和survival to并没有明显的界限,两块区域在经历一次young GC后,总会有一块是空的,一块是有数据的。在经历young GC后,Eden区存活的对象,会复制到survival from/to中(取决于哪块区域是空的),并标记其存活标志位为1,survival from/to中(取决于哪块区域是有数据的)存活的对象,会复制到另一块区域,并标记其存货标记位为2,直到下一次young GC,存活标志位为2的对象如果依然存活,则转移到老年代。简单来说,新生代中如果经历三次young GC依然存活,那么就会被转移到老年代。

老年代:存放由新生代晋升过来的对象。该区域满的时候也会进行GC,频率正常来讲会远低于young GC。

永久代:举例来说,spring创建的bean都被存放于永久代。这里涉及bean的创建方式,一种是声明bean之后直接创建该实例,第二种是当调用时再创建对应实例。相比较来看,第二种会对永久代的空间利用更好。永久代是触发full GC的主要区域,一般来说,尽可能不要触发full GC或者降低full GC的频率,因为full GC会暂停程序所有线程,在GC完成后重新启动线程,这样CPU线程调度带来的消耗将是灾难性的。

9、数据库隔离级别

9.1、并发下出现的问题

脏读:事务读取到另一个事务未提交的数据。

不可重复读:同一个事务的多次查询操作,会读取到不同的数据(针对update和delete)。

幻读:同一个事务的多次查询操作,查询结果行数不一致(针对insert操作)

9.2、解决

读未提交:以上三种情况都会出现(修改操作加行锁)

读已提交:解决脏读,写事务未提交时,读事务不会重复读取到未提交的修改;依然会出现不可重复读和幻读(读操作加行级共享锁,读完立即释放;写操作加行级排他锁,事务结束后释放)

可重复读:通过加行锁解决不可重复读,会出现幻读(读操作加行级共享锁,事务结束后才释放;写操作加行级排他锁,事务结束后释放)

串行化:事务串行执行

10、计算机网络

10.1、网络七层模型

应用层(HTTP、FTTP等):客户端应用程序

表示层:数据的加密解密

会话层:负责在网络中的两节点之间建立、维持和终止通信。

(在TCP/IP五层模型中,以上三层称为应用层)


传输层(TCP、UDP等):定义传输数据的协议端口号,以及流控和差错校验

网络层(IP):将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方

链路层(数据帧):物理地址寻址、数据的成帧、流量控制、数据的检错、重发

物理层:例如接上网卡,就实现了一整套的网络模型

10.2、浏览器输入网址后发生什么

①DNS解析:主机向本地域名服务器发送消息,采用递归形式不断向后面的服务器查询。已访问过的网站,本地域名服务器会有保存,下次访问会比较快;

②封装HTTP请求:封装请求行,请求头及请求正文;

③TCP连接:已得到ip地址在应用层,应用层通过socket与传输层建立连接(采用TCP/IP),打包到网络层的ip数据包,在链路层转为数据帧,再到物理层利用比特流形式进行数据传输;

④服务器永久重定向响应:将对不同名称统一网络地址的网址重定向为统一的网址,避免因输入的名称不同而导致产生多次请求;

⑤浏览器跟踪重定向地址:重复第三步,客户端再次发送HTTP请求;

⑥服务器处理请求:

⑦服务器返回HTTP响应:

⑧浏览器显示HTML

⑨浏览器发送获取嵌入HTML中的资源

你可能感兴趣的:(面试知识点大杂烩(java))