常见面试题总结篇
常见面试题总结篇
- (一)Java 基础
- (二)IO
- (三)集合容器
- (四)多线程
- ==(1)并行和并发有什么区别?什么是线程安全?==
- (2)线程和进程的区别
- (4)创建线程有哪几种方式?
- (6)线程锁有哪几种?乐观锁和悲观锁的区别?CAS是什么?可能造成的问题和解决方法(ABA问题--加版本号)
- (6)线程有哪些状态?
- (7)sleep() 和 wait() 有什么区别?
- (8)notify()和 notifyAll()有什么区别?
- (9)线程的 run()和 start()有什么区别?
- (17)==ThreadLocal 是什么?有哪些使用场景?==
- (18)说一下 synchronized 底层实现原理?
- (19)==synchronized 和 volatile 的区别是什么?==
- (20)==synchronized 和 Lock 有什么区别?==
- (五)反射
- (六)泛型
- (七)异常
- (八)网络
- (九)设计模式
- (十)算法
- (十)Spring/Spring MVC(框架和中间件要结合项目经验)
- (1)为什么要使用 spring?
- (2)==解释一下什么是 aop,解决什么问题(实现原理,实现流程,jdk动态代理和Cglib动态代理的区别)?==
- (3)==解释一下什么是 ioc(实现原理,实现流程)?==
- (4)spring 常用的注入方式有哪些?
- (5)spring 中的 bean 是线程安全的吗?
- (6)spring 支持几种 bean 的作用域?
- (8)==描述一下spring bean的生命周期(创建Bean的过程)==?
- (9)spring 事务实现方式有哪些?
- (10)说一下 spring 的事务隔离?
- (11)==说一下 spring mvc 请求的执行流程?==
- (12)spring mvc 有哪些组件?
- (13)@RequestMapping 的作用是什么?
- (14)@Autowired 的作用是什么?
- (15)什么是Tomcat?
- (十一)Spring Boot/Spring Cloud
- (十二)Mybatis
- (十三)MySql(基础语句+索引+锁+事务+优化)
- (十四)Redis
- (十五)JVM
- (1)==说一下 jvm 的主要组成部分?及其作用?(要能手画)==
- (5)==堆分为哪几个部分?为什么持久代改为了元空间(直接内存)?==
- (6)==说一下类加载的执行过程?双亲委派是干嘛的?如果我自己写一个String类能不能加载进内存?==
- (7)怎么判断对象是否可以被回收?
- (8)==java 中都有哪些引用类型?==
- (9)==说一下 jvm 有哪些垃圾回收算法(GC算法)?它们的优缺点?==
- (10)==说一下 jvm 有哪些垃圾回收器,详细介绍一下GC算法?==
- (11)详细介绍一下 CMS 垃圾回收器?
- (14)HotSpot 为什么要分为新生代和老年代?简述分代垃圾回收器是怎么工作的?
- (25)Minor Gc 和 Full GC 有什么不同呢?
- (19)如何判断一个常量是废弃常量
- (20)如何判断一个类是无用的类
- (16)项目问题
(一)Java 基础
(1)JDK 和 JRE 有什么区别?
1-JDK: 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。
2-JRE: 普通用户而只需要安装JRE(Java Runtime Environment)来运行Java程序。而程序开发者必须安装JDK来编译、调试程序。
3-JVM: 当我们运行一个程序时,JVM负责将字节码转换为特定机器代码,JVM提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是java程序可以一次编写多处执行的原因。
4-区别与联系:
JDK用于开发,JRE用于运行java程序 ;
JDK和JRE中都包含JVM ;
JVM是java编程语言的核心并且具有平*立性。
(2)== 和 equals 的区别是什么?
首先,类对象的创建是在栈内存中开辟内存空间,用来放置对象名称,对象实例化会在堆内存中开辟内存空间,用来存放对象的具体内容,栈内存中的引用会指向堆内存中的地址。
然后,==就是用来判断两个对象的具体内容和堆内存的地址是不是都相同,而equals是专门用来判断对象的具体内从是不是相同,不会对对象的地址进行判断。
(3)两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
不一定
(4)static 在 java 中有什么作用?
static声明的属性会放在内存的全局区,被所有对象所共享。static可以声明属性、方法和内部类。
1-当static声明属性时,只能在类的主体里定义,不能在方法里定义。static属性在类加载的时候就开始创建,创建完的属性放在内存的全局数据区,栈内存中的类对应的所有对象都可以调用这个属性,但是通常都用类名称直接调用static属性。
2-当static声明方法时,这个方法可以直接使用类名称调用,在static方法中不能调用非static的属性和方法,因为static是在类加载时就创建了,而非static的属性和方法是在对象实例化的时候才放进堆内存。
3-当static声明内部类的时候,这个类就会变成外部类,同样不能访问非static的外部类属性。
(5)final 在 java 中有什么作用?
final可以用来声明类、方法、变量。
1-当final声明类时,这个类不能被继承,例如抽象类不能用final声明
2-当final声明方法时,这个方法不能被重写,例如抽象方法不能被fianl声明
3-当final声明变量时,这个变量就变成了常量,只能被赋值一次,可以在创建的时候赋值,也可以创建以后再赋值,但是一经赋值就不能再修改。另外,注意final声明的变量要全部大写。
(6)java 中的 Math.round(-1.5) 等于多少?
-1
(6)String 属于基础的数据类型吗?
不属于,基础的数据类型有四种,分别是整型、字符型、浮点型和布尔型,字符串只是比较常用。
(7)java 中操作字符串都有哪些类?它们之间有什么区别?
主要有三个String、StringBuffer、StringBuilder。
1-首先String类型是被final声明的,所有一旦创建不可修改,String是线程安全的
2-StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,没有用final声明,所以可以修改。另外,StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
在工作中,操作少量数据时用String,操作大量数据的时候用StringBuffer,例如download数据到csv文件要用StringBuffer拼接文件名,又例如在编写Dao层sql时也用到StringBuffer拼接较复杂的sql语句。StringBuilder 虽然性能好一点,但是不用,因为会有线程不安全的风险。
(8)String str="i"与 String str=new String(“i”)一样吗?
不一样,一个是在字符串池中找匹配的对象,找不到再实例化对象,而使用new就直接实例化对象,用到了享元模式
(9)如何将字符串反转?
(10)String 类的常用方法都有那些?
1-split:根据指定的字符进行字符串分割
2-length:求长度
3-equals:判断是否相等
4-substring:截取子字符串
5-trim:清除左右空格
(11)抽象类必须要有抽象方法吗?
首先,抽象类的作用类似“模板”,目的是让设计者依据它的格式来修改并创建新的类。但是并不能直接由抽象类创建对象,只能通过子类来创建对象。
1-抽象类不一定有抽象方法,有抽象方法必是抽象类
2-抽象类可以有构造方法,但是不能实例化对象,只能由子类实例化对象
3-抽象类和抽象方法不能使用private和final声明,否则就不能被继承和重写了
(12)普通类和抽象类有哪些区别?
(14)java基类Object中的常见方法?
1-equals
2-hashCode
3-toString
(15)接口和抽象类有什么区别?
1-子类只能继承一个抽象类,不能继承多个;子类可以实现多个接口
2-抽象类和接口中都可以包含静态成员变量。抽象类可以定义public,protected,package,private,静态和非静态属性,final和非final属性;但是接口中声明的属性,只能是public、静态、final的【抽象类中可以有普通成员变量,接口中没有普通成员变量】
3-抽象类中可以包含静态方法,接口中不能包含静态方法
4-抽象类可以有构造方法,接口中不能有构造方法。
5-抽象类中可以有普通成员变量,接口中没有普通成员变量
6-抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
7-优先选用接口,尽量少用抽象类
8-抽象类和接口都不能被实例化。但是可以创建对象,向上转型,让其子类或者实现类实例化
(二)IO
(1)java 中 IO 流分为几种?
字节流:操作byte类型,操作时不会到内存缓冲区,而是直接对文件本身进行操作
字符流:操作char类型,操作时会到内存缓冲区,通过缓冲区再操作文件
内存操作流:把内容写入内存,把内存的数据输出
管道流:进行两个线程间的通信
数据操作流:
压缩流:完成对一个文件或者文件夹的压缩
(2)BIO、NIO、AIO 有什么区别?
BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装API。
1-BIO是Blocking IO,也就是阻塞IO模式,数据的读取写入必须阻塞在一个线程内等待完成,等待期间无法执行其他任务。连接数不高的情况下使用,百万级连接就要使用NIO
2-NIO是Non-blocking IO,是一种同步非阻塞的IO模型,在java1.4后引入,新增了Channel通道、Selector选择器、Buffer缓冲等,支持面向缓冲的,基于通道的I/O操作方法
3-AIO是Asynchronous I/O,也就是异步非阻塞的IO模型,Java1.7引入。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
(3)Files的常用方法都有哪些?
1-public static final String pathSeparator;:表示的是分隔符,也就是“;”
2-public static final String separator;:表示的还是分隔符,不过是“\”
3-public File(String var1) :File类的构造方法,用来创建File的对象,并且指定了文件的路径,File类的所有操作都面向这个文件
4-public boolean createNewFile() throws IOException:这个方法被调用就会创建一个名为构造器中String的文件,又因为这个方法向上抛出异常,所以在调用的时候必须对异常进行捕捉或者向上抛出
5-delete:删除文件
6-exists:判断文件是否存在
7-isDirectory:判断给定的路径是不是一个目录
8-length:返回文件的大小
9-public String[] list():把给定目录的全部内容,罗列放在一个String数组中,只是列出名称,不列出文件的内容
10-public File[] listFiles() :列出给定目录的全部内容,放在一个File数组中,列出的是文件的完整路径
11-mkdir() :创建一个目录
12-renameTo(File var1):为已有的文件重新命名
(三)集合容器
(1)java 容器都有哪些?
1-List接口:ArrayList、LinkedList
2-Set接口:HashSet(调用hashmap)、TreeSet
3-Map接口:HashMap、TreeMap、HashTable、ConcurrentMap
(2)Collection 和 Collections 有什么区别?
Collection 是接口,Collections 是一个类
(3)List、Set、Map 之间的区别是什么?
1-List(对付顺序的好帮手): List接口存储一组不唯一的(可以有多个元素引用相同的对象),有序的对象。
2-Set(注重独一无二的性质): 不允许重复的集合。不会有多个元素引用相同的对象。
3-Map(用Key来搜索的专家): 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
(4)HashMap 和 Hashtable 有什么区别?
1-HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式。
2-但是HashTable 内部的方法基本都经过synchronized 修饰,所以HashTable 是线程安全,而HashMap 是非线程安全的,又因为线程安全问题,HashMap 的效率要比HashTable 高一点,HashMap 已经被淘汰,要想实现线程安全可以使用ConcurrentHashMap 。
3-HashMap 中,key值允许为空null,而HashTable 中如果put进来一个null值key,就会抛出空指针异常。
4-初始容量大小和每次扩充容量大小的不同。Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍
5-底层数据结构不同,HashMap 在解决哈希冲突时,如果链表长度大于阈值(默认为8)时,将链表转化为红黑树。Hashtable 没有。
(5)HashCode的作用?
首先HashCode就是散列值,所有对象都有一个对应的HashCode,HashCode在可以大大提高数据的保存和查找的速度。例如List中没有用到HashCode,保存和查找数据时要沿着链表挨个查找,而HashMap中用hash算法处理HashCode可以迅速找到保存的位置,同样查找的时候可以根据HashCode计算出下标,到数组中进行定位,迅速找到查找的数据在数组中的位置。
(6)如何决定使用 HashMap 还是 TreeMap?
1-HashMap 的key没有顺序、可以为空null、不能重复
2-TreeMap 继承SortedMap接口,所以key值可以排序,不能重复
(7)说一下 HashMap 的实现原理?(必会)
1)HashMap是无序的吗?
因为HashMap没有继承继承SortedMap接口,所以是无序的。
2)Map中有序的子类有哪些?
有序的Map实现类有TreeMap和LinkedHashMap,都继承了继承SortedMap接口
3)它们是如何保证它的顺序的?哪个有序的实现比较好?
LinkedHashMap是按照插入顺序排序,而TreeMap是按照Key的自然顺序或者Comprator的顺序进行排序,在实现原理上LinkedHashMap是双向链表,TreeMap是红黑树,也就是排序二叉树原理。
4)有没有更高效的实现方法?
5)HashMap的key值和value值可以为空吗?key可以重复吗?是怎么实现key唯一的?
1-key值和value值可以为空
2-HashMap和HashSet的底层数据结构都是维护了一张哈希表HashTable,每次加入元素到HashTable时都会先判断元素是否重复。判断重复的方式就是先比较哈希值hashcode,每个元素对象都有自己的hashCode,如果hashCode不同就加入HashTable,如果hashCode相同那就说明元素的地址相同,再接着用equals比较元素的内容是否相同,如果也相同的话就说明地址和内容都相同,判定确实存在重复元素,JVM就不会再加入数据。
6)HashMap不是线程安全的么?
HashMap是非线程安全的,
7)并发下使用什么Map?
ConcurrentHashMap
8)HashMap内部的实现原理是什么,例如如何实现扩容、hashcode、存储方式、默认容量?
0-默认值:
loadFactor负载因子,默认为0.75
threshold表示所能容纳的键值对的临界值(阈值),threshold = capacity * loadFactor,当Size>=threshold的时候,那么就要考虑对数组的扩容了
size是hashmap中实际存在的键值对数量
modCount字段用来记录Hashmap内部结构发生变化的次数
DEFAULT_INITIAL_CAPACITY默认容量是16,Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容
1-存储数据put
首先,JDK1.8之后的HashMap结构为数组+链表+红黑树,采用这种结构来解决哈希冲突。
当我们调用put方法传入一个k-v,这个k-v会放在实现Entry接口的Node节点中(JDK1,8就把Entry类换成Node类了)。在hash()函数中通过key值获取到元素的hashCode值,然后hashCode值右移16位,把右移后的值与原来的hashCode做异或运算,并返回hash值结果。(其中h>>>16,在JDK1.8中,优化了高位运算的算法,使用了零扩展,无论正数还是负数,都在高位插入0)
在putVal()方法中,取得上面异或返回的hash值和数组长度n,通过(n-1)&hash(hash和15取异或)获取该对象的键在hashmap中的位置。((n-1)&hash就等价于hash%n,且&运算的效率高于%运算)
Entry找到数组的下标位置后,如果该位置是空的,就可以直接把Entry放进去。但是如果这个位置有值了,就会出现hash冲突,这时要先根据hashCode和equals方法判断元素是否重复,如果判断元素重复了就直接覆盖上去,如果判断元素没有重复就要启用链表来存放,使用尾插法把新的Node插入到链表的尾部。
注意:JDK1.7用的是头插法,为什么1.8要改成尾插法呢?因为HashMap不是线程安全的,在单线程操作下不会出现问题,但是在多线程情况下,当数组扩容后进行Rehash的时候,如果两个线程同时Rehash,就可能导致链表会形成死循环。(高并发场景想要线程安全可以选用ConcurrentHashMap)
随着数据的插入,链表也会越来越长,效率也会越来越低,这时要考虑把结构转成红黑树。当链表的长度大于8时,链表转成红黑树,进行二叉树排序放置。当红黑树的长度小于6的时候,红黑树转成链表,为什么是8和6?因为链表的时间复杂度是O(n),红黑树的时间复杂度O(logn),虽然红黑树的复杂度是优于链表的,但是在节点少的时候,红黑树所占空间较大,所以只会在节点足够多的时候才会舍弃链表。通常情况下,链表长度很难达到8,但是特殊情况下链表长度为8,哈希表容量又很大,造成链表性能很差的时候,只能采用红黑树提高性能,这是一种应对策略。
2-扩容机制Resize
随着数据不断的插入,数组的空闲位置越来越少,当数组中的实际存放数量Size>阈值threshhold时,就表示可以扩容了,其中:threshhold(阈值)=capacity(数组容量,默认16) * loadFactor(负载因子,默认0.75)。也就是说当数组的实际存放量数量达到了 16 * 0.75 = 12 时,就可以开始对数组进行扩容了。
扩容的方式就是创建一个新的Node空数组,长度是原来数组的2倍(32),然后调用ReHash方法,遍历原来的Node数组,把所有的Node重新使用hash函数放到新数组中。此时要求新数组的长度必须是2的幂数,这样做是为了减少hash函数的冲突次数,实现一个尽量分布均匀的hash函数。
3-查找数据get
当我们调用get方法从HashMap中取出数据时,都是根据key值来取数据,同样先取得key值的hashCode,然后调用hash函数找到元素存储的数组下标。如果该下标的位置只有一个数,那就可以直接取出了,但是如果这个位置有很多个Entry,那就要顺着链表的头结点,或者是红黑树的根节点,一个一个向下查找,查找判断的是hashCode,如果hashCode对应上了,说明这个数据就是我们要查找的。
9)hash碰撞的解决方法?
在putVal()方法中,取得上面异或返回的hash值和数组长度n,通过(n-1)&hash(hash和15取异或)获取该对象的键在hashmap中的位置。((n-1)&hash就等价于hash%n,且&运算的效率高于%运算)
(8)说一下 HashSet 的实现原理?
HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 、writeObject()、readObject()是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。
HashSet只用来存储对象,要求加入的对象不能重复,当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功
(9)ArrayList 和 LinkedList 的区别是什么?
1-ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全
2-Arraylist 底层使用的是 Object 数组,ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n-i);LinkedList 底层使用的是 双向链表 数据结构,插入,删除元素时间复杂度不受元素位置的影响,近似 O(1)
3-ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
(10)如何实现数组和 List 之间的转换?
(11)ArrayList 和 Vector 的区别是什么?
(12)Array 和 ArrayList 有何区别?
Array是定长的,ArrayList的长度是可变的
(13)在 Queue 中 poll()和 remove()有什么区别?
(14)哪些集合类是线程安全的?
1)喂(Vector):就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
2)S(Stack):堆栈类,先进后出
3)H(hashtable):就比hashmap多了个线程安全
4)E(enumeration) :枚举,相当于迭代器
(15)迭代器 Iterator 是什么?
是一个用来遍历集合的接口,是集合遍历的标准形式
(16)Iterator 怎么使用?有什么特点?
(17)Iterator 和 ListIterator 有什么区别?
Iterator 接口的主要功能是从前往后单向输出,但是如果想实现从后往前或者从前往后的双向输出,就可以使用ListIterator 接口
(18)怎么确保一个集合不能被修改?
使用
(19)comparable 和 Comparator的区别
1-comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序
2-comparator接口实际上是出自 java.util 包它有一个compare(Object obj1, Object obj2)方法用来排序
3-一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo()方法或compare()方法,
(四)多线程
(1)并行和并发有什么区别?什么是线程安全?
并发:一个处理器可以同时处理多个任务。这是逻辑上的同时发生。
并行:多个处理器同时处理多个不同的任务。这是物理上的同时发生。
并发:一个人同时吃三个苹果。并行:三个人同时吃三个苹果。
线程安全:在多线程环境中,操作共享数据时,能永远保证程序的正确性。
(2)线程和进程的区别
1-进程:进程是线程一次动态执行过程,它需要经历从代码加载、代码执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。
2-线程:线程是比进程更小的执行单位,多线程是指进程在执行过程中可以产生多个更小的程序单元,一个进程可以包含多个同时执行的线程。
3-例如你打开一个word文档,那就是开启了一个进程,而在这个进程纸上又有很多其他程序在运行,比如拼写检查,那么这些程序就是一个小的线程。
(3)守护线程是什么?
当 JVM 中不存在任何一个正在运行的非守护线程时,则 JVM 进程即会退出。
(4)创建线程有哪几种方式?
1-继承Thread类(多线程实现类):先继承Thread多线程实现类,并且覆写Thread类中的run方法,使用时调用tart方法让线程进入就绪队列。Thread类也是Runnable接口的子类,不适合多个线程共享资源
2-实现Runnable接口(推荐使用):适合多个相同程序代码的线程去处理统一资源的情况;可以避免由于Java的单继承特性带来的局限;增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
(5)说一下 runnable 和 callable 有什么区别?
(6)线程锁有哪几种?乐观锁和悲观锁的区别?CAS是什么?可能造成的问题和解决方法(ABA问题–加版本号)
1)线程锁有:读写锁、互斥锁、乐观锁、悲观锁等等
为了实现事务处理的可串行性,数据库在执行update / insert / delete等语句时,会对相应数据加锁。当其他事务处理需要修改已被加锁的数据时,就会等待锁被释放。
2)悲观锁:
悲观锁(每次拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿数据就会阻塞直到他拿到锁,也就是说共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程)——适用于“多写”情况
多线程的悲观锁是一种利用数据库内部机制提供的锁的方法,也就是对更新的数据加锁,这样在并发期间一旦有一个事务持有了数据库记录的锁,其他的线程将不能再对数据进行更新了。
在SQL语句里加上for update语句,意味着将持有对数据记录的行更新锁(因为是主键查询),意味着在高并发情况下,当一条事务持有了这个更新锁才能接着往下操作,其他的线程想要更新这个数据就必须得等待,这样就不会出现超发现象引发的数据一致性问题
3)乐观锁
乐观锁(不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据)——适用于“多读”情况
乐观锁是一种不会阻塞其他线程并发的机制,不会使用数据库的锁进行实现,乐观锁使用的是CAS原理
3.1)CAS原理概述
对于多个线程共同的资源,先保存一个旧值(Old Value),例如抢红包进入线程后,先查询当前剩余红包数并且把这个旧值保存起来,然后经过逻辑处理后,比较数据库现在的值和旧值是否一致。如果一致就说明数据保持了一致性,可以进行扣减红包的操作,如果值和旧值不一致就说明这个值已经被其他线程修改了,不再进行操作。
但是CAS原理有一个问题,就是ABA问题。
3.2)ABA问题
例如保持旧值为A,进行逻辑操作的期间,旧值被其他线程修改成了B,又修改成了A,这个时候再进行值与旧值比较的时候,发现值和旧值一致,会认为这个值没有被修改,然后进行更新。
ABA问题是因为业务逻辑存在回退的可能性。可以加入一个逻辑属性,比如加入一个版本号(version),只要修改了一次值,版本号就会递增且不会倒退,这样版本号就不会出现回退的现象,也就可以准确判断出值与旧值是否一致。
3.3)乐观锁重入机制
上面说的加入乐观锁,就会有很多数据sql执行失败。例如用户抢购时,很多人的订单会因为乐观锁的存在而提交失败,最后货物还有很多没卖出去。这个时候就要考虑提高请求的成功率,如果第一次请求失败,那就再自动请求一次。
有两种方法:1-加入时间戳执行乐观锁重入,2-加入限制重入次数执行乐观锁重入
1-加入时间戳执行乐观锁重入
在一定时间戳内,例如100毫秒,不成功的请求会循环到成功为止,知道超出100毫秒后,不成功的请求才会退出,返回失败
2-加入限制重入次数执行乐观锁重入
有时候时间戳并不是很稳定,也会随着系统的空闲或者繁忙导致重试次数不一。可以限定重入次数为3次,尝试3次请求后如果还是不成功,就退出并返回失败。
(6)线程有哪些状态?
1-创建状态:用构造方法创建一个线程对象后,新的线程对象便处于新建状态,已经有了响应的内存空间和其他资源
2-就绪状态:调用线程的start方法就可以启动线程,启动后线程进入就绪状态。此时,线程进入线程队列排序,等待CPU,已经具备运行条件
3-运行状态:当就绪状态的线程被调用并且获得处理器资源时,线程就进入了运行状态,此时,自动调用该线程对象的run方法
4-堵塞状态:一个正在执行的线程,爱被认为挂起或者需要执行耗时的输入/输出操作时,例如调用sleep、suspend、wait等方法时,线程会暂时中断自己的执行,并且让出CPU处理器,进入阻塞状态。当引起堵塞的原因被消除后,线程就可以再次进入排队队列,转入就绪状态。
5-死亡状态:线程调用stop方法或者run方法执行结束后,进入死亡状态,不再具有继续运行的能力。
(7)sleep() 和 wait() 有什么区别?
1-sleep(long var0):在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。不推荐使用。sleep()使当前线程进入阻塞状态,在指定时间内不会执行。
2-wait(): 在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
(8)notify()和 notifyAll()有什么区别?
1-等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。
2-锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待
3-notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。
(9)线程的 run()和 start()有什么区别?
1-run()方法是
2-调用 start() 方法是用来启动线程的,让线程进入就绪队列等待,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法
(10)创建线程池有哪几种方式?
1-newFixedThreadPool:定长线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程
2-newCachedThreadPool:可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制
3-newScheduledThreadPool:定长线程池,可执行周期性的任务
4-newSingleThreadExecutor:单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行
5-newSingleThreadScheduledExecutor:单线程可执行周期性任务的线程池
6-newWorkStealingPool:
(11)线程池都有哪些状态?
(12)线程池中 submit()和 execute()方法有什么区别?
(13)在 java 程序中怎么保证多线程的运行安全?
可以使用两种方法解决:同步代码块、同步方法(synchronized 修饰)
Synchronized的作用主要有三个:
1-确保线程互斥的访问同步代码
2-保证内存可见性
3-解决指令重排序问题
(14)多线程锁的升级原理是什么?
(15)什么是死锁?
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
(16)怎么防止死锁?
1-资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
2-只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
3-可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
4-资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
(17)ThreadLocal 是什么?有哪些使用场景?
ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
(18)说一下 synchronized 底层实现原理?
1)synchronized作用
1-原子性:synchronized保证语句块内操作是原子的
2-可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同步回主内存”实现)
3-有序性:synchronized保证有序性(通过“一个变量在同一时刻只允许一条线程对其进行lock操作”)
2)synchronized的使用
1-修饰实例方法,对当前实例对象加锁
2-修饰静态方法,多当前类的Class对象加锁
3-修饰代码块,对synchronized括号内的对象加锁
(19)synchronized 和 volatile 的区别是什么?
volatile的特点(线程可见性,禁止指令重排)
1-volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2-volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
volatile仅能实现变量的修改可见性,但不具备原子特性,而synchronized则可以保证变量的修改可见性和原子性.
3-volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
4-volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化.
(20)synchronized 和 Lock 有什么区别?
两者都是锁,用来控制并发冲突,区别在于Lock是个接口,提供的功能更加丰富,除了这个外,他们还有如下区别:
1-synchronize自动释放锁,而Lock必须手动释放,并且代码中出现异常会导致unlock代码不执行,所以Lock一般在Finally中释放,而synchronize释放锁是由JVM自动执行的。
2-Lock有共享锁的概念,所以可以设置读写锁提高效率,synchronize不能。(两者都可重入)
3-Lock可以让线程在获取锁的过程中响应中断,而synchronize不会,线程会一直等待下去。lock.lockInterruptibly()方法会优先响应中断,而不是像lock一样优先去获取锁。
4-Lock锁的是代码块,synchronize还能锁方法和类。
(21)synchronized 和 ReentrantLock 区别是什么?
(22)说一下 atomic 的原理?
(五)反射
(1)什么是反射?
正常情况下,我们需要创建并实例化一个类的对象,才能通过对象调用属性和方法。但是java中也允许使用Class类通过一个实例化对象反向得到对应类的完整信息。
getClass方法是从Object中继承而来的,所有类的对象实际上也就是都是Class类的实例,所以多有的对象都可以转变为Class类型表示。
(2)什么是 java 序列化?什么情况下需要序列化?
序列化就是实现Serializable接口,把一个对象变为二级制的数据流,二进制是机器能够识别的语言,所以序列化后可以方便的实现对象的传输或者存储。把二进制的数据流再转换成对象,就是反序列化。
(3)动态代理是什么?有哪些应用?
(4)怎么实现动态代理?
# (5)对象拷贝
为什么要使用克隆?
如何实现对象克隆?
深拷贝和浅拷贝区别是什么?
# (6)Java Web
jsp 和 servlet 有什么区别?
jsp 有哪些内置对象?作用分别是什么?
说一下 jsp 的 4 种作用域?
session 和 cookie 有什么区别?
说一下 session 的工作原理?
如果客户端禁止 cookie 能实现 session 还能用吗?
spring mvc 和 struts 的区别是什么?
如何避免 sql 注入?
什么是 XSS 攻击,如何避免?
什么是 CSRF 攻击,如何避免?
(六)泛型
(七)异常
(1)throw 和 throws 的区别?
1-用在方法体上:在定义一个方法时可以使用throws关键字声明,表示这个方法不处理异常,而是交给方法的调用处进行处理,谁调用这个方法谁再用try…catch进行处理。
2-用在方法内部:使用throw关键字人为的抛出一个异常,抛出时直接抛出异常类的实例化对象。
(2)final、finally、finalize 有什么区别?
(3)try-catch-finally 中哪个部分可以省略?
try不能省略,catch和finally都是可以省略的,但是不能同时都省略,只能省略其中一个
(4)try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
会
(5)常见的异常类有哪些?说出五种常见的RuntimeException?
常见的异常分为运行时异常和非运行时异常
1)运行时异常RuntimeException
1-下标越界异常:ArrayIndexOutOfBoundsException
2-除数不能为0异常:ArithmeticException
3-空指针异常:NullPointerException
4-非法参数异常:IllegalArgumentException
5-类转换异常:ClassCastException
2)非运行时异常CheckedException
(6)异常中的error和exception有什么区别?
Exception和Error都是Throwable的子类
1)Exception:表示的是程序中出现的问题,可以直接使用try…catch处理,是最大的捕捉范围。多个异常要分别进行捕获,不要直接使用Exception捕获全部异常。
2)Error:一般值得是JVM错误,程序中无法处理
(八)网络
(1)http 响应码 301 和 302 代表的是什么?有什么区别?
(2)forward 和 redirect 的区别?
1-forword是服务器内部的重定向,服务器直接访问目标地址的 url网址,把里面的东西读取出来,但是客户端并不知道,因此用forward的话,客户端浏览器的网址是不会发生变化的。redirect是服务器根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,所以地址栏显示的是新的地址。
2-由于在整个定向的过程中用的是同一个request,因此forward会将request的信息带到被重定向的jsp或者servlet中使用。即可以共享数据。redirect不能共享
3-forword 一般用于用户登录的时候,根据角色转发到相应的模块。redirect一般用于用户注销登录时返回主页面或者跳转到其他网站
4-forword效率高,而redirect效率低
5-forword只有一次请求;而redirect有两次请求,
6-forword转发是服务器上的行为,而redirect重定向是客户端的行为
(3)简述 tcp 和 udp的区别?
(4)简述TCP/IP协议?
tcp/ip协议j是指传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的。TCP/IP 定义了电子设备(比如计算机)如何连入因特网,以及数据如何在它们之间传输的标准。TCP/IP是一个四层的分层体系结构。高层为传输控制协议,它负责聚集信息或把文件拆分成更小的包。低层是网际协议,它处理每个包的地址部分,使这些包正确的到达目的地。
从协议分层模型方面来讲,TCP/IP由四个层次组成:网络接口层、网络层、传输层、应用层。 每一层都呼叫它的下一层所提供的网络来完成自己的需求。
(5)tcp 为什么要三次握手,两次不行吗?为什么?
三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。
第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
所以三次握手就能确认双发收发功能都正常,缺一不可。
(6)说一下 tcp 粘包是怎么产生的?
(7)OSI 的七层模型都有哪些?
(8)get 和 post 请求有哪些区别?
1-get是用来从服务器上获取数据,而post是用来向服务器传递数据;
2-get将表单中数据按照variable=value的形式,添加到action所指向的URL后面,并且两者用"?“连接,变量之间用”&"连接;而post是将表单中的数据放在form的数据体中,按照变量与值对应的方式,传递到action所指定的URL。
3-get是不安全的,因为在传输过程中,数据是被放在请求的URL中;而post的所有操作对用户来说都是不可见的。
4-get传输的数据量小,这主要应为受url长度限制;而post可以传输大量的数据,所有上传文件只能用post提交。
5-get限制form表单的数据集必须为ASCII字符;而post支持整个IS01 0646字符集。
6-get是form表单的默认方法
(9)什么情况下调用doGet()和doPost()?
默认情况是调用doGet()方法,JSP页面中的Form表单的method属性设置为post的时候,调用的为doPost()方法;为get的时候,调用deGet()方法。
(10)如何实现跨域?
(11)说一下 JSONP 实现原理?
(九)设计模式
(1)说一下你熟悉的设计模式?手写单例模式(double-check)?为什么要两次check?
(1.1)熟悉的设计模式包括:
单例模式、工厂模式、代理模式、观察者模式等等
(1.2)单例模式又分为:
饿汉式、懒汉式(双重检查-最常用)、静态方法、静态类、枚举等
(1.3)懒汉式(双重检查)
(1)三个注意点
1)构造器私有保证外部无法实例化
2)instance的声明有一个voliate关键字,如果不用该关键字,有可能会出现异常。
因为instance = new Test(); 并不是一个原子操作,会被编译成三条指令:
1.给Test的实例分配内存
2.初始化Test的构造器
3.将instance对象指向分配的内存空间(注意 此时instance就不为空)
但是JVM在执行上面三条指令的时候不一定按照1-2-3的顺序执行,可能是1-3-2,这样的话在执行3的时候对instance就已经不为空了,此时2还没来得及执行,如果这个时候有其他进程调用getInstance,就会判断instance不为空并且直接返回这个instance对象进行使用,空的instance用起来肯定就会报错了。
复习一下voliate的特点:可见性;禁止指令重排序(new创建对象的指令必须按照上面的1-2-3顺序执行,本质就是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,不能自己重新排序执行。不会阻塞其他进程)
3)getInstance为什么要有两次判空?
第一次判空可以删除的,但是去掉以后,不管instance有没有被实例化都会执行synchronized操作,而synchronized是一个重操作消耗性能,所以去掉第一个判空会导致效率下降,加上第一个判空可以省去没必要的运算。
第二个判空方法是不能删除的,如果没有第二个判空条件,有可能两个线程a和b都通过了第一个判空条件,然后依次对instance进行实例化,这不符合单例的要求,有了第二个判空,线程b进入后发现instance不为空,就不会再次进行实例化,从而保证了单例的效果!
复习一下synchronized的特点:1-可见性;2-原子性(synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住)
class Singleton05{
//1-构造器私有化
private Singleton05(){ }
//2-本类内部创建对象实例
private static volatile Singleton05 instance;
//3-提供一个公有的静态方法,当使用到这个方法时,才去创建instance
public static Singleton05 getInstance(){
if(instance==null){
synchronized (Singleton05.class){
if(instance==null){
instance=new Singleton05();
}
}
}
return instance;
}
}
(2)简单工厂和抽象工厂有什么区别?
1-简单工厂模式没有使用反射技术来实例化对象,而是使用new关键字来实例化对象,当我们把一个对象名称传给简单工厂类时,它会用new完成对象实例化,并且返回一个这个对象。
2-而工厂模式内部使用的是反射技术,让Class类根据传入的"类.包"名来反向获取这个类,然后再利用Class类对象调用构造方法完成对象的实例化,替代了new关键字,达到了降低代码耦合度的目的。
3-抽象工厂模式可以把简单工厂模式和工厂模式进行整合,把抽象工厂分成两层,一层是抽象工厂接口,一层是抽象工厂的实现类。这样不同的工厂都可以实现这个接口,当需要创建对象时,就调用这个接口的对象,等于是把多个工厂交给这个接口进行管理,变成针对接口编程,而不是针对实现编程。
(3)动态代理模式的JDK版和Cglib版有什么区别?
(十)算法
(1)常见的排序算法?
(1)冒泡
public static void sortUpadte(int arr[]){
int temp=0;//临时变量,用来交换位置
boolean flag=false;//标识变量,表示是否进行过交换
for (int j = 1; j < arr.length-1; j++) {
for (int i = 0; i < arr.length-j; i++) {
if (arr[i]>arr[i+1]){
flag=true;
temp=arr[i+1];
arr[i+1]=arr[i];
arr[i]=temp;
}
}
System.out.println("第"+j+"趟排序后的数组:"+ Arrays.toString(arr));
if (!flag){
break;//在一趟排序中,不存在前面数比后面大,一次交换都没有发生过,就直接结束
}else {
flag=false;//重置flag,进行下一次判断
}
}
}
(2)快速
public static void sort(int arr[], int left, int right){
int temp=0;
int l=left;//左下标
int r=right;//右下标
//中间轴
int pivot=arr[(left+right)/2];
//while循环的目的是让比pivot值小的放到左边,值大的放到右边
while (l<r){
//在pivot的左边一直找,找到大于等于pivot的值,才退出
while (arr[l]<pivot){
l+=1;
}
//在pivot的右边一直找,找到小于等于pivot的值,才退出
while (arr[r]>pivot){
r-=1;
}
//如果l>=r说明pivot的左右两边值,已经按照左边全部小于pivot,右边全部大于pivot
if (l>=r){
break;
}
//交换左右位置
temp=arr[l];
arr[l]=arr[r];
arr[r]=temp;
//如果交换之后,发现这个arr[l]==pivot。等于r--,前移
if (arr[l]==pivot){
r-=1;
}
//如果交换之后,发现这个arr[r]==pivot。等于l++,后移
if (arr[r]==pivot){
l+=1;
}
}
//如果l==r,必须l++,r--,否则出现栈溢出
if(l==r){
l+=1;
r-=1;
}
//向左递归
if (left<r){
sort(arr,left,r);
}
//向右递归
if (right>l){
sort(arr,l,right);
}
}
(3)插入
public static void sort(int arr[]){
for (int i = 1; i < arr.length; i++) {
// 先把下标为1的值作为待插入值
int insertVal=arr[i];//下标为1的值
//待插入值前面的值为被比较值
int insertIndex=i-1;//下标为1的值的前面,前面值下标为0
//给insertVal找到插入的位置
//要插入的值,从下标为0的数开始比,如果比前面的数小,说明待插入的数还没有找到位置
while (insertIndex >= 0 && insertVal <= arr[insertIndex]){
arr[insertIndex+1]=arr[insertIndex];//把值后移以为,让出空间让待插入值插入
insertIndex--;//insertIndex自减,就是接着往前面比较的意思
}
//加一句判断,是否需要交换位置
if (insertIndex+1!=i){
arr[insertIndex+1]=insertVal;
}
arr[insertIndex+1]=insertVal;//前面insertIndex已经减一了,这里加一,就等同于原来的arr[insertIndex]位置
System.out.println("第"+i+"轮排序后的数组:"+ Arrays.toString(arr));
}
}
(2)常见的链表、栈、队列的思路?
(十)Spring/Spring MVC(框架和中间件要结合项目经验)
(1)为什么要使用 spring?
1-spring框架的重要理念包括IOC和AOP
2-IOC认为所有资源都是Bean,把JavaBean的创建、时间、行为等都交给IOC容器管理,IOC自己通过描述来创建Bean,用户可以通过描述让IOC找到需要的资源,替代了new关键字,依赖关系也可以通过配置完成,大大降低了代码的耦合度,方便代码的后期管理和维护。Spring IOC 还提供对Bean的生命周期管理、延迟加载等,更加方便的使用和管理Java资源
3-IOC的目标是管理类,而Bean是Java面向对象的基础设计的,但是有些情况是面向对象无法处理的,例如事务,还需要使用面向切面的编程。AOP常用于数据库事务的编程。
(2)解释一下什么是 aop,解决什么问题(实现原理,实现流程,jdk动态代理和Cglib动态代理的区别)?
看博客
(3)解释一下什么是 ioc(实现原理,实现流程)?
看博客
(3)spring 有哪些主要模块?
(4)spring 常用的注入方式有哪些?
配置文件注入有:setter注入、构造方法注入、接口注入
还有注解的注入,例如@AutoWired依赖注入
@Value(“$”)的配置文件注入
@Value(“#”)的SpringEL信息注入
(5)spring 中的 bean 是线程安全的吗?
实际上大部分时间Bean是无状态的(比如Dao) 所以说在某种程度上来说Bean其实是安全的
但是如果Bean是有状态的 那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变bean的作用域 把 "singleton"改为’‘protopyte’ 这样每次请求Bean就相当于是 new Bean() 这样就可以保证线程的安全了
(6)spring 支持几种 bean 的作用域?
Spring支持四种作用域,分别是单例Singleton、原型prototype、会话session、请求request。可以使用注解@Scope进行配置
1-单例Singleton:最常用,也是默认选项,在整个应用里Spring只生成一个Bean的实例对象
2-原型prototype:每次通过IOC容器获取Bean时,Spring都会为它创建一个新的实例对象
3-会话session:在Web应用里使用,就是在会话过程中Spring值创建一个实例
4-请求request:在Web应用里使用,就是一次请求中Spring会创建一个实例,但是不同的请求会创建不同的实例
(7)spring 自动装配 bean 有哪些方式?
Xml配置、注解配置
(8)描述一下spring bean的生命周期(创建Bean的过程)?
先思考一下Servlet的生命周期:实例化,初始init,接收请求service,销毁destroy;
- 实例化bean对象(通过构造方法或者工厂方法)
- 设置对象属性(setter等),按照Spring上下文对实例化的Bean进行配置(IOC依赖注入)
- 如果Bean实现了BeanNameAware接口,工厂调用Bean的setBeanName()方法传递Bean的ID,就是注解或者配置文件里Bean的名字(和下面的一条均属于检查Aware接口)
- 如果Bean实现了BeanFactoryAware接口,工厂调用setBeanFactory()方法传入工厂自身
- 将Bean实例传递给Bean的前置处理器的postProcessBeforeInitialization(Object bean, String beanname)方法
- 调用Bean的初始化方法
- 将Bean实例传递给Bean的后置处理器的postProcessAfterInitialization(Object bean, String beanname)方法
注:以上工作完成以后就可以应用这个Bean了,那这个Bean是一个Singleton的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例 - 使用Bean
- 容器关闭之前,调用Bean的销毁方法
(9)spring 事务实现方式有哪些?
(1)编程式事务管理:对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
(2)基于 TransactionProxyFactoryBean的声明式事务管理
(3)基于 @Transactional 的声明式事务管理
(4)基于Aspectj 的AOP配置事务
(10)说一下 spring 的事务隔离?
(11)说一下 spring mvc 请求的执行流程?
- 客户端(浏览器)发送请求,直接请求到 DispatcherServlet—>
- 前端控制器 DispatcherServlet 接受客户端请求(拦截HTTP发送过来的请求) —>
- 找到处理器映射 HandlerMapping, 解析请求找到对应的 Handler—>
- 处理适配器HandlerAdapter 会根据 Handler 来调用真正的处理器Controller层开始处理请求,并处理相应的业务逻辑 —>
- 处理器Controller层返回一个模型视图 ModelAndView,Model 是返回的数据对象,View 是个逻辑上的 View。 —>
- 视图解析器ViewResolver进行解析(如果是逻辑视图就解析,如果不是就直接进行视图渲染数据模型) —>
- 返回一个视图对象—>
- 前端控制器 DispatcherServlet 把返回的 Model 传给 View(渲染数据)(Moder)—>
- 将得到视图对象返回给用户
(12)spring mvc 有哪些组件?
1)前端控制器DispatcherServlet(不需要工程师开发),由框架提供(重要)
作用:Spring MVC 的入口函数。接收请求,响应结果,相当于转发器,*处理器。有了 DispatcherServlet 减少了其它组件之间的耦合度。用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。
2)处理器映射器HandlerMapping(不需要工程师开发),由框架提供
作用:根据请求的url查找Handler。HandlerMapping负责根据用户请求找到Handler即处理器(Controller),SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。
3)处理器适配器HandlerAdapter
作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler 通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
4)处理器Handler(需要工程师开发)
注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。 由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。
5)视图解析器View resolver(不需要工程师开发),由框架提供
作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。 一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。
6)视图View(需要工程师开发)
View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf…)
(13)@RequestMapping 的作用是什么?
@RequestMapping是一个用来处理请求地址映射的注解,可用于类或者方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
(14)@Autowired 的作用是什么?
@Autowired是用在JavaBean中的注解,通过byType形式,用来给指定的字段或方法注入所需的外部资源
(15)什么是Tomcat?
Tomcat是一种web服务器,java编写的web项目可以部署在上面,用户在客户端请求时,都是将请求发到Tomcat上,Tomcat在将请求发到对应的项目上。
(十一)Spring Boot/Spring Cloud
(1)什么是 spring boot?为什么要用 spring boot?
(2)spring boot 核心配置文件是什么?
(3)spring boot 配置文件有哪几种类型?它们有什么区别?
(4)spring boot 有哪些方式可以实现热部署?
(5)jpa 和 hibernate 有什么区别?
什么是 spring cloud?
spring cloud 断路器的作用是什么?
spring cloud 的核心组件有哪些?
(十二)Mybatis
(1)mybatis 中 #{}和 ${}的区别是什么?
1- #{}是 sql 的参数占位符,生成 ?占位符,#{item.name} 的取值方式为使用反射从参数对象中获取 item 对象的 name 属性值。#方式能够很大程度防止sql注入
2- ${}是Properties配置文件中的变量占位符, ${} 则只是简单的字符串替换,属于静态文本替换,比如{driver}会被静态替换为com.mysql.jdbc.Driver。 $方式无法防止Sql注入
(2)mybatis 有几种分页方式?
(3)RowBounds 是一次性查询全部结果吗?为什么?
(4)mybatis 逻辑分页和物理分页的区别是什么?
(5)mybatis 是否支持延迟加载?延迟加载的原理是什么?
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。
(6)说一下 mybatis 的一级缓存和二级缓存?
1-缓存一般用在可高速读写的存储器上,为提高速度,可以暂时把常用的数据缓存在内存中,这样读取速度就会远快于读取磁盘的速度。
2-一级缓存(在SqlSession上缓存),但是SQLSession层面的缓存要求必须是同一个SQLSession对象,如果是不同的对象,就无法获取到缓存数据。要想实现不同的SQLSession对象也能获取同样的缓存数据,就要继续往上层去缓存,也就是把数据缓存在SqlSessionFactory上。
3-二级缓存(在SqlSessionFactory上缓存),SQLSession是由SqlSessionFactory生成的,而数据也缓存在SqlSessionFactory中,所以不同的SQLSession对象能够获取到相同的缓存数据。
(7)mybatis 和 hibernate 的区别有哪些?
(8)mybatis 有哪些执行器(Executor)?
Mybatis 有三种基本的 Executor 执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
**SimpleExecutor:**每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
**``ReuseExecutor`:**执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。简言之,就是重复使用 Statement 对象。
**BatchExecutor:**执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。
(9)mybatis 分页插件的实现原理是什么?
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
举例:select _ from student,拦截 sql 后重写为:select t._ from (select * from student)t limit 0,10
(10)mybatis 如何编写一个自定义插件?
Mybatis 仅可以编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
RESTful,讲讲PUT/GET/POST/DELETE分别的含义,使用RESTful和非RESTful的URL有什么区别?
Nginx。正向代理与反向代理有什么区别?
(十三)MySql(基础语句+索引+锁+事务+优化)
(1)Mysql主要分为几部分?
主要分为三部分:索引、锁、事务
(2)一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?
如何获取当前数据库版本?
(3)char 和 varchar 的区别是什么?
1-char有固定的长度,合用在身份证号码、手机号码等。而varchar属于可变长的字符类型
2-查询速度: char最快, varchar次之
(4)float 和 double 的区别是什么?
float类型表示单精度浮点数值,double类型表示双精度浮点数值
(5)mysql 的内连接、左连接、右连接有什么区别?
1-内连接查询 inner join:组合两个表中的记录,返回关联字段相符的记录,也就是返回两个表的交集
2-左连接查询 left join:左表(a_table)的记录将会全部表示出来,而右表(b_table)只会显示符合搜索条件的记录。右表记录不足的地方均为NULL
3-右连接 right join:左表(a_table)只会显示符合搜索条件的记录,而右表(b_table)的记录将会全部表示出来。左表记录不足的地方均为NULL
(6)mysql 索引的分类?索引是怎么实现的?什么情况下适合建索引?索引失效的场景?
1)索引主要有三类:
B+树索引、全文索引、hash索引
2)B+树的分类:
1-聚集索引(数据和索引不分离,文件本身即索引,文件直接存叶子里)、
2-辅助索引(索引和数据分离,叶子里找索引,索引找数据)
3)聚集索引和辅助索引的区别?
4)B+树和B树的区别?
1-B树的所有节点既存放 键(key) 也存放 数据(data);而B+树只有叶子节点存放 key 和 data,其他内节点只存放key。
2-B树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
3-B树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
5)索引是怎么实现的?
对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,使用B+Tree作为索引结构。
InnoDB存储引擎使用B+Tree作为索引结构,B+树索引使用二分法查找,将记录有序化(递增或递减)排列,在超找过程中采用跳跃式方式查找,既先以有序数列的中心点位置比较对象,如果要查找的元素小于该元素的中心点元素,则将待查找的元素缩小为左半部分,否则为右半部分,通过一次比较,将查找区间缩小一半。
6)mysql 索引是根据什么条件创建的(什么情况下适合建索引)?
7)怎么验证 mysql 的索引是否满足需求?
使用 explain 查看 SQL 是如何执行查询语句的,从而分析你的索引是否满足需求
8)索引失效的场景?
(9)说一下 mysql 常用的引擎?InnoDB和myIsam的区别?
(1)介绍InnoDB引擎
1-大多数时候我们使用的都是 InnoDB 存储引擎,InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
2-InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
3-InnoDB支持外键
(2)说一下 mysql 的行锁和表锁?
1-MySQL中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。
2-MySQL中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
(3)InnoDB和myIsam的区别?(MyISAM和InnoDB实现BTree索引方式的区别?)
1-MyISAM:
B+树叶子节点的data存放的是数据的地址,在索引检索的时候,首先按照B+树搜索算法搜索索引,如果指定的key存在,就取出它data里的地址值,然后根据这个地址读取相应的数据记录。这称为“非聚簇索引”
2-InnoDB:
MyISAM的索引文件和数据文件是分离的,要根据叶子节点中的索引文件再找到相应的数据;而InnoDB的数据文件本身就是索引文件,两者是不分离的,B+树的叶子节点data保存完整的数据文件。
这个索引的key是数据表的主键,所以说InnoDB表数据文件本身就是主索引,被称为“聚簇索引(聚集索引)”,而其余的索引都作为“辅助索引”,辅助索引的data存储主键的值而不是地址,这也是InnoDB和MyISAM不同的地方。
在根据主索引搜索时,直接找到key所在的节点就可以取出数据;在根据辅助索引搜索时,就需要先取出主键的值,再走一遍索引。因此,设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
(10)说一下乐观锁和悲观锁?
1-为了实现事务处理的可串行性,数据库在执行update / insert / delete等语句时,会对相应数据加锁。当其他事务处理需要修改已被加锁的数据时,就会等待锁被释放。
2-悲观锁(每次拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿数据就会阻塞直到他拿到锁==共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程)——适用于“多写”情况
3-乐观锁(不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据)——适用于“多读”情况
(1)悲观锁
悲观锁是一种利用数据库内部机制提供的锁的方法,也就是对更新的数据加锁,这样在并发期间一旦有一个事务持有了数据库记录的锁,其他的线程将不能再对数据进行更新了。
在SQL语句里加上for update语句,意味着将持有对数据记录的行更新锁(因为是主键查询),意味着在高并发情况下,当一条事务持有了这个更新锁才能接着往下操作,其他的线程想要更新这个数据就必须得等待,这样就不会出现超发现象引发的数据一致性问题
(2)乐观锁
乐观锁是一种不会阻塞其他线程并发的机制,不会使用数据库的锁进行实现,乐观锁使用的是CAS原理
1)CAS原理概述
对于多个线程共同的资源,先保存一个旧值(Old Value),例如抢红包进入线程后,先查询当前剩余红包数并且把这个旧值保存起来,然后经过逻辑处理后,比较数据库现在的值和旧值是否一致。如果一致就说明数据保持了一致性,可以进行扣减红包的操作,如果值和旧值不一致就说明这个值已经被其他线程修改了,不再进行操作。
但是CAS原理有一个问题,就是ABA问题。
2)ABA问题
例如保持旧值为A,进行逻辑操作的期间,旧值被其他线程修改成了B,又修改成了A,这个时候再进行值与旧值比较的时候,发现值和旧值一致,会认为这个值没有被修改,然后进行更新。
ABA问题是因为业务逻辑存在回退的可能性。可以加入一个逻辑属性,比如加入一个版本号(version),只要修改了一次值,版本号就会递增且不会倒退,这样版本号就不会出现回退的现象,也就可以准确判断出值与旧值是否一致。
3)乐观锁重入机制
上面说的加入乐观锁,就会有很多数据sql执行失败。例如用户抢购时,很多人的订单会因为乐观锁的存在而提交失败,最后货物还有很多没卖出去。这个时候就要考虑提高请求的成功率,如果第一次请求失败,那就再自动请求一次。
有两种方法:1-加入时间戳执行乐观锁重入,2-加入限制重入次数执行乐观锁重入
1-加入时间戳执行乐观锁重入
在一定时间戳内,例如100毫秒,不成功的请求会循环到成功为止,知道超出100毫秒后,不成功的请求才会退出,返回失败
2-加入限制重入次数执行乐观锁重入
有时候时间戳并不是很稳定,也会随着系统的空闲或者繁忙导致重试次数不一。可以限定重入次数为3次,尝试3次请求后如果还是不成功,就退出并返回失败。
(11)说一下 ACID 是什么?(数据库事务)
事务的ACID:
1-子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;
2-一致性(Consistent):事务结束后系统状态是一致的;
3-隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
4-持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败。通过日志和同步备份可以在故障发生后重建数据。
(12)说一下数据库的事务隔离?InnoDB是哪种?可重复读会造成什么问题?InnoDB是怎么解决这个问题的?什么是MVCC?
(1)SQL 标准定义了四个隔离级别:
1-READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
2-READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
3-REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
4-SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
(2)InnoDB是哪种属于3-REPEATABLE-READ(可重复读)
(3)可重复读可能会造成幻读
(4)InnoDB是使用MVCC解决幻读问题的
(13)mysql 问题排查都有哪些手段?
1-使用 show processlist 命令查看当前所有连接信息
2-使用 explain 命令查询 SQL 语句执行计划。
3-开启慢查询日志,查看慢查询的 SQL。
(14)如何做 mysql 的性能优化?
1-为搜索字段创建索引
2-避免使用 select *,列出需要查询的字段
3-选择正确的存储引擎,尽量用InnoDB
4-尽量使用主键进行检索
(十四)Redis
(1)redis 是什么?都有哪些使用场景?基本的操作命令?
redis是一种基于内存的键值(key-value)数据库,以支持每秒十几万次的读写操作,提供一定的持久化功能,支持集群、分布式、主从同步等配置,还能支持一定的事务能力,在高并发情况下可以保证数据的安全和一致性。
高并发情况下大量数据操作会导致数据库瘫痪,所以考虑使用redis的缓存。
1-String字符串:String数据结构是简单的key-value类型,犹如java的Map结构,value其实不仅可以是字符串,也可以是整数和浮点数
2-Hash哈希散列表:hash 是一个 string 类型的 field 和 value 的映射表,是一个键值对应的无序列表,hash 特别适合用于存储对象(比如用户信息)
3-List列表:list 就是链表,每个节点都包含一个字符串
4-Set集合:set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。在它里面的每一个元素都是一个字符串,而且各不相同
5-Sorted Set有序集合:和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。它是一个有序集合,可以包含字符串、整数、浮点数、分值score,元素的排序根据分值的大小来决定。
(2)redis 有哪些功能?
1-基本事务和回滚机制
2-锁的机制和watch/UNwatch
3-流水线提高redis的命令性能
4-发布订阅模式
5-超时命令和垃圾回收策略
6-Luau语言
(3)redis 和 memecache 有什么区别?
(4)redis 为什么是单线程的?
所有数据都放在Redis中,如果使用多线程,CPU在切换线程的时候,有一个上下文切换时间,而这个上下文切换时间是非常耗时的。当使用I/O操作时,要用多线程,因为I/O操作一般可以分为两个阶段:即等待I/O准备就绪和真正操作I/O资源。而Redis读取内存是没有I/O操作的,所有使用单线程效率最高。
(5)什么是缓存穿透?什么是缓存雪崩?怎么解决?
缓存穿透:大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。
解决方法:最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
(6)redis 支持的数据类型有哪些?
(7)redis 支持的 java 客户端都有哪些?
redisclient
(8)jedis 和 redisson 有哪些区别?
(9)怎么保证缓存和数据库数据的一致性?
读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
(10)redis 持久化有几种方式?(怎么保证 redis 挂掉之后再重启数据可以进行恢复)
在redis中存在两种方式的备份:一种是快照(snapshotting,RDB),备份当前瞬间redis在内存中的数据记录;另一种是只追加文件(append-only file,AOF),作用就是当redis执行写命令后,在一定的条件下将执行过的写命令依次保存在redis的文件中,将来就可以依次执行那些保存的命令恢复redis的数据
对于快照备份而言,如果当前redis的数据量大,备份会造成redis卡顿,但是恢复重启的速度较快;对于AOF而言,它只是追加写命令,所以备份一般不会造成卡顿,但是恢复重启要执行更多的命令,备份文件可能也很大。
(11)redis 怎么实现分布式锁?
(12)redis 分布式锁有什么缺陷?
(13)redis 如何做内存优化?
(14)redis 淘汰策略有哪些?
(15)redis 常见的性能问题有哪些?该如何解决?
(十五)JVM
(1)说一下 jvm 的主要组成部分?及其作用?(要能手画)
1-程序计数器:字节码解释器通过改变程序计数器来依次读取指令,实现代码的流程控制。标记位置,线程被切换回来的时候能够知道该线程上次运行到哪儿
2-Java虚拟机栈:每次方法调用,的时候都会创建一个栈帧,用来存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成,对应一个栈帧在虚拟机栈中入栈到出栈
3-本地方法栈:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务,Native方法生成栈帧入栈执行
4-Java堆:存放对象实例,几乎所有的对象实例以及数组都在这里分配内存
5-方法区:存放类信息、常量、静态变量、即时编译器编译后的代码
6-运行时常量池:存放编译期生成的各种字面量和符号引用
7-直接内存:不是虚拟机运行时数据区的一部分
(2)说一下 jvm 运行时数据区?
(3)说一下堆栈的区别?
(4)队列和栈是什么?有什么区别?
(5)堆分为哪几个部分?为什么持久代改为了元空间(直接内存)?
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代;再细致一点新生代可以分为:Eden 空间、From Survivor、To Survivor 空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
JDK7分为:新生代、老年代、持久代(永生代)
JDK8改成:新生代、老年代、元空间(元空间用的是直接内存)
移除永久代原因:为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。
有了元空间就不再会出现永久代OOM问题了!
(6)说一下类加载的执行过程?双亲委派是干嘛的?如果我自己写一个String类能不能加载进内存?
系统加载 Class 类型的文件主要三步:加载->连接->初始化。连接过程又可分为三步:验证->准备->解析。
每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 双亲委派模型 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。
(7)怎么判断对象是否可以被回收?
1-引用计数算法,当计数器为0时,这个对象就是不可能再被使用的。
可达性分析算法,
2-当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,就可以判定是可回收的对象
(8)java 中都有哪些引用类型?
1-强引用(StrongReference):最普遍,当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
2-软引用(SoftReference):如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存
3-弱引用(WeakReference):在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
4-虚引用(PhantomReference):
(9)说一下 jvm 有哪些垃圾回收算法(GC算法)?它们的优缺点?
1-标记-清除算法
优点:最基础
缺点:效率较低,标记清除后会产生大量不连续的碎片,造成空间问题
2-复制算法(针对新生代对象,使用较大的Eden和其中一块Survivor)
优点:解决了效率问题,只对内存区间的一半进行回收
缺点:
3-标记-整理算法(针对老年代)
标记过后不是直接清除,而是让所有存活的对象往内存的一端移动,聚齐以后把剩下的对象清除掉。解决了空间碎片的问题
4-分代收集算法
根据对象存活周期的不同把内存分为新生代和老年代,在老年代的对象存活率高使用标记-整理算法收集,在新生代的对象存活率低使用复制算法收集。效率问题和空间问题都能解决。
(10)说一下 jvm 有哪些垃圾回收器,详细介绍一下GC算法?
1-Serial收集器(单线程、新生代)
2-ParNew收集器(Serial的多线程、与CMS配合)
3-Parallel Scavenge收集器(多线程、新生代、复制算法)
4-Serial Old收集器(Serial老年代、单线程、标记整理算法)
5-Parallel Old收集器(Parallel老年代、多线程、标记整理算法)
6-CMS收集器(并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作)
7-G1收集器
(11)详细介绍一下 CMS 垃圾回收器?
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
1-初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
2-并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
3-重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
4-并发清除: 开启用户线程,同时 GC 线程开始对为标记的区域做清扫。
(12)新生代垃圾回收器和老生代垃圾回收?
(13)收器都有哪些?有什么区别?
(14)HotSpot 为什么要分为新生代和老年代?简述分代垃圾回收器是怎么工作的?
新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
(25)Minor Gc 和 Full GC 有什么不同呢?
Minor GC : 清理年轻代
Major GC : 清理老年代
Full GC : 清理整个堆空间,包括年轻代和永久代
(15)说一下 jvm 调优的工具?
(16)常用的 jvm 调优的参数都有哪些?
(19)如何判断一个常量是废弃常量
假如在常量池中存在字符串 “abc”,如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量
(20)如何判断一个类是无用的类
类需要同时满足下面 3 个条件才能算是 “无用的类”:
1-该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
2-加载该类的 ClassLoader 已经被回收。
3-该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
(16)项目问题
(1)简单介绍一下你写过的项目。
(2)Shiro是什么东西呢?简单说说你在项目中是如何使用Shiro的
(3)Elasticsearch又是什么东西呢?简单说说你在项目中是如何使用Elasticsearch的
如果按比例来分,上面提到的技术及其原理占80%,项目占20%。
(1)自己最熟悉的是哪个项目,这个项目是干嘛的,其业务是什么;这个项目的QPS是多少
(2)自己干了啥;这个项目的难点和亮点是啥;开发中遇到了哪些问题,是怎么解决的;有没有遇到过什么线上问题
(3)以上这些要提前想好,否则当场是想不起来咋说的,不要问我咋知道的
(4)其余的像期望薪资和最低薪资要提前想好;上下班时间,五险一金咋交,公司有没有其它福利等要问清楚
下一篇: 微信小程序 闭包写法详细介绍