知识点汇总

261 阅读27分钟

1、垃圾回收机制的三个假说?

  • 弱分代假说:绝大多数对象都是朝生夕灭的
  • 强分代假说:熬过越多次垃圾回收过程的对象越难以消亡

上面两个说明了Java虚拟机采用分代的收集算法,可以避免对大量对象进行扫描,通过分代可以减小垃圾收集在内存空间和时间消耗的开销

  • 跨代引用假说:跨代引用相对于同代引用来说只占少数

跨代引用假说使得JVM在新生代分配一块结构,用来标记哪一部分老年代对象可能存在跨代引用的情况,那么以后进行Minor GC的时候直接扫描这个区域即可


2、JVM中有哪些垃圾回收的算法?

  • 标记-清除算法:标记需要回收的对象(标记阶段),然后统一把它们回收掉(清除阶段)
img

但是这就有两个缺点:

(1)如果需要被回收的对象相对比较多,就需要进行大量的标记和清除的动作

(2)回收后内存空间会产生大量内存碎片,当需要存放较大对象时,如果找不到区域会进行下一次垃圾回收

  • 标记-整理算法:标记过程和标记-清除算法一致,但回收过程中,存活的对象会向内存的一端移动,然后回收掉其他的空间。

这也有个问题,一旦存活的对象比较多,移动存活对象还要更新引用地址,负担很重,而且更新地址时需要暂停用户的程序才行

img
  • 标记-复制算法:把内存空间分两半,每次只使用一般,当某一半内存空间满了,会触发垃圾回收把存活的对象放入另一个区域,然后清空当前区域

但是这种方式也有点问题就是内存会变为一半,而且当存活对象比较多,会进行多次复制操作

img

在分代算法中也部分采取了这种方式:分出一块Eden区和两块较小的Survivor区,每次使用Eden和一块Survivor区,当发生垃圾回收时,会把存活的对象放入另一块Survivor区,Eden和Survivor的比例时8:1,这就很好的解决了问题

  • 分代算法:设置新生代和老年代,会根据对象的年龄分配到不同的内存空间上
img

3、tcp/ip的三次握手和四次挥手是什么?

三次握手

  • 第一次握手:客户端发送了一个SYN标记为1的包,并生成了一个随机数seq(c) = x作为初始序列号,发送过后进入SYN-SEND状态
  • 第二次握手:服务器收到了SYN=1的包,知道了客户端想和自己建立连接,随即进入LISTEN状态。发送一个ACK = 1,SYN = 1,ack= x+1,并随机生成一个序列号seq(s) = y的包,并进入SYN-RECV状态
  • 第三次握手:客户端收到服务器发送的包,检查ACK的值是否等于SEQ(c)+1,SYN是否等于1,确认成功后再发送一个seq(c) =x+1,ack=seq(s) + 1的包,双方进入ESTABLISHED状态,tcp连接建立
在这里插入图片描述

四次挥手

  • 第一次挥手:客户端发送FIN报文,FIN=1,seq = u,并且停止发送数据,并且进入FIN-WAIT-1状态
  • 第二次挥手:服务器接收到客户端发送的报文,发送ACK = 1,seq = v,确认号ack = u+1的包,进入CLOSE-WAIT状态,客户端接收到服务器发送的确认后,进入FIN-WAIT-2状态
  • 第三次挥手:服务器发送完所有的数据后,发送FIN=1,ACK=1,seq=w,ack=u+1的包,B进入LAST-ACK状态
  • 第四次挥手:客户端接收到服务器发送的连接释放的报文后,发出ACK=1,seq=u+1,ack=w+1的包,随后进入TIME-WAIT状态,服务器接收到数据关闭,客户端等待2MSL后没有响应也进入关闭状态
在这里插入图片描述

4、Java中都有哪些引用类型?

  • 强引用类型:就是最基本的Object obj = new Object();只要强引用关系存在,当发生GC的时候就不会被回收,一旦内存不足会抛出OutOfMemoryException异常来
  • 软引用类型:SoftReference类来实现软引用,被软引用的对象,在系统将要发生内存溢出异常前,会把这些对象进行二次垃圾回收,如果内存空间依然不足仍会抛出OutOfMemoryException异常
  • 弱引用类型:WeakReference类来实现弱引用,被弱引用的对象,不管内存是否充足,只要进行下一次垃圾回收,弱引用都会被回收掉
  • 虚引用类型:PhantomReference类实现虚引用,无法通过虚引用获取对象,它只是垃圾回收时返回一个通知
String abc=new String("abc"); //1
SoftReference<String> softRef=new SoftReference<String>(abc); //2
WeakReference<String> wea kRef = new WeakReference<String>(abc); //3
abc=null; //4
softRef.clear();//5

5、如何判断对象是否可以被垃圾回收?

判断是否可以被垃圾回收是判断当前哪些对象是处于存活状态,哪些对象处于“死亡”状态的。

两种方式:

  • 引用计数算法:给每个对象创建一个引用计数器,每被一个对象所引用就 +1,引用被释放时就 -1,当要垃圾回收时,判断计数是否等于0即可,但是循环依赖的问题没办法解决
  • 可达性分析算法:根节点称为GC Roots,通过GC Roots根据引用关系向下搜索,所走的引用路径叫做引用链,当一个对象没有任何引用链时,那么这个

6、哪些对象可以作为GC Roots的对象?(见深入Java虚拟机)

  • 在虚拟机栈中引用的对象,譬如当前正在运行的方法所使用到的参数、局部变量、临时变量等
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型的静态变量
  • 在方法区中常量引用的对象,譬如字符串常量池里的引用
  • 在本地方法栈中JNI(也就是native方法)引用的对象
  • Java虚拟机内部的引用,比如基本数据类型对应的Class对象,一些常驻的异常对象,还有系统类加载器
  • 所有被同步锁(synchronized关键字)持有的对象
  • 反映Java虚拟机内部情况的像JMXBean、JVMTI中注册的回调、本地代码缓存等
  • 还包括一些根据用户所选垃圾回收器临时加入的一些对象

7、常用的字符集都有哪些,它们有哪些区别?(不用考)

  • ASCII字符集:收录128个字符,包括空格、标点、数字、大小写字母和一些不可见的字符;使用1个字节编码
  • ISO 8859-1字符集:共收录256个字符,在ASCII字符集基础上扩充的,有扩充了一些西欧字符;使用1个字节编码
  • GB2312字符集:收录了一些拉丁字母、希腊字母、日文、俄语、部分汉字还有其他符号;如果在ASCII中使用1个字节编码,不在则使用2个字节编码
  • GBK字符集:对上面GB2312字符集进行了扩充;同上面GB2312字符集的编码方式
  • UTF-8字符集:几乎收录了所有字符和语言,现在仍然在扩充,编码采用可变的1-4个字节的方式

8、Redis的持久化机制都有哪些?原理是什么?

Redis的数据都存储于内存中,一旦宕机就容易导致数据丢失,那么Redis就需要有持久化的机制,Redis的持久化机制有两种,rdb和aof两种方式,一种叫做rdb快照,一种叫做aof日志。

rdb快照方式

Redis因为是单线程的,在持久化的过程中,我们就既要完成持久化操作,又要进行redis的请求处理,持久化的I/O操作是非常耗费时间的,而且还有个问题就是在持久化的过程中,如果有人修改或者删除了某个数据怎么办?Redis的rdb方式采用的是操作系统多进程的Copy On Write(COW)机制来实现快照持久化。

Redis默认会将持久化的快照文件位于当前进程工作目录之下,叫做dump.rdb文件。

原理

首先Redis在持久化时会调用glibc函数fork产生一个子进程,父进程进行请求处理,持久化则由子进程来进行。首先某一时刻进行了快照,产生了dump.rdb文件,然后进行持久化操作。

优点:

  • 当数据量很大时,rdb方式相比于aof方式更加高效;
  • 产生一个dump.rdb文件,可以放到安全的目录中,容灾性好;
  • 而且采用fork子进程进行持久化,I/O效率高

缺点:定期进行快照,如果某一时刻修改了数据还没来得及快照,那一段时间的数据修改就丢失了

aof日志方式

原理

对Redis每次修改的命令记录到日志中,当需要恢复数据的时候会将记录的日志重新执行一遍。

优点:

  • 可以设置aof的持久化频率,比如每次或者多久一次
  • 即使出现宕机或文件没了,也可以通过redis-check-aof来恢复日志文件

缺点:aof文件比rdb文件大,而且指令如果太多不容易恢复


9、Redis为什么这么快?

  • Redis是基于内存的,内存的操作相对于磁盘的速度是要快很多的
  • Redis中的数据结构都是经过专门设计的,数据结构种类少,且对数据的操作简单
  • Redis采用的是多路复用,而不是阻塞I/O
  • Redis的单线程避免了多线程复杂的上下文切换和竞争问题,也不会产生死锁的问题

10、MySQL语句的执行顺序

sql语句范例:

SELECT 
    字段等
FROM
    表名1,表名2
JOIN
    表名2或表名1
ON
    条件
WHERE
    查询条件
GROUP BY 
    分组条件
HAVING
    条件
ORDER BY
    排序条件
LIMIT
    分页条件;
image.png
  1. FROM: 对FROM的左边的表和右边的表计算笛卡尔积。产生虚表VT1
  2. ON: 对虚表VT1进行ON筛选,只有那些符合的行才会被记录在虚表VT2中。
  3. JOIN: 如果指定了OUTER JOIN(比如left join、 right join),那么保留表中未匹配的行就会作为外部行添加到虚拟表VT2中,产生虚拟表VT3, rug from子句中包含两个以上的表的话,那么就会对上一个join连接产生的结果VT3和下一个表重复执行步骤1~3这三个步骤,一直到处理完所有的表为止。
  4. WHERE: 对虚拟表VT3进行WHERE条件过滤。只有符合的记录才会被插入到虚拟表VT4中。
  5. GROUP BY: 根据group by子句中的列,对VT4中的记录进行分组操作,产生VT5.
  6. HAVING: 对虚拟表VT5应用having过滤,只有符合的记录才会被 插入到虚拟表VT6中。
  7. SELECT: 执行select操作,选择指定的列,插入到虚拟表VT7中。
  8. DISTINCT: 对VT7中的记录进行去重。产生虚拟表VT8.
  9. ORDER BY: 将虚拟表VT8中的记录按照<order_by_list>进行排序操作,产生虚拟表VT9.
  10. LIMIT:取出指定行的记录,产生虚拟表VT10, 并将结果返回。

11、Spring bean的生命周期?

image-20210607103405031
  • 实例化:反射的方式生成对象
  • 填充属性bean:熟悉可以谈谈populateBean,循环依赖问题(三级缓存)
  • 调用aware接口方法:invokeAwareMethod(完成BeanName,BeanFactory,BeanClassLoader对象的属性设置)
  • 调用BeanPostProcessor的前置处理方法:使用比较多的由ApplicationContextPostProcessor,设置ApplicationContext,Enviroment,ResourceLoader等对象
  • 调用init-method方法:invokeInitmethod()方法,判断是否实现了InitializeingBean接口,如果由调用AfterPropertiesSet方法,没有不调用
  • 调用BeanPostProcessor的后置处理方法:使用比较多的Spring aop就是在这里实现的,AbstractAutoProxyCreator就是干这个用的
  • 获取到完整的bean,可通过getBean获取对象
  • 销毁:判断是否实现了DispoableBean接口,调用destroyMethod方法

12、SpringMVC的工作流程

  • 用户发送请求至前端控制器DispatcherServlet;
  • DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
  • 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
  • DispatcherServlet 调用 HandlerAdapter处理器适配器;
  • HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
  • Handler执行完成返回ModelAndView;
  • HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
  • DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
  • ViewResolver解析后返回具体View;
  • DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
  • DispatcherServlet响应用户。

img


13、MySQL的InnoDB有几种行格式?每种行格式都包含哪些信息?

行格式

一共4种:

  • COMPACT行格式
  • REDUNDANT行格式
  • DYNAMIC行格式
  • COMPRESSED行格式

COMPACT行格式包含信息

Compact行格式示意图:

image_1c9g4t114n0j1gkro2r1h8h1d1t16.png-42.4kB
  • 变长字段长度列表:一些可变长的字段(比如varchar类型、varbinary类型、text类型、blob类型等)被存储在变长字段长度列表中,各个变长字段真实数据占用的字节数存放在开头的位置,按照逆序存放

    image_1c9grq2b2jok1062t8tov21lqjbj.png-42.6kB

  • Null值列表:顾名思义是存储null值用的,如果某个表没有可存储Null值的字段,那么就没有Null值列表。如果有可存储Null值的列,那么就需要存储它,需要整数个字节的位来存储在这几个列,这些Null值列采用的是逆序存储的方式,在前面为空的字段补0。如果某个记录的可变长字段不为null,那么这个列的位就记录为0,为null就记录为1。然后把这个二进制位转换成十六进制存储在Null值列表中

image_1c9g8ps5c1snv1bhj3m48151sfl6r.png-20.6kB

  • 记录头信息:固定采用5个字节,也就是40个二进制位。各个位代表的信息不同
img
名称大小(单位:bit)描述
预留位11没有使用
预留位21没有使用
deleted_mask1标记该记录是否被删除
min_rec_mask1B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned4一个页面包含若干个组,每个组都有带头大哥和小弟,大哥的n_owned表示当前记录拥有的记录数,小弟的则为0
heap_no13表示当前记录在记录堆的位置信息
record_type3表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录(Infimum),3表示最大记录(Supremum)
next_record16表示下一条记录的相对位置

然后按照每8位从二进制换算成十六进制,把数值写入上面的列中(如下面所示)

img
  • 真实的数据

包含有一些隐藏列:

列名是否必须占用空间描述
row_id6字节行ID,唯一标识一条记录
trx_id6字节事务ID
roll_pointer7字节指针回滚

row_id:这里会生成主键,如果定义了主键那么就按照自定义的主键;如果没定义主键,那就找一个UNIQUE键作为主键;如果还是没有,那么就生成这个隐藏的row_id列作为主键。

trx_id和roll_pointer是肯定会有的,row_id不一定,找不到主键才会使用隐藏列。

真实的数据会按照字符集的映射规则存储,如果是固定长度那么就在空余的地方补上空格的字符集对应编码。

REDUNDANT行格式包含信息

Redundant行格式示意图:

image_1c9h896lcuqi16081qub1v8c12jkft.png-36.2kB
  • 字段长度偏移量:这个记录会把所有列的长度信息都记录下来,然后通过偏移量记算各个长度
  • 记录头信息:
名称大小(单位:bit)描述
预留位11没有使用
预留位21没有使用
deleted_mask1标记该记录是否被删除
min_rec_mask1B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned4表示当前记录拥有的记录数
heap_no13表示当前记录在记录堆的位置信息
n_field10表示记录中列的数量
1byte_offs_flag1标记字段长度偏移列表中每个列对应的偏移量是使用1个字节还是2个字节表示的
next_record16表示下一条记录的相对位置

DYNAMIC行格式和COMPRESSED行格式

  • Dynamic行格式:在处理溢出列的时候采用的是存储全部真实的数据,而Compact存储的却是前768个字节
  • Compressed行格式:和Dynamic行格式类似,但是会采用压缩算法对页面进行压缩,节省空间

14、反射的一些问题

反射的概念

反射的定义:反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

反射有哪些API

  • java.lang.Class:代表了一个类
  • java.lang.reflect.Field:代表一个类的成员变量
  • java.lang.reflect.Method:代表一个类的方法
  • java.lang.reflect.Constructor:代表一个类的构造器

如何获取Class对象

  • 通过类的forName()方法获取Class对象
  • 通过类.class获取Class对象
  • 通过对象.getClass()方法获取Class对象
  • 如果是基本数据类型可以使用TYPE来获取
public static void main(String[] args) throws ClassNotFoundException {
    Class clazz1 = Class.forName("com.xuangong.reflection.User");
    Class clazz2 = User.class;
    Class clazz3 = new User().getClass();
    Class clazz4 = Integer.TYPE;
    System.out.println(clazz1.hashCode());
    System.out.println(clazz2.hashCode());
    System.out.println(clazz3.hashCode());
}

Class类的常用方法有哪些?

方法描述
Field[] getFields()返回一个包含Field对象的数组,存放该类或接口的所有可访问公共属性(含继承的公共属性)
Field[] getDeclaredFields()返回一个包含Field对象的数组,存放该类或接口的所有属性(不含继承的的属性)
Field getField(String name)返回一个指定公共属性名的Field对象
Method[] getMethods()返回一个包含Method对象的数组,存放该类或接口的所有可访问公共方法(含继承的公共方法)
Method[] getDeclaredMethods()返回一个包含Method对象的数组,存放该类或接口的所有方法(不含继承的方法)
Constructor getConstructor()返回一个包含Constructor对象,存放该类的所有公共构造方法
Class[] getInterfaces()返回一个包含Class对象的数组,存放该类或接口实现的接口
T newInstance()使用无参构造方法创建该类的一个新实例
String getName()以String的形式返回该类(类、接口、数组类、基本类型或void)的完整名

15、Spring bean的循环依赖如何解决?什么是二级缓存什么是三级缓存?

什么是循环依赖?

类A中包含类B的某一属性,类B中包含类A的某一属性。比如老师对应一些学生,那么老师就可以有学生的属性,学生也对应老师,学生也有老师这个属性,当Spring在创建对象的时候这种相互引用,相互依赖的情况就是循环依赖。

img

Spring bean对象是怎么创建的?

  • Spring生成容器
  • 创建对象A,这个时候仅仅是在内存当中分配了一块区域
  • 对A进行赋值操作,发现A对象有B属性,那么在容器中找B对象,因为B还没进行创建,那么就开始B的创建
  • 创建对象B,这个时候仅仅是在内存当中分配了一块区域
  • 对B进行赋值操作,发现B对象有A属性,那么在容器中找A对象,因为A还没完成,那么就开始A的创建......
  • 产生了循环依赖

image-20210606164625716

什么是一级、二级、三级缓存?

  • 一级缓存:SingletonObject——把刚刚完成创建的对象放入这个缓存当中
  • 二级缓存:EarlySingletonObject——没完成创建的对象放入这个缓存
  • 三级缓存:SingletonFactory——对象工厂,用来创建对象的

其实循环依赖的时候创建A对象时,没创建完也可以放入缓存中,我们不需要所有的对象都创建完整。只需要没创建完也把这个对象放入缓存即可。

但是这里也有个问题:如果用到了动态代理u(aop)的话,动态代理相当于使用了这个类的代理对象,这个代理对象和最开始放入二级缓存的对象不是同一个对象,那么创建对象的时候还是找不到这个对象,还是没办法完成创建


16、Redis的缓存击穿、缓存穿透、缓存雪崩、缓存预热分别是什么?如何解决?

  • 缓存击穿
    • 描述:某一个热点的key失效了,突然间有大量集中的请求去访问它,导致缓存无法命中,压力都直接集中到数据库中
    • 解决办法:加上互斥锁或者队列,也可以采用阻塞其他线程的方式,让请求一个一个访问;或者设置热点用户为永不过期
  • 缓存穿透
    • 描述:数据库中没有某个key,缓存中也没有这个key,当恶意攻击时,不断发送请求这个key的数据,导致大量请求直接越过缓存去访问数据库,造成数据库压力过大
    • 解决办法:直接把请求过来的无效的key存入缓存,使用布隆过滤器
  • 缓存雪崩
    • 描述:缓存某一时刻大量的key失效,然后大量的访问请求数据库,造成数据库压力增大
    • 解决办法:设置key的失效时间尽量不要在同一时间段,让key均匀的失效;或者设置分级缓存,一级缓存失效了可以访问二级缓存等;或者设置热点数据永远不会失效
  • 缓存预热
    • 描述:在系统上线之前,先提前把一些数据加入到缓存当中,当数据量不大的时候,可以直接等到系统上线再走缓存,当数据量很大的时候,就需要提前跑一下测试系统,看看哪些是热点数据,然后把热点数据先加入缓存

17、Spring事务的传播行为和隔离级别?

事务的7种传播行为

事务的传播行为含义备注
REQUIRED如果调用的方法没有事务就创建事务,有事务就用以前的事务Spring默认的传播行为
SUPPORTS如果调用的方法没有事务就不用事务,有事务就用以前的事务
MANDATORY方法必须在事务中进行如果当前方法没有事务,那么抛异常
REQUIRES_NEW不管当前方法是否有事务,都在新事务中运行
NOT_SUPPORTED不支持事务,当前方法没有事务那就不使用,有事务就挂起直到这个方法结束适用于不需要事务的sql语句
NEVER不支持事务,只在没有事务的环境中使用如果当前方法有事务,那么抛异常
NESTED嵌套事务,调用的方法如果抛异常那么只回滚自己内部的sql,不回滚主方法的sql

事务的隔离级别

事务容易产生的问题:

  • 脏读:事务1读取到事务2修改但没提交的数据
  • 不可重复读:事务1读到了某数据,然后被事务2修改并提交,事务1读不到原来的数据了
  • 幻读:事务1读取了记录,然后事务2对表或记录进行了修改,导致事务1读取到的和原来的数据的结构不一样
隔离级别脏读不可重复读幻读
读未提交(Read-Uncommited)
读已提交(Read-Commited)×
可重复读(Repeatable-Read)××
序列化×××

18、OSI协议和TCP/IP协议介绍?

网络协议分层主要是对复杂的网络进行简化和解耦,确保各层独立,每层只做本层所需要提供的功能,然后与上一层和下一层进行通信即可。

OSI七层协议(应表会运网数物):ISO提出的标准协议,但是由于分层太多,复杂也不实用,现在基本不用了。

  • 应用层
  • 表示层
  • 会话层
  • 运输层
  • 网络层
  • 数据链路层
  • 物理层

现在用到的一般也就是TCP/IP的四层或五层模型:

在这里插入图片描述

四层的话:

  • 应用层
  • 运输层
  • 网络层
  • 网络接口层

如果是五层的话:

  • 应用层
  • 运输层
  • 网络层
  • 数据链路层
  • 物理层

19、什么是线程死锁?怎么解决死锁的问题?

死锁概念

死锁是指两个对象分别持有锁,并且相互等待对方释放锁的现象。

实现的代码:

public class DeadLock {
    public static void main(String[] args) {
        Person person1 = new Person(0,"张三");
        Person person2 = new Person(1,"李四");
        person1.start();
        person2.start();
    }
}
​
/**
 * 勺子
 */
class Spoon{
​
}
​
/**
 * 叉子
 */
class Fork{
​
}
​
/**
 * 人
 */
class Person extends Thread{
    static Spoon spoon = new Spoon();
    static Fork fork = new Fork();
​
    int choice;
    String personName;
​
    public Person(int choice,String personName){
        this.choice = choice;
        this.personName = personName;
    }
​
    @Override
    public void run() {
        try {
            eat();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
​
    public void eat() throws InterruptedException {
        if (choice == 0){
            synchronized (spoon){
                System.out.println(this.personName + "拿到了勺子");
                Thread.sleep(1000);
                synchronized (fork){
                    System.out.println(this.personName + "拿到了叉子");
                }
            }
        }else{
            synchronized (fork){
                System.out.println(this.personName + "拿到了叉子");
                Thread.sleep(1000);
                synchronized (spoon){
                    System.out.println(this.personName + "拿到了勺子");
                }
            }
        }
    }
}

死锁产生的条件

  • 某个资源在某时间内只允许一个线程操作;(只有一个)
  • 该资源不能被夺走,只能由该线程自己释放;(我不想给)
  • 某线程占用了一个资源,还想占用其他资源;(我还想要)
  • 循环关系,即A需要B的资源,B需要C的资源,C需要A的资源。(想要你的)

如何解决死锁的问题?

  • 如果某个线程的资源不能夺走,而它又访问了其他资源而没拿到,那么就释放它,这样就允许其他线程访问。
  • 既然你占了一个资源,还想占另一个,那么不如直接让一个拿到全部资源再执行。(《图解Java多线程设计模式》中的成对拿取方式)
  • 既然是循环,那么不如按照一定分配次序来拿,这种方式不会造成资源的闭环。(《图解Java多线程设计模式》中的以相同顺序拿取资源

20、JVM有哪些垃圾回收器?详细说明?

Serial收集器

Serial收集器是历史最悠久、最基础的收集器,但它是单线程的收集器。不仅在收集线程时只会用一个线程来收集,而且在收集时还会暂停其他的工作线程(STW:stop the world)。新生代采用复制算法,老年代采用标记-整理算法进行垃圾收集

Serial收集器简单高效,额外的内存消耗非常小,而且单线程不存在线程切换的问题,所以收集效率很高。

ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本。 它的优点是可以与CMS收集器进行配合,ParNew负责新生代收集,CMS负责老年代收集。同样它也存在暂停其他线程的问题。

Parrallel Scavenge收集器

称为吞吐量优先收集器,它关注的是最高效率利用资源、处理程序运算任务,这种收集器会根据设定的参数去自适应以达到最优的等待时间和吞吐量。其中有两个最主要的参数:-XX:MaxGCPauseMillis参数,收集器会尽可能保证垃圾回收的时间不超过这个值;-XX:GCTimeRatio表示垃圾回收和总时间的占比:1/(1+N) ,比如设置为99,那么垃圾回收时间的占比就是1%。

Serial Old收集器

也是单线程收集器,是Serial收集器的老年代版本,使用标记-整理算法

Parallel Old收集器

是Parrallel Scavenge的老年代版本,支持多线程并行收集,也是采用的标记-整理算法。

CMS收集器

是一种追求获取最短回收停顿时间的老年代垃圾收集器,它采用的是标记-清除算法

其垃圾回收过程一共分为四步:

  • 初始标记:初始标记仅仅标记GC Roots能直接达到的对象;
  • 并发标记:并发标记是根据刚才标记的关联对象开始遍历整个对象图,这个过程耗时较长但是不会停顿;
  • 重新标记:重新标记是在上面标记完之后,标记那些因为没有停止线程而产生变化的对象;
  • 并发清除:最后是进行清理阶段。

其中初始标记和重新标记依然需要stop the world,CMS无法集中处理浮动垃圾(在并发标记和并发清理阶段没停止其他线程,而导致当次的垃圾不能被标记清理),如果浮动垃圾过多会产生CMF(Concurent Mode Failure)造成stop the world来完成一次Full GC。同时标记-清除算法会产生垃圾碎片。

G1收集器

G1成为了现在默认使用的GC垃圾回收器,它可以面向堆中任何的部分进行垃圾回收,不再是基于某个分代,而是哪块内存中垃圾数量最多、收集效益最大,去收集哪一块儿。

G1是把堆划分为多个大小相等的Region区域,Region中采用的是标记-整理算法,两个Region中间采用的是复制算法。

垃圾回收过程:

  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,这阶段需要停顿线程,但耗时很短
  • 并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。
  • 最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行
  • 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。这个阶段也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。

21、Spring是如何创建一个对象的?

先想一想创建一个bean都需要哪些内容?

  • 需要可以读取配置文件的类
  • 需要读取配置文件后可以执行反射的工具类

这里还需要知道一个类叫做DefaultListableBeanFactory;在org.springframework.beans.factory.support包下。看它的继承结构图(点这个类去选择Diagram):

image-20211016223240162

这个类有个子类叫做XmlBeanFactory,它可以读取xml文件的BeanDefinition,其中包含了一个XmlBeanDefinitionReader的reader属性,可以用来读取配置文件。

读取的步骤如下:;

  • 通过继承自AbstractBeanDefinitionReader类的方法,使用ResourceLoader将资源文件路径转换为对应的Resource文件
  • 通过DocumentReader对Resource文件进行转换,将Resource文件转换为Document文件
  • 通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析