面试宝典
2023-01-28 18:08:16 26 举报
AI智能生成
java面试整理
作者其他创作
大纲/内容
java基础
集合
Collection
Set
不重复,基于Map集合的单列Key值去重
HashSet
无序
基于HashMap的key,通过Hash值比较,如果一致,再通过eques方法比较达到去重效果
LinkedHashSet
SortedSet
NavigableSet
TreeSet
有序
基于TreeMap的key,通过实现compare接口的compareTo方法来保证不一致
List
有序集合,可重复
ArrayList
基于数组,查询速度快,添加元素慢
默认长度是10,在进行扩容的时候会进行 1.5 倍的增长 不存在自动减容机制
LinkedList
基于双向链表,索引是通过从头开始遍历计算的
查找修改速度慢,添加删除速度快
Vector
线程安全(所有方法都加了锁,同步锁)
Map
详情
默认值16
负载因子0.75(扩容大小,默认值*负载因子)
当数据到达12位会自动扩容(两倍的增长)
当链表节点长度为9,并且数组长度大于等于64时,链表变成红黑树
当同一个索引位置的节点移除后达到6个的时候,并且当位置节点是红黑树结构,会自动转换成链表
链表:通过hash算法来计算存储的位置,如果计算的位置不为空,那么就创建一个链表,链表节点的最大值是8,如果长度大于64的时候自动转换成红黑树
红黑树:使用红黑树查询效率高于普通链表,因为用的是二分查找效率要高于顺序查找并且会保持效率。如果当数量小于6的时候会自动转换成链表
实现类
HashMap
jdk1.8之前是数组+链表,之后是数组+链表+红黑树
按照键值对的方式存储,key不能重复,value可以为null也可以重复,线程不安全
为什么使用 数组+链表+红黑树 结构?
提高查询效率
一开始数据存储的时候发生hash冲突,这个时候需要把冲突的数据放到后面的链表中(链地址法,拉链法),如果hash冲突的数据过多,就会让链表过长,查询效率会变低,所以jdk1.8就换成了链表长度大于8的时候转换成红黑树
jdk1.7数据结构为什么是数组加链表
就是为了解决hash冲突问题
hash是线程不安全的,1.7的时候会造成死锁问题,1.8解决了死锁,但是会出现值覆盖问题,在put的过程中同时put两个值,然后覆盖
解决方案:加锁,加sync或lock锁,或者使用concurrentHashMap
jdk1.7使用的是头插法,在多线程情况下,链表会产生环形,因此1.8修改成了尾插法。多线程推荐使用ConcurrentHashMap
继承自AbstractMap类
key不重复 是先通过hash值比较的,如果一致再进行eques方法进行一致性比较来保证唯一key
ConcurrentHashMap
线程安全的hashmap
jdk1.8之后是用node里面加sync锁升级和cas配合使用,相当于在原有基础上加上红黑树
采用分段锁
jdk1.7的原理: ConcurrentHashMap实现的原理 :分段锁机制,在对象中保存一个Segment数组,将整个Hash表划分为多段;每一个Segment元素都类似域一个Hashtable,根据哈希值定位具体的Segment元素,然后对该Segment元素加锁,从而实现多线程开发的安全性
HashTable
不允许key和value为null
属于线程安全,每个方法上都有synchronized锁
HashTable的初始值是11,扩容2*n+1
继承自Dictionnary,比HashMap多提供了elments() 和contains() 两个方法。
elements() 方法用于返回此Hashtable中的
value的枚举
contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,
contansValue() 就只是调用了一下contains() 方法
TreeMap
有序的key 的散列表
数据结构是红黑树
key是通过实现compare接口的compareTo方法来保证不重复的
hash冲突解决
再hash
拉链法/链地址法
把具有相同散列地址的关键字放在同一个单链表里面(每个数组里面的链表就相当于拉链)
地址法/开放定址法
遇到哈希冲突时,去寻找一个新的空闲的哈希地址。
线程
线程的状态
1.新建状态 new
创建线程的方式
1.继承Thread类,重写run方法
2.实现runable接口,无返回值
3.实现callable接口,有返回值
4.通过线程池创建
2.准备就绪状态 start()
执行了start方法,但是还没有获取cpu的资源,等待cpu指针找到这个线程,并且去执行
3.运行时状态 runtime
也是执行了start方法,只不过cpu现在已经获取到了这个线程,该线程也拿到了cpu的资源
4.阻塞状态(被同步锁阻塞blocked)
sleep睡眠
Thread的方法,时间到会自动唤醒,会占用cpu和锁资源,唤醒之后继续执行
wait等待
object的方法,一定需要notify方法进行唤醒释放锁资源,睡眠的时候会进入等待区域(性能不好,因为会有守护线程来唤醒)
notify和notifyall的区别:notify会随机唤醒一个线程,是虚拟机控制的;notifyAll会将所有的线程唤醒,放到锁池,参与锁的竞争,竞争成功就执行,失败就继续留到锁池
5.结束
1.线程执行完成自动结束
2.使用stop方法,强行终止,不会释放ReentrantLock锁,不过在jdk1.5之后就被弃用了
3.使用interrupt方法终止线程,终止会抛出InterruptedException并且会自动退出线程,比较温和,可以将休眠状态的线程转换到runnable状态
如何保证线程执行顺序
join方法:是Thread的方法,作用是调用线程需等待该join线程执行完成后,才可以继续运行
wait方法:是object的方法,作用是让当前线程进入等待状态,同时,wait也会让当前线程释放它所持有的锁。直到其他线程调用次对象
线程池
创建方式 7种
Executors.newCachedThreadPool()
没有核心线程,线程池可容量最大线程为max,休眠时间,线程队列(用在段时间处理大量任务)
特点:当设置休眠的时间,又没有空闲线程时,会创建新的线程去执行任务;当没有设置休眠时间时,有空闲线程时,会重复使用线程去执行任务
创建可缓存的线程池。如果线程池的长度超过需要处理的长度,请灵活地重用空闲线程,如果不能重用就创建新的线程。
Executors.newFixedThreadPool(int nThreads)
传的参数为最大线程数(最大线程数包括核心线程数,还有线程队列)
特点:当有任务执行时,核心线程先运行,再有新的任务时,会放入到线程队列里面,再有新的任务就会创建新的线程去执行
提交优先级:核心线程 --> 线程队列 --> 新创建的线程
执行优先级:核心线程 --> 新创建的线程 --> 线程队列
创建固定长度的线程池,用于控制线程的最大并发行数,超出线程将在队列中等待。
Executors.newSingleThreadExecutor()
传的参数为最大线程数;只有一个核心线程,线程池最大的线程数是核心线程数,线程队列
特点:只有一个核心线程在执行任务
创建单线程池,只在唯一的工作线程上执行任务,以确保所有任务都在指定顺序(FIFO、LIFO、优先级)执行。
Executors.newScheduledThreadPool()
创建固定长度的线程池,以支持计划和定期任务执行
newThreadPoolExecutor()
自定义线程池,最常用的,因为可以自己设置线程池的参数,jdk提供的最大线程数是integer的最大值
线程池的7个参数
1.核心线程数(corePoolSize)不会销毁
2.最大线程数(maximumPoolSize)为核心线程数和创建线程数总和,空闲一段实际则会销毁创建的线程
3.活跃时间(keepAliveTime)非核心线程的最大空闲时间,没有被使用就会销毁
4.时间单位(unit)
5.队列(workQueueSize)
6.线程工厂(threadFactory)
7.线程的执行策略(handle)四种
关键的三个参数:
corePoolSize-(最小)核心线程数
workQueue - 阻塞队列
maximumPoolSize - 最大线程数
线程池的拒绝策略:当任务超过队列的最大时,线程池会拒绝后面的任务不在存放到队列中
AbortPolicy 终止策略
这是ThreadPoolExecutor线程池默认的拒绝策略,程序将会抛出RejectedExecution异常。
当线程队列满了就会抛弃后续的任务
CallerRunsPolicy 调用者运行策略
线程池中没办法运行,那么就由提交任务的这个线程运行
DiscardOldesPolicy
丢弃最早未处理请求策略,丢弃最先进入阻塞队列的任务以腾出空间让新的任务入队列
为什么要用线程池
1.降低能源消耗:通过重复利用创建的线程降低创建和销毁造成的损耗
2.提高响应速度:当任务达到的时候可以不用等待线程创建,直接执行就可以
3.提高线程可管理性:线程的资源是很稀缺的,如果没有限制的创建,不仅消耗系统资源,还会降低系统的稳定性,使得线程可以进行统一的调优分配和监控
线程池提交任务的两种方式
execute():只能执行Runnable类型的任务
submit():可以执行Runnable和Callable类型的任务 并通过返回的Future对象获取结果
线程安全
保证线程安全的三种方式
1.使用安全类,比如java.util.concurrent下的类
2.使用自动锁synchronized
3.使用手动Lock锁
synchronized 锁升级原理
升级顺序:
无锁-->偏向锁-->轻量级锁-->重量级锁
升级目的:
锁升级是为了减少锁带来的性能消耗
无锁
没有线程线程参与锁的竞争时
偏向锁
如果在很长一段时间只有同一个线程在竞争锁就会发生偏向锁
通过锁在内存中的mark word来确定是否为同一线程。在mark word里面记录ThreadId,在锁竞争时比较,如果一致则是同一线程
轻量级锁/自旋锁
如果有多个线程同时竞争锁,那么就会升级成轻量级锁
当mark word中的锁指针指向该栈,则栈中持有轻量级锁。没有获取到锁的线程会进行cas自旋
cas(Compare And Swap)
比较并交换
基本操作数
内存地址V
旧的预期值A
要修改的新值B
交换值步骤
1.读取内存地址V
2.在原子操作中比较内存地址V的值是否与A相同
3.相同时,修改内存地址V的值为B,原子操作成功
4.不相同时,循环执行第一至第三步(自旋),直到成功
优缺点
优点:
不会引起调用者休眠,阻塞的时间很短,提高响应的速度
缺点:
如果一直得不到锁竞争的线程使用自旋会消耗cpu的资源
重量级锁
通过操作系统的互斥锁完成
持有锁的线程调用锁对象的wait方法会升级成重量级锁
因为锁对象处于偏向或者轻量级锁的状态下,是没有管程对象和等待队列的,所以无法保存线程节点
如果多个线程自旋失败,则升级成重量级锁,操作系统将线程挂起,将线程从用户态转换成该内容和态
内核态
CPU可以访问任何的数据,包括外围设备,比如网卡、硬盘、处于内核态的CPU可以从一个程序切换到另外一个程序,并且占用CPU不会发生枪占情况,一般处于特权级0的状态我们称之为内核态
用户态
CPU受限的访问内存,并且不允许访问外围设备,用户态下的CPU不允许独占,这个时候的CPU是能够被其他程序获取的
优缺点
优点:
线程竞争不使用自旋,不会消耗cpu
缺点:
线程会阻塞,响应的速度比较慢
作用
1.确保线程互斥的访问同步代码
2.保持共享变量能及时看见
3.有效解决重排序问题
用法
1.修饰普通方法
2.修饰静态方法
3.修饰代码块
synchronized 底层实现原理
synchronized 中有一个monitor 对象,通过判断当前线程是否被占用来设置状态
通过monitorentry和monitorexit
在执行monitorenter,线程试图获取monitor(存在于每个java对象的对象头中,也是java中任意对象可以作为锁的原因)对象的持有权,当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行monitorexit指令后,将锁计数器设为0,表明被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止
synchronized和lock的区别
1.synchronized可以修饰类,方法,代码块;Lock只给代码块加
2.synchronized为自动锁,会自动释放锁,Lock为手动锁,必须使用unlock方法手动释放
volatile关键字
可以保证代码的可见性,但不保证原子性
禁止进行指令重排序
atomic原理
atomic主要利用的是cas和volatile和native方法来保证原子操作,从而避免synchronzied的高开销,执行效率大为提升
什么是死锁:两个线程同时占用了自己的锁,并且释放的时候需要获取到对方的锁
怎么防止死锁
1.使用tryLock方法设置超时时间,超时可以退出
2.使用安全类来代替锁
3.降低锁的使用粒度(减少使用锁)
4.减少在方法上加锁
ThreadLocal:
可以使每个线程都有自己独立的工作副本,每个线程都只改变自己的副本,不会影响其他副本
ThreadLocal可能出现的问题:
jmm(java线程内存模型)的值存在堆中,方法再栈中执行就涉及到可见性的问题,当共享值改变时,其他线程可以实时更新本地值
ThreadLocal内存泄漏问题:内存不可达的情况
1软引用:相对强引用弱一点的引用,需要用类来实现,可以让对象豁免一些垃圾回收机制,当内存不足时,会报内存溢出异常
用来描述一些还有用但非必要的对象,当系统内存不足时才会回收类
2弱引用:比弱引用弱一点的引用,被弱引用关联的对象在垃圾回收机制工作,不管内存是否足够,都会被回收
3强引用:将一个对象赋给另一个引用变量,这个引用变量就是一个强引用
当对象被强引用变量引用时,处于可达状态,不能被垃圾回收机制回收,所以有时会造成内存泄漏
4虚引用:用来描述跟踪对象垃圾回收的状态,当对象被回收时收到一个系统通知或者后续添加进一步处理
每个线程都维护一个ThreadLocalMap,key为ThreadLocal,value为要存的值,同时key时做为弱引用使用,弱引用对象会在垃圾机制时被回收,ThreadLocalMap和Thread生命周期一致,当ThreadLocal对象被回收时,线程还未结束,key为null,value访问不到情况就会导致内存泄漏
主要原因:ThreadLocalMap的生命周期和Thread一样,没有及时remove时,线程还未结束,导致内存泄漏
当使用线程池和ThreadLocal时要注意线程是不断重复的,不手动删除会导致value的积累
解决内存泄漏:利用弱引用和自动回收机制配合使用,垃圾回收机制会回收旧的ThreadLocal对象,因为key使用弱引用,当弱引用的ThreadLocal对象被回收后,该key为null时,下一次使用ThreadLocalMap会被清除
cas和aba
cas:负责将某处内存地址的值与一个期望值地址进行比较,如果不相等,则将该内存地址处的值替换为新值
aba:cas需要检查带操作的值是否发生改变,没发生改变则跟新(但是存在一种状况,一个值原来为a,变成了b,然后又变回了a,那么cas检查时发现没有该改变,但实际发生了改变)
java8新特性
lambda表达式
foreach
做集合的迭代
map
将一个集合转换成一个新的集合
filter
filter
sorted
用来做集合的排序
可以通过重写Comparable的compareTo
collect
归集,将集合的元素收集
List
Set
Map
.collect(Collectors.toMap(Department::getId, Function.identity()));
.collect(Collectors.toMap(Department::getId, department -> department));
.collect(Collectors.toMap(Department::getId, department -> department, (k1, k2) -> k1))
函数式接口
optional
接口的default方法
javaweb
Http
协议
Http
请求头
Content-Type:标注请求体中的请求类型,可以是json.xml,或者文件,图片,from,js等等
User-Agent:告诉网站服务器,访问者是通过什么工具来请求的,如果是爬虫请求,一般会拒绝,如果是用户浏览器,就会应答
请求体
传参的地方,只有post请求才有请求体,get请求的参数都是在地址栏上的,地址栏上是中文会乱码URLEncoder,URLDecoder
响应体
服务端传给浏览器端的内容,这个内容除了json,xml还有text格式以外,其余所有内容全部转换成二进制文件
Https
网络传输更安全,需要安装ssl证书,这个证书就是一串秘钥,这个秘钥一份存在自己的服务端,一份存在cdn服务器上(cdn就是存放你们域名与ip+port映射的地方),最终找到我们的服务器完成请求
使用的是对称加密+非对称加密
https响应速率肯定是比不上http,每次请求需要传秘钥,秘钥是参数,参数就会占用带宽,为了提高响应速率,秘钥格式使用的是对称加密,对称加密解密效率高一点
浏览器输入一个地址后做了哪些操作
1.浏览器需要解析url地址
url划分
协议
www
域名
锚
参数
2.解析完后,首先会去找本地的hosts文件
3.找不到,才会通过交换机找到网络上的cdn服务器进行解析
4.拿到最终服务器的ip+端口号后找到对应的服务
5.服务器通过交换机找到对应的DHCP服务器
6.dhcp会映射给网卡(MAC地址是每台电脑唯一的地址)
7.完成网络传输
网络组成
七层协议
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
简化成四层
应用层
传输层
网络层
网络接口层
底层的实现
Tcp
协议是可靠的,确定接受端一定收到数据才会中断连接
协议可靠是因为三次握手四次挥手
1.第一次握手,是a向b发送请求,是否能够建立连接
2.b收到后向a发送请求,确实可以建立建立连接
3.a接受到后,开始向b发送数据
4.第一次挥手是客户端a首先想服务端b发送一个断开连接的请求
5.服务端b收到后,然后向客户端a发送一个确认收到的请求
6.A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
7.b服务端会告诉a客户端数据已经接受完毕,可以断开链接
8.a客户端会改变状态为关闭状态完成断开连接的操作
Udp
协议不可靠,数据传输过后,直接断开连接
post&get
post
传输的数据在请求体,数据安全,大小没有限制,数据类型可以有很多种,是通过content-type定义的
get
数据拼接在地址栏上,首先大小受限,数据容易乱码,并且不安全
Session&cookie
Session
session是基于cookie传输,每次cookie中会携带一个sessionId,通过sessionId找到对应的session,找到session中存的用户信息
cookie
存在浏览器中,本地存储,每次请求,浏览器会默认帮我们携带cookie,
servlet
本质是一个内嵌在tomcat的应用程序,servlet是完全独立与任何框架的,所有框架想要做web应用,都需要导入tomcat的jar包,与servlet类似的是filter:本质就是一个servlet
框架部分
spring
AOP:面向切面编程
静态代理
通过字节码的方式去动态写入响应的位置,在编译阶段就生成,性能更快
动态代理
jdk自带的动态代理
实现proxy代理类里面的InvocationHandler,里面的invoke方法
cglib动态代理
通过字节码形式来实现的动态代理
动态代理失效的原因
不是被public修饰的类
不被spring容器管理的
自己捕获了异常或者手动try catch
IOC:控制反转
创建对象的控制权的转移,ioc让对象的创建不用去new了,可以由spring来管理,根据反射动态的创建bean对象
三种注入方式
构造器注入
setter方法注入
注解注入
DI:依赖注入
spring bean的生命周期
1.实例化bean
实例化bean,通过BeanDefinition对象的信息,通过反射获取构造方法进行创建bean
2.设置对象属性
spring根据BeanDefinition中的信息通过BeanWrapper提供的设置属性接口完成依赖注入
3.处理aware接口
使用XXXAware可以让Bean获得Spring容器的服务,从而获取SpringBoot启动时的一些信息
4.postProcessBeforeInitialization
5.InitializingBean
设置属性,执行afterPropertiesSet方法
6.postProcessAfterInitialization
初始化之后执行,一般在这个阶段进行aop生成proxy代理类
7.DisposableBean
清理阶段,如果Bean实现了DisposableBean这个接口,会调用destroy方法
8.destroy-method
如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法
三级缓存
一级缓存存放实例化对象
二级缓存存放已经在内存空间创建好但是还没有给属性赋值的对象
三级缓存存放对象工厂,用来创建提前暴露到bean的对象
三级缓存的作用,「尽可能」的让代理对象的创建要在目标对象完全初始化后进行
二级缓存完全可以解决循环依赖的 bean 是代理对象的情况,只不过需要 spring 实例化一个 bean 后,需要立即判断是否要代理此 bean,若需代理, 则先生成代理 bean 放到二级缓存中而后进行初始化。但是这样做显然会不合规,所以 spring 引入了三级缓存
spring mvc
1概念:解耦,将视图与模型进行解耦,将前端的html页面与后端将要返回的数据进行解耦
2.执行流程
核心控制器DispatcherServlet
视图解析器:ViewResolver
作用:判断当前是否需要去识别视图,如果打上@responseBody是不会去找对应视图的,那么试图解析器首先会帮我们封装参数,然后将参数通过el表达式映射到视图中,最终返回视图给前端
处理器映射器:HandlerMapping
作用:帮助我们识别所有的url请求,通过requestmapping注解获取我们前端访问的url与注解上的url对应的地址,获取到当前需要执行的方法,去调用method.invock()
处理器适配器:HandlerAdapter
作用:首先去识别@requestBody@responseBody,帮我们处理入参出参,(将json转成javabean对象,将javabean对象转换成json,默认 使用的转换工具是jackon,还可以自己配置fastJson),还会帮我们处理国际化问题,参数校验,包括校验异常的抛出都是处理器适配器去帮助我们完成的
3.springmvc常用注解
1.去做url映射的注解
.requestmapping
自动帮我们匹配当前请求的请求方式
getmapping
只能解析get请求
参数特点:在浏览器上拼接,大小受限,不安全,涉及到中文编码问题
, postmapping
只能解析post请求
参数特点:在请求体中,请求格式可以自定义,content-type,一般使用json
2.参数解析的
1.requestBOdy,一般是;配合postmapping使用的,去用来解析前端传过来的json参数,将json转为javabean对象,默认的使用的是jackson转换工具,支持配置fastjson
2.@RequestParam配合get请求使用的,是将前端浏览器传过来的参数通过request,getParameter()方法获取参数
3.参数反解析
@ResponseBody,将java对象转换成json传给前端
详细流程
spring boot
1.概念:基于spring 的一个简化配置,约定大于配置的框架
1.快速搭建web项目
1.内嵌mvc核心包
2.内嵌tomcat
3.json解析的依赖
默认使用jackson
常用的是fastjson
2.整合了市面上大多数框架的版本,设置到springboot-parent中,帮助我们解决依赖版本不同导致冲突的问题
dependencyManagement 只做jar包的版本管理,不会下载依赖
dependencies 这个会帮我们下载jar包,以及管理jar包
3.简化了整合进的框架配置,spring容器提供了大量的默认配置
2.启动流程-执行流程@springbootapplication
@EnableAutoConfiguration
Springboot根据应用声明的依赖来对spring框架进行自动配置,他会去帮我找一个spring.factories,这个文件定义了所有spring整合的XXXAutoConfiguration
@SpringBootConfiguration
被标注的类等于在spring的XML配置文件中(applicationContext.xml),装配所有bean事务,提供了一个spring的上下文环境
@ComponentScan
组件扫描,可自动发现和装配Bean,默认扫描Springapplication的run方法里面的Booter.class所在的包路径下的文件,所以最好将改启动类放到根包路径下
详细流程
springcloud
JVM虚拟机
jvm内存模型
类装载子系统
类加载子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识
类加载过程
加载
通过类的完全限定名,查找此类字节码文件,利用字节码文件创建Class对象.
验证
确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全.
准备
进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null).不包含final修饰的静态变量,因为final变量在编译时分配.
解析
将常量池中的符号引用替换为直接引用的过程.直接引用为直接指向目标的指针或者相对偏移量等
初始化
主要完成静态块执行以及静态变量的赋值.先初始化父类,再初始化当前类.只有对类主动使用时才会初始化
字节码执行引擎
解释器
JIT
运行时数据区
线程共享
堆
新生代
eden区
survivor存活区,分为s0和s1
老年代
1.8永久代
1.7常量池
1.7静态变量
1.7方法区/1.8元空间
1.7常量池
1.7静态变量
类信息(.class)
线程私有
本地方法栈
存储C/C++相关的变量和方法
虚拟机栈/线程栈
局部变量表
操作数运算时一块临时的空间来存放操作数
操作数栈
操作数运算时一块临时的空间来存放操作数
动态链接
将代码的符号引用转换为在方法区(运行时常量池)中的直接引用
方法出口
存储了栈帧中的方法执完之后回到上一层方法的位置
程序计数器
记录线程运行指令的步骤,可以看成是指令执行的行号,在切换线程时cpu根据行号来执行
GC垃圾回收机制
三种垃圾回收算法
标记复制
标记整理
标记清除
常见的垃圾收集器
分代回收
Serial新生代串行垃圾回收器
单线程
Serial Old老年代串行垃圾回收器
单线程,标记整理
Parallel Scavenge新生代并行垃圾回收器
多线程
Parallel Old老年代并行垃圾回收器
多线程,标记整理
ParNew新生代并发清除回收器
CMS(Concurrent Mark Sweep)老年代并发标记清除回收器 清理不够彻底,会产生浮动垃圾,但速度快,stw时间短
初始标记
仅仅只标记GC Roots能直接关联的对象,速度很快,仍然需要暂停所有工作线程
并发标记
根据刚刚标记的对象向下找,需要时间,不会暂停所有工作线程,但会占用cpu资源,导致应用程序线程变慢,吞吐量降低
重新标记
为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有用户线程
并发清除
清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程,基于标记结果,直接清除对象,采用的标记清除算法,会产生碎片空间,碎片空间不够的话容易触发FullGC
分区回收regin
G1
G1可以选择部分区域进行垃圾回收,这样缩小了回收的范围,因此可以减少stw的时间
G1会跟踪regin里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的regin。保证在有限时间内尽可能的获取到最高的收集率
G1会占用额外空间,G1可以采用应用程序的线程来进行GC,不过会降低吞吐量
G1垃圾回收过程
YoungGC年轻代GC
YongGC + concurrent mark并发标记过程
MixedGC混合回收
FullGC(在特定条件下发生)
ZGC
java对象引用
强引用
永远不会回收
弱引用
发现就回收
软引用
内存不足时回收
虚引用
不会影响对象的生命周期,也无法通过虚引用获得对象实例。虚引用的作用就是对象被回收时收到一个系统通知(回收跟踪)
JVM调优
调优命令
jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机
进程中的类装载、内存、垃圾收集、JIT编译等运行数据
jmap,JVM Memory Map命令用于生成heap dump文件
jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
jstack,用于生成java虚拟机当前时刻的线程快照
jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数
调优工具
jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控
和管理控制台,用于对JVM中内存,线程和类等的监控
jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等
MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java
heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
GChisto,一款专业分析gc日志的工具
调优参数
-Xmx:堆内存最大限制。
设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代
-XX:NewSize:新生代大小
-XX:NewRatio 新生代和老生代占比
-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比
设定垃圾回收器 年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC
设计模式
1.单例模式
单例模式的作用:避免频繁的创建对象,增大资源的消耗
饿汉式
已启动就帮你创建对象
懒汉式
调用的时候才创建对象
懒汉式创建时需要加双重锁判断
减少竞争锁的资源开销
2.工厂模式
其中复杂对象指的是类的构造函数参数过多等对类的构造有影响的情况下,我们将创建对象的过程抽离出来,减少代码中的耦合以及后期我们的维护成本
静态工厂
spring的ioc
jdbc的连接池
抽象工厂
简单工厂
3.代理模式
静态代理
动态代理
aop
底层实现是通过proxy类完成的,需要去实现他的句柄
4.适配器模式
springmvc就是适配器模式
5.观察者模式
缓冲IO流
6.迭代器模式
所有集合都有迭代器
7.装饰者模式
缓冲IO流
8.策略模式
线程池的策略参数
9.享元模式
自动拆装箱
10.桥接模式
JDbc
11.责任链模式
拦截器,过滤器都是
中间件
redis
redis的读写效率
读11万,写8万
redis端口号
6379
redis的数据类型
1.hash
2.string
3.set
4.zset/sored set
5.list
持久化策略
rdb(默认开启)
存储的是数据快照,效率很高,存储的文件小,不占用空间,有可能会有数据丢失
save 900 1
save 600 5
save 100 100
aof(手动开启)
存储的是所有写的操作命令,每秒执行一次,性能比较低,存储文件大,不会有数据丢失
公司一般用那种?
rdb+aof
redis事务
redis所谓的事务是一个假事务,只是将命令打包成一个批处理执行
redis的删除策略
定时删除
设置一个定时器,监听所有设置过期时间的key,但凡过期,就删除
定期删除
每隔一段时间去删除过期的key,是随机挑选key进行删除
惰性删除
使用key的时候,再去判断key是否过期,如果过期,就删除
redis默认使用的是 定期 + 惰性
为什么不用定时删除?
定时删除,会用一个定时器来负责监视key,过期自动删除。虽然及时的释放内存,但是十分消耗cpu的资源,如果并发很大的话,cpu要处理很多请求,就非常占用cpu的资源
redis的淘汰策略
noeviction: 不删除,直接返回报错信息
allkeys-lru:移除最久未使用(使用频率最少)使用的key。推荐使用这种
volatile-lru:在设置了过期时间的key中,移除最久未使用的key
allkeys-random:随机移除某个key
volatile-random:在设置了过期时间的key中,随机移除某个key
volatile-ttl: 在设置了过期时间的key中,移除准备过期的key
allkeys-lfu:移除最近最少使用的key
volatile-lfu:在设置了过期时间的key中,移除最近最少使用的key
redis的缓存雪崩
原有的缓存失效,新的缓存未到期间
在这个时间段有大量请求,本来是走缓存的,但去访问数据库了,就可能发生数据库宕机
解决方案:加锁或者用队列来保证不会有大量的线程对数据库进行一次性的读写
redis穿透
用户查询数据,在数据库里面不存在,那么在缓存里面也不可能有,那么每次查询就会访问数据库,进行io操作,非常消耗资源
解决方案
1.使用布隆过滤器,将所有可能查到的数据进行hash算法(因为hash算法一般是O(n)级别的,性能会特别的高)计算放到足够大的bitmap里面,不存在的数据过过滤掉(推荐使用)
2.如果一个查询返回的结果为空,那么直接把这个查询结果缓存,设置过期时间,一般不会超过5分钟,下次再有这个查询直接返回
redis缓存预热
就是系统上线,直接将相关的缓存数据加载到缓存系统。这样用户请求就可以直接拿缓存,不去访问数据库,降低数据库的压力
1.直接写个缓存刷新页面,上线时手工操作
2.数据量不大,可以在项目启动的时候进行加载
3.定时刷新缓存
redis缓存降级
非核心服务影响到核心流程的性能时,仍然要保证服务可用,这个时候可以根据一些关键数据进行降级,也可以配置开关实现人工降级。
最终目的是要保证即使有损,核心服务也是可用的。
redis为什么这么快
1.纯内存操作
2.单线程操作,避免了频繁的切换上下文
3.采用了非阻塞I/O多路复用机制
redis的缓存如何更新
定时去清理过期缓存
当用户请求过来判断缓存有没有过期,有过期就去数据库重新读取数据更新
redis集群的三种模式
主从复制
哨兵模式
集群模式
redis的延迟双删问题
什么是redis的延迟双删呢?
redis的延迟双删是指在并发的情况下,修改了数据库的数据,然后缓存还没来得及改,就已经被线程获取到了,所以这个时候的数据是脏数据
先把redis缓存的数据删掉,然后在修改数据库数据,这时候sleep一下(即延迟),然后在把redis缓存的数据删掉。防止有其他线程在你修改数据库的时候,更新缓存导致数据不一致
阿里提供的解决方案,通过mysql的binglog日志解决,直接给redis推送信息完成数据更新
rebbitMQ
使用场景
秒杀削峰 处理高并发
异步响应
并发排队 滴滴打车
天府通扣费
安装
1.安装erlang,配置环境变量
2.安装RabbitMQ,开启管理插件
消息模型
1.HelloWorld
生产者
1.创建连接工厂
2.创建连接
3.创建信道
4.创建队列
5.发送消息
消费者
1.创建连接工厂
2.创建连接
3.创建信道
4.消息处理回调
5.消息监听
2.签收模式
1.自动签收
2.手动签收
手动ack确认消息是否发送成功,以及消息是否消费成功
3.workqueue
和helloworld一样,只是搞多个消费者
设置消息的消费并发数(能者多劳)
4.fanout:发布订阅模型
生产者
1.创建连接工厂
2.创建连接
3.创建信道
4.创建交换机(fanout)
5.准备消息
6.发送消息
使用上面创建的交换机
不用指定的routing key
消费者
1.创建连接工厂
2.创建连接
3.创建信道
4.创建队列
5.把队列绑定到交换机:指定一个routing key
6.监听队列
7.回调:处理消息
5.direct:路由模型
生产者
1.创建连接工厂
2.创建连接
3.创建信道
4.创建交换机(direct)
5.准备消息
6.发送消息
使用上面创建的交换机
要用指定routing key
消费者
1.创建连接工厂
2.创建连接
3.创建信道
4.创建队列
5.把队列绑定到交换机:指定一个routing key
6.监听队列
7.回调:处理消息
6.topic:主题模型
生产者
1.创建连接工厂
2.创建连接
3.创建信道
4.创建交换机(topic)
5.准备消息
6.发送消息
使用上面创建的交换机
要使用指定routing key
消费者
1.创建连接工厂
2.创建连接
3.创建信道
4.创建队列
5.把队列绑定到交换机:指定一个routing key(可以通配符)
6.监听队列
7.回调:处理消息
7.持久化
1.交换机持久化
2.队列持久化
4.消息持久化
多路复用模型
同步阻塞io
单任务按顺序执行
同步非阻塞nio
多任务,定时查看任务执行状态
异步阻塞aio
单任务,自动提交任务执行状态
异步非阻塞bio
多任务,自动提交任务执行状态,合理分配,最大化利用资源
MySql
事务隔离级别
读未提交
可能导致脏读、幻读、不可重复读
读已提交
可能会出现幻读和不可重复度
在事务提交之前创建一个视图
可重复读
可能会出现幻读。mysql5.6之后不会出现幻读
在事务启动的时候就会创建一个视图,避免了不可重复度
串行化
不会出现脏读、幻读不可重复读,相当于单线程
不同隔离级别的问题
脏读
A事务未提交,B事务读取到A事务的内容
不可重复度
A事务执行中,B事务修改了A事务的数据,导致A事务前后读取的数据内容不一致
幻读
A事务执行中,B事务进行了添加或删除操作,导致A事务读取的数据条数不一致
mysql三大日志
redo log
记录数据修改之后的值,无论事务是否提交。可以实现事务的持久性
undo log
回滚日志,记录数据修改之前的值,实现事务的原子性(用于回滚)
bin log
归档日志,属于逻辑日志,是以二进制的形式存储整个数据库的执行语句
数据库三范式
原子性,列不可再分
唯一性,保证每一行的数据是唯一的
独立性,消除数据冗余,每一列的数据都不可以再分
数据库引擎
InnoDB
mysql5.5之后的默认引擎,默认索引类型是B+数的聚簇索引,支持行锁、事务和外键约束,支持数据同步,默认的事务隔离级别是可重复读
MyISAM
默认索引类型是B+树,叶子节点保存的是数据对应的指针。只支持表锁,不支持事务,保存了表的行数,所以读写操作效率高
数据库优化
分库
垂直拆分
将所有的表拆分到不同的数据库中
水平拆分
复制多个相同的数据库做集群(单库的数量建议控制在5000万以内)
分表
垂直拆分
将同一张表中的字段拆分到多个表中,并使用一对一关联,适用于多字段的大表
水平拆分
复制相同结构的表,将数据分散。建议200万以上的数据就进行分表
数据索引
索引分类
聚簇索引
就是主键索引,mysql默认给表主键索引,没有主键就找唯一键为索引,没有唯一键就会生成一个6位数的row_id作为索引
非聚簇索引
唯一索引
索引列的值都只能出现一次,值可以为空
普通索引
非唯一索引,索引列值可以重复,可以为空
全文索引
全文索引类型为fulltext,可以在varchar,char,text类型上创建
联合索引
多列值组成的一个索引,准备用于组合搜索
特点:叶子节点只存储该索引键和对应的主键,不存储整条数据
索引相关名词
回表
先通过聚簇索引查询到所需记录的主键,然后通过主键索引去找到主键对应的值
索引覆盖
当需要查询的数据列可以从索引中直接获取,不需要进行回表的操作称之为索引覆盖
最左匹配原则
在联合索引中,数据库会依据最左边的字段来构建B+数,这个原则就是最左匹配原则
索引下推
建立联合索引的时候,数据库会遵循最左匹配原则,此时如果查询条件中存在多个索引列,数据库服务器会将这部分条件下推到存储引擎中进行过滤
mysql5.6之前是先通过最左索引列过滤后的数据返回到数据库server层,在进行右边索引列数据过滤,此操作增加回表次数
索引失效的情况
查询条件包含or并且不是所有条件列都建了索引
数据类型不匹配(隐式转换导致)
不满足最左匹配原则
对索引进行了算数运算
对索引列使用了<>、not in、in、!=、is null、is not null
mysql自己的优化,认为全表扫描更快,所以不走索引
expain执行计划
MVCC机制
概念:多版本并发控制,是一种数据库并发读写的解决方法
表的隐藏字段
db_trx_id
创建这条记录或者最后一次修改该记录的事务id
db_roll_ptr
回滚指针,指向上一个历史版本数据(undo log)
db_rowid
待整理
分布式事务
1.单机事务ACID
原子性
一组事务要么全部成功,要么全部失败
一致性
事务改变的前后数据总数不变
隔离性
事务与事务之间相互不干扰
持久性
保存在磁盘中
2.mysql的二阶段提交
1.通常发生在update操作,mysql操作数据时会先将数据读到内存中,首先将原始数据保存到undolog 文件中,然后将修改后的数据刷到redo log cacha 以及bin log cacha缓存中,会先将 redo log cacha 刷到文件,标记状态prepare,然后再将bin log cacha 刷到binlog文件中,然后生成xid(事务id),最终再去找redolog文件,将状态改为commit,整个事务结束
关于悲观锁乐观锁
悲观锁:处理业务时,事务A在执行时,会进行加锁,其它事务在此期间无法执行
乐观锁:处理业务时,可以允许多个事务同时执行,只会在最终提交事务时,加锁判断你们的版本号是否匹配,如果版本号不一致,会回滚
什么是分布式事务
在分布式项目中,服务之间的调用要保证原子性
出现的场景
1.不同的服务,不同的数据库,出现业务的互相调用
2.相同的服务不同的数据库出现事务问题
3.不同的微服务相同的数据库出现事务问题
解决分布式的方案
理论知识
CAP理论
A:高可用性
C:一致性
P:分区容错性
Base理论
分布式一致性理论
强一致性
弱一致性
最终一致性
TX(单机事务) XA(分布式事务)
TX
关于acid的概念
XA
分布式原子性的概念
2pc阶段提交
1.客户端发送一个事务请求,涉及到不同的服务之间进行调用
2.首先所有参与该事务的服务需要请求事务协调器完成注册
3.事务协调器需要对每一个参与该事务的服务进行一个发起执行请求
4.所有受邀请的服务都需要给事务协调器返回一个执行的结果(本地执行事务)
5.事务协调器获取到所有服务的状态后,开始对每个服务进行允许提交或者回滚的操作
6.所有服务执行提交完成,整个分布式事务完成
3pc阶段提交
在2pc提交流程上
1.在首先事务管理器给所有参与事务的服务发情注册邀请时,如果其中但凡有一个在规定时间内没有返回消息,就全部回滚
2.在所有参与事务的服务等待事务管理器发送提交请求的时候,一直收不到消息,就自动执行commit操作
优势:3pc弥补了2pc可能会出现事务阻塞的问题,从而导致服务器宕机
缺点:可能会出现数据不一致的问题,保证不了数据的最终一致性
tcc
mq完成最终一致性
1.场景一,生成本地消息表
1.先生成一个本地的事务
2.直接提交这个事务
3.生成一个本地的消息表(是需要给调用服务发消息的数据)
4.如果失败不断轮询,进行最大努力尝试,设置尝试次数,如果操作次数,进行人工干预
2.场景二,不生成本地消息表
搭建seata的过程
1.pom.xml导入依赖
2.yml添加关于seata的服务事务分组配置
3.在资源目录下添加关于file.conf 以及registry.conf两个配置文件
4.去配置手动的datasource数据源
5.启动类上排除@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
6,在需要开启全局事务的地方打上 @GlobalTransactional(rollbackFor = Exception.class)//开启seata全局事务
数据结构与算法
数据结构
字符串,数组
链表
单链表
双链表
stack栈
Queue队列
tree树
二叉树
平衡二叉树
红黑树
线段树
B树
B+树
graph图
有向图
无向图
算法
排序算法
冒泡排序
选择排序
插入排序
希尔排序
归并排序
快速排序
计数排序
桶排序
基数排序
搜索算法
深度优先搜索
广度优先搜索
动态规划
背包问题
最长公共子序列
二分查找
贪心算法
分治算法
加密算法
0 条评论
下一页
为你推荐
查看更多