后端面试
JavaSE
面向对象
特点
封装:把属性和行为看成一个整体,封装在一个不可分割的单元中;隐藏不需要让外界知道的信息,高内聚低耦合
继承:子类继承父类的特征和行为,提高复用性、维护性,是多态的前提
多态:子类对象可以与父类对象进行转换,根据使用的子类不同功能也不同,重载重写,灵活,可扩展
原则
开闭原则:对扩展开放,对修改关闭
里氏代换原则:基类可以出现的地方,子类一定可以出现,是继承复用的基石
依赖倒转原则:针对接口编程,依赖抽象而不依赖具体,是开闭原则的基础
接口隔离原则:使用多个隔离的接口,比使用单个接口要好,降低依赖,耦合
最少知道原则:实体应尽量少地与其他实体之间发生相互作用,使系统功能模块相对独立
合成复用原则:尽量使用合成/聚合的方式,而不是使用继承
基础
JDK与JRE
JDK:Java开发工具包,提供Java开发环境和运行环境(JRE+开发工具集)
JRE:Java运行环境,为Java的运行提供所需环境(JVM+Java标准类库)
字节码
只面向虚拟机
比解释型语言效率高 ,又保留解释型语言可移植的特点
权限修饰符
private:类内部缺省:同包及子包
protected:子类public:同工程
成员变量与局部变量
成员变量
属于类
在堆中
除
final以外能自动赋值
局部变量
方法中定义的或是方法的参数
在栈中
不能自动赋值
包装类型的缓存
Byte/Short/Integer/Long: [-128,127]Character:[0,127]全部使用
equals方法比较
==与equals
==基本类型:值比较
引用类型:引用地址的比较
equals是方法而非运算符
在
Object类中定义与==相同比较两个对象的实体内容是否相同
String,Date和包装类等类重写类此方法哈希值相等 ← 对象相等(哈希碰撞)
final
被修饰的类叫最终类,此类不能被继承
被修饰的方法不能被重写
被修饰的变量叫常量,必须初始化且初始化之后值不能被修改
native
- 在本地方法栈中,调用底层C语言的函数库
intern
本地方法
将指定的字符串对象的引用保存在字符串常量池中,返回该引用
操作字符串的类
String声明为
fianl,不可被继承赋值
直接赋值:数据在常量池中
new+ 构造器:数据在堆空间中
拼接
常量与常量:在常量池中
final关键字修改之后的String会被编译器当做常量来处理
变量与常量:在堆中
StringBuffer:可变的字符序列,线程安全StringBuilder:可变的字符序列,线程不安全
重写和重载
重写
子类和父类同名同参的方法,运行时子类的方法会覆盖父类的方法
体现多态性
晚绑定
重载
一个类中的多个同名方法,参数个数或参数类型不同
提高可读性
早绑定
抽象类
抽象类不是必须要有抽象方法
与普通类的区别
普通类
不能包含抽象方法
可以直接实例化
抽象类
- 可以包含抽象方法
- 不能直接实例化
与接口的区别
- 都可以有默认实现的方法
- 接口
- 使用
implements实现(多实现) - 不能有构造函数
- 不能有
main()方法
- 使用
- 抽象类
使用
extends继承(单继承)可以有构造函数
可以有
main()方法且能被运行
接口
可以创建匿内部类
接口和当前类有关,和父子类无关
IO流
按数据单位
字节流(8bit):主要处理非文本数据
字符流(16bit):主要处理文本数据
按流向
输入流:数据 → 程序
输出流:程序 → 数据
按角色
节点流:直接与特定的目标相连
处理流:套接在已有流的基础上
泛型
把元素的类型设计成一个参数
允许在定义类/接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型,这个类型参数将在使用时确定
IO
- BIO:同步阻塞式IO
- 应用程序发起
read调用后,会一直阻塞,直到内核把数据拷贝到用户空间 - 并发能力低
- 应用程序发起
- NIO:同步非阻塞式IO
- BIO的升级
- 通过选择器,实现多路复用
- 一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务
- AIO/NIO2:异步非堵塞式IO
- 基于事件和回调机制实现
反射
- 可以获取并调用任意一个类的所有属性和方法
- 无视泛型参数的安全检查
序列化
数据结构或对象与二进制字节流的互相转化
对于不想进行序列化的变量,使用
transient关键字修饰- 只能修饰变量
- 修饰的变量在反序列化后变量值将会被置成类型的默认值
static变量因为不属于任何对象,不会被序列化使用场景
持久化到文件或数据库中
网络传输
对象拷贝
实现
实现
Cloneable接口并重写Object类中的clone()方法实现
Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
类型
引用拷贝:两个不同的引用指向同一个对象
浅拷贝:在堆上创建一个新的对象,直接复制内部对象的引用地址
深拷贝:深拷⻉会完全复制整个对象,包括这个对象所包含的内部对象
容器
常用容器
graph
1[Collection] --> 3[List]
1 --> 4[Queue]
1 --> 6[Set]
6 --> HashSet
6 --> SortedSet --> TreeSet
3 --> Vector --> Stack
3 --> ArrayList
3 --> 5[LinkedList]
4 --> 5
4 --> PriorityQueue
2[Map] --> HashMap --> LinkedHashMap
2 --> TreeMap
ArrayList
线程不安全,用
CopyOnWriteArrayList解决扩容规则
无参构造:使用长度为零的数组,添加元素时进行首次扩容
有参构造:使用指定容量的数组或给定的集合的大小作为数组容量
首次扩容:10或实际元素个数
二次扩容:增加容量的一半或实际元素个数
与
LinkedList的区别LinkedList双向链表,无需连续内存空间
随机访问慢
头尾插入删除性能高
占用内存多
ArrayList基于数组,需要连续内存空间
随机访问快
尾部插入删除性能高,其它部分插入删除性能低
与
Vector的区别Vector同步,ArrayList非同步
HashSet
线程不安全,用
CopyOnwriteHashSet解决实现原理
HashSet底层由HashMap实现HashSet的值放在HashMap的key上HashMap的value统一为PRESENT
Queue
Queue:单端队列,只能从一端插入元素,另一端删除元素,一般遵循先进先出规则Deque:双端队列,在队列的两端均可以插入或删除元素poll()与remove()都是从队列中取出一个元素
poll()获取元素失败时返回空remove()获取元素失败时会抛出异常
PriorityQueue- 默认小顶堆
- 是非线程安全的
- 不支持存储
null和non-comparable的对象
HashMap
线程不安全,用
ConcurrentHashMap解决可存储
null的key和value可以把它的
hash值设大一点来避免原始的扩容结构
- 数组 +(链表|红黑树)
树化与退化
树化意义
- 防止链表超长时性能下降
- 红黑树更新时间和空间占用比链表大
树化规则
- 链表长度超过树化阈值8时,先尝试扩容来减少链表长度,数组容量≥64才会树化
- 极限长度为11
graph LR
8:16 --> |扩容到32| 9:32 --> |扩容到64| 10:64 --> |转为红黑数| 11:64
退化规则
在扩容时如果拆分树,树元素个数≤6,则会退化
remove树节点时,若根结点/左孩子/右孩子/左孙子中有一个为nullroot/root.left/root.right/root.left.left
与
HashTable的区别HashTable线程安全
不允许空键值
HashMap线程不安全
允许空键值
使用
HashMap与TreeMap的策略插入/删除和定位元素操作选择
HashMap遍历操作选择
TreeMap
HashTable与ConcurrentHashMap都是线程安全的
Map集合Hashtable- 并发度低
- 整个
Hashtable对应一把锁,同一时刻只能有一个线程操作它
ConcurrentHashMap- 并发度高
- 数组的每个头节点作为锁,若多个线程访问的头节点不同则不会冲突
- 首次生成头节点时若发生竞争,用CAS进一步提升性能
List/Queue/Set/Map
List:元素有序、可重复Queue:元素有序、可重复(按特定排队规则确定先后顺序)Set:元素无序、不可重复Map:Key无序、不可重复
Comparable与Comparator
Comparable:java.lang的接口- 使用
compareTo(Object obj)排序
- 使用
Comparator:java.util接口- 使用
compare(Object obj1, Object obj2)排序
- 使用
Iterator
功能简单,创建代价小,只能单向移动
和
ListIterator的区别Iterator可用来遍历
Set和List集合只能前向遍历
ListIterator- 只能用来遍历
List - 可以前向也可以后向
- 拥有增加替换元素等功能
- 只能用来遍历
红黑树/B树/B+树
- 红黑树:二叉自平衡搜索树
- 深度大,多用于内存排序
- B树:多路搜索树
- 深度低,搜索数据时磁盘IO较少多用于索引外存数据
- 只支持随机访问,不支持顺序访问
- B+树:B树的改进
- 内节点不保存数据地址指针,内节点可看做为外节点的索引,所有数据地址存储在外节点
- 数据搜索效率一致
- 外节点依关键字组成顺序链表,支持区间搜索,且空间局部性好,缓存命中率高
- 支持随机访问和顺序访问
异常
Exception与Error
Exception:程序本身可以处理的异常,可以通过catch进行捕获Checked Exception:受检查异常,必须处理Unchecked Exception:不受检查异常,可以不处理
Error:属于程序无法处理的错误。异常发生时,JVM一般会选择线程终止
throw与throws
throws:用来声明一个方法可能抛出的所有异常信息,将异常上抛但不处理throw:指抛出的一个具体的异常类型
final/finally/finalize
final:可修饰类,方法,变量修饰类表示该类不能被继承
修饰方法表示该方法不能被重写
修饰变量表示该变量是一个常量不能被重新赋值
finally:一般作用在try-catch代码块中,处理异常时将一定要执行的代码方法finally代码块中,不管是否出现异常该代码块都会执行,一般用来存放关闭资源的代码finalize:Object类的一个方法,调用gc()方法时由垃圾回收器调用finalize()回收垃圾
try-catch-finally
finally不执行的情况- 程序所在的线程死亡
- 关闭 CPU
- 不要在
finally语句块中使用return- 当
try语句和finally语句中都有return语句时,try语句块中的return语句会被忽略
- 当
常见异常类
NullPointerException:空指针异常SQLException:数据库异常IndexOutOfBoundsException:数组下标越界异常NumberFormatException:数字格式异常FileNotFoundException:文件不存在异常IOException:IO异常当ClassCastException:类型转换异常IllegalArgumentException:非法参数异常ArithmeticException:算术运算异常RuntimeException:运行时异常
设计模式
装饰器模式
- 可以在不改变原有对象的情况下拓展其功能
- 对已有的业务逻辑进一步的封装,使其增加额外的功能(核心不变,扩展功能)
- 例:缓冲流,RDD(Spark)
适配器模式
- 主要用于接口互不兼容的类的协调工作
工厂模式
- 用于创建对象
- 创建对象时不会对客户端暴露创建逻辑,通过使用一个共同的接口来指向新创建的对象
观察者模式
- 对象间一对多的依赖关系
- 当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
代理模式
- 动态代理
可以给实现某个接口的类的方法添加额外的处理,这个动态生成的代理类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类
具有解耦意义,灵活,扩展性强
AOP
加事务
加权限
加日志
JUC
线程
并行与并发
并行是微观下同时运行,并发是宏观下同时运行
并行是在不同实体上的多个事件,并发是在同一实体上的多个事件
同步与异步
- 同步:发出调用后,在没有得到结果之前一直等待
- 异步:调用在发出之后不用等待返回结果,直接返回
线程与进程
进程是资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程
进程在执行过程中拥有独立的内存单元,多个线程共享内存资源,减少切换次数,效率更高
线程是进程的一个实体,是CPU调度和分派的基本单位,是比程序更小的能独立运行的基本单位
同一进程中的多个线程之间可以并发执行
六种状态
新建
当一个线程对象被创建但还未调用
start()方法不与操作系统底层线程关联
就绪
调用了
start()方法,由新建进入就绪与底层线程关联,由操作系统调度执行
终结
线程内代码已经执行完毕,由可运行进入终结
取消与底层线程关联
阻塞
获取锁失败,需要等待锁释放
持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
等待
获取锁成功但条件不满足阻
需要等待其他线程做出一些特定动作(通知或中断)
有时限等待
- 在指定的时间后自行返回
线程池
特点
降低资源消耗
提高响应速度
提高线程的可管理性
Executor框架
任务:
Runnable/Callable定义
Runnable接口的实现类并实现run()方法定义
Callable接口的实现类并实现call()方法
任务执行:
Executor异步计算的结果:
FutureFuture接口以及其实现类FutureTask都可以代表异步计算的结果把
Runnable或Callable的实现类提交给ThreadPoolExecutor或ScheduledThreadPoolExecutor执行- 调用
submit()方法时会返回一个FutureTask对象
- 调用
调用
FutureTask对象的get()获得子线程执行结束后的返回值
七大参数
corePoolSize:核心线程数目,池中保留的最多线程数maximumPoolSize:最大线程数目,核心线程+救急线程的最大数目keepAliveTime:生存时间,救急线程的生存时间,生存时间内没有新任务,此线程资源会释放unit:时间单位,救急线程的生存时间单位,如秒、毫秒等workQueue:没有空闲核心线程时,新来任务会加入到此队列,队列满会创建救急线程执行任务- 无参构造时队列容量为
Integer.MAX_VALUE
- 无参构造时队列容量为
threadFactory:线程工厂,可定制线程对象的创建,例如设置线程名字、是否是守护线程等handler:拒绝策略,当所有线程都在繁忙且workQueue也放满时,会触发拒绝策略
创建方式
通过
ThreadPoolExecutor构造函数创建防止内存溢出
自带线程池允许请求的长度或线程数量为
Integer.MAX_VALUE
通过
Executor框架的工具类Executors创建FixedThreadPool:固定线程数量的线程池SingleThreadExecutor:单线程的线程池CachedThreadPool:可根据实际情况调整线程数量的线程池ScheduledThreadPool:可延迟或定期执行任务的线程池
JMM
- 原子性:互斥访问,同一时刻只能有一个线程对数据进行操作
atomic/synchronized
- 可见性:一个线程对主内存的修改可以及时地被其他线程看到
synchronized/volatile多个线程从主内存拷贝值到各自的工作内存,若有线程更改拷贝的值并返回给主内存,此时其他线程不知道该值被修改
线程修改工作内存的值并返回给主内存后要及时通知其他线程
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序
- Java源代码会经历编译器优化重排 → 指令并行重排 → 内存系统重排的过程
CAS
比较当前工作内存中的值和主内存中的值,若相同则执行规定操作,否则继续比较直到值一致为止
三个操作数
内存值V:
volatile修饰,所以其他线程总能看到对他的修改旧预期值A
更新值B
当预期值A和内存值V相同时,将内存值V改为B
缺点
多次循环比较时间开销大
只能保证一个共享变量的原子性
会导致ABA问题:比较替换有时间差,会导致数据变化
- 增加时间戳解决
与
synchronized的区别synchronized加锁CAS不加锁,提高并发性
AQS
用来构建锁或者其它同步器组件的基础框架,
通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个
volatile int state变量表示持有锁的状态以
ReentrantLock为例state初始值为0,表示未锁定状态- A线程
lock()时会调用tryAcquire()独占该锁并将state + 1 - 其他线程再
tryAcquire()时就会失败,直到A线程unlock()到state =0(即释放锁)为止,其它线程才有机会获取该锁
常见同步工具类
CountDownLatch:倒计时器- 让一些线程阻塞直到另外一些完成后才被唤醒
- 当一个或多个线程调用
await()方法时调用线程会被阻塞 - 其他线程调用
countDown方法计数器减1(不会阻塞) - 当计数器的值变为0被阻塞的线程被唤醒
- 当一个或多个线程调用
- 让一些线程阻塞直到另外一些完成后才被唤醒
CyclicBarrier:循环栅栏- 让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活
Semaphore:信号量- 可以用来控制同时访问特定资源的线程数量
- 超过数量后其他尝试获取资源的线程将被阻塞
- 公平模式/非公平模式
volatile
指示JVM被修饰变量是共享且不稳定的,每次使用它都到主存中进行读取
线程安全
不能解决原子性:用悲观锁或乐观锁解决
可见性:让一个线程对共享变量的修改对另一个线程可见
有序性:用
volatile修饰共享变量能防止编译器等优化
注意
volatile变量写加的屏障是阻止上方其它写操作越过屏障排到volatile变量写之下- 变量赋值时,
volatile变量要在最后赋值
- 变量赋值时,
volatile变量读加的屏障是阻止下方其它读操作越过屏障排到volatile变量读之上- 变量读取时,
volatile变量要在最前读取
- 变量读取时,
volatile读写加入的屏障只能防止同一线程内的指令重排
synchronized
悲观锁,非公平锁,递归锁
方法或代码块运行时,同一时刻只有一个方法可以进入临界区,还保证共享变量的内存可见性
Java中每个对象都可以作为锁,这是
synchronized实现同步的基础普通同步方法:锁是当前实例对象
静态同步方法:锁是当前类的
class对象同步方法块:锁是括号里面的对象
构造方法不能使用
synchronized关键字修饰
与
volatile的区别volatile仅能使用在变量级别
仅能实现变量的修改可见性,不能保证原子性
不会造成线程的阻塞
标记的变量不会被编译器优化
synchronized使用在代码块/方法/类级别
可以保证变量的修改可见性和原子性
可能会造成线程的阻塞
与
lock的区别相同点
- 均属于悲观锁、都具备基本的互斥、同步、锁重入功能
不同点
语法层面
synchronized是关键字,源码在JVM中,退出同步代码块锁会自动释放Lock是接口,源码由JDK提供,需要手动调用unlock()方法释放锁
功能层面
synchronized不能中断,不支持精确唤醒Lock提供了许多synchronized不具备的功能,如获取等待状态、公平锁、可打断、可超时、多条件变量Lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock
性能层面
在没有竞争时,用
synchronized在竞争激烈时,
Lock的实现通常会提供更好的性能
ThreadLocal
使线程都有自己的专属本地变量
原理
- 每个线程内有一个
ThreadLocalMap类型的成员变量,用来存储资源对象 ThreadLocalMap可以存储以ThreadLocal为key,Object对象为value的键值对
- 每个线程内有一个
内存释放机制
被动
gc()释放key- 仅是让
key的内存释放,关联value的内存并不会释放
- 仅是让
懒惰被动释放
valueget()时发现是null,则释放其value内存set()时使用启发式扫描,清除临近的key为空的value内存
主动
remove()释放key,value同时释放
key,value的内存,也会清除临近的key为空的value内存推荐使用
与
synchronized区别synchronized用于线程间的数据共享
利用锁机制,使变量或代码块在某一时该只能被一个线程访问
ThreadLocal用于线程间的数据隔离
为每一个线程都提供了变量的副本
锁
公平锁/非公平锁
公平锁:多个线程按照申请顺序获取锁
非公平锁:多个线程不按申请顺序获取锁
默认
吞吐量大,但高并发下可能导致优先级反转或饥饿现象
悲观锁/乐观锁
悲观锁
线程只有占有了锁才能去操作共享变量,每次只有一个线程占锁成功,获取锁失败的线程都得停下来等待
线程从运行到阻塞、再从阻塞到唤醒,涉及线程上下文切换,如果频繁发生,影响性能
实际上线程在获取
synchronized和Lock锁时,如果锁已被占用,都会做几次重试操作,减少阻塞的机会锁的代表:
synchronized和Lock锁
乐观锁
- 无需加锁,每次只有一个线程能成功修改共享变量,其它失败的线程不需要停止,不断重试直至成功
- 由于线程一直运行不需要阻塞,因此不涉及线程上下文切换
- 锁的代表:
AtomicInteger,使用CAS来保证原子性
递归锁(可重入锁)
线程可以进入任何一个它已拥有的锁的同步代码块
可以避免死锁
自旋锁
尝试获取锁的线程不会立即阻塞,会采用循环的方式尝试获取锁
优点:减少线程上下文切换
缺点:循环导致消耗CPU资源
独占锁/共享锁/互斥锁
独占锁:该锁一次只能被一个线程持有
共享锁:该锁一次只能被多个线程持有
读写/写读/写写过程是互斥的
多线程锁的升级原理
- 从低到高依次为:无状态锁,偏向锁,轻量级锁,重量级锁,状态会随竞争情况逐渐升级
- 锁可以升级但不能降级
写时复制
- 往容器添加元素时,不直接往当前容器
object[]添加,而是先将当前容器object[]进行copy()复制出一个新的object[]元素,向新容器object[]元素里面添加数据,添加数据后将原容器的引用指向新的容器 - 可以进行并发的读,不需要加锁
- 读写分离:读和写不同的容器
方法对比
wait()与sleep()共同点
都让当前线程暂时放弃CPU使用权,进入阻塞状态
都可以被打断唤醒
不同点
方法归属不同
sleep()是Thread的静态方法wait()是Object的成员方法,每个对象都有
醒来时机不同
sleep(long)和wait(long)都会在等待相应毫秒后醒来wait()可以被notify()/notifyAll唤醒sleep()可以被interrupt()打断
锁特性不同
wait()放权sleep()不放权
notify()与notifyAll()若线程调用对象的
wait()方法,线程会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁notifyAll():唤醒所有wait()线程notify():只随机唤醒一个wait()线程优先级高的线程竞争到对象锁的概率大
若线程没有竞争到该对象锁会留在锁池中,唯有线程再次调用
wait()才会重新回到等待池中若线程竞争到对象锁的线程则继续往下执行,直到执行完
synchronized代码块,会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁
run()与start()start():启动线程并使线程进入就绪状态run():不会以多线程的方式执行
wait()与await()wait()Object类- 使用
notify()唤醒 - 一般用于
synchronized
await()ConditionObject类- 阻塞需要另外的一个
conditionawait对象 - 使用
signal()唤醒 - 只用于
ReentrantLock锁
submit()与execute()submit()有返回值,execute()没有submit()方便Exception处理
死锁
多个进程在执行过程中由于竞争资源或彼此通信而造成的一种阻塞现象,若无外力作用它们都将无法推进下去,这些永远在互相等待的进程称为死锁进程,是操作系统层面的错误
原因
系统资源不足或分配不当
进程运行推进顺序不合适
必要条件
互斥条件:进程分配到的资源不许其他进程访问,其他进程访问该资源只能等待,直至占有该资源的进程使用完释放该资源
请求和保持条件:进程获得一定资源后又对其他资源发出请求,该资源可能被其他进程占有,此时请求阻塞又对自己获得的资源保持不放
不可剥夺条件:进程已获得的资源在未完成使用之前不可被剥夺,只能在使用完后自己释放
环路等待条件:进程发生死锁后若干进程之间形成一种头尾相接的循环等待资源关系
JVM
概念
Java的核心,用来执行Java字节码(二进制)指令的程序虚拟机
内部体系结构
类装载器
运行时数据区
执行引擎
运行时数据区
结构
方法区:存放已被加载的类信息、常量、静态变量、即时JIT编译后的代码等数据(线程共享)
堆:对象实例(线程共享)
虚拟机栈:存放Java方法内的局部变量、方法参数(线程私有)
常见问题
调整栈大小不能保证不溢出
栈内存不是越大越好
垃圾回收不涉及栈
结构
局部变量表
一个数字数组,存储方法参数和方法体内的局部变量
容量大小在编译期确定
操作数栈
保存计算过程的中间结果
只通过出栈/入栈对数据进行操作
动态链接
每个栈内都有一个
用来将常量池中的符号引用(变量和方法引用)转为其在内存地址中的直接引用
方法返回地址
- 以调用者的程序计数器的值作为返回地址
本地方法栈:存放本地方法(
hashCode()这种无法单独依靠Java运行的方法)(线程私有)- Hotspot与虚拟机栈合并
程序计数器:当前线程所执行的字节码的行号指示器(线程私有)
直接内存:在Java堆外的直接向系统申请的内存
访问速度快
分配回收成本高,且不受JVM内存回收管理
解释器:把Java的字节码翻译成适用于各平台的机器码
即时编译器:把热点代码翻译成机器代码并且缓存起来
- 可能会导致可见性的问题,可能需要
volatile关键字防止编译器优化
- 可能会导致可见性的问题,可能需要
会发生内存溢出的区域
不会出现内存溢出的区域
- 程序计数器
出现
OutOfMemoryError的情况堆内存耗尽:对象越来越多,又一直在使用,不能被垃圾回收
方法区内存耗尽:加载的类越来越多,很多框架都会在运行期间动态产生新的类
虚拟机栈累积:线程个数越来越多,而又长时间运行不销毁时
出现
StackOverflowError的区域- 虚拟机栈内部:每个线程最多会占用1M内存,方法递归调用未正确结束或反序列化JSON时循环引用导致线程内1M内存用尽
类加载器
ClassLoader把字节码文件装入JVM中生成模板,通过模板可以创建多个实例对象
只负责
class文件的加载启动类加载器:加载Java核心类
C++编写
没有父加载类
无法直接访问
扩展类加载器:加载Java的扩展类
应用类加载器:加载
classpath中所有的类- 默认的类加载器
过程
加载
通过全类名获取定义此类的二进制字节流
将字节流所代表的静态存储结构转换为方法区的运行时数据结构
在内存中生成一个代表该类的
Class对象,作为方法区这些数据的访问入口
链接
验证:验证类是否符合Class规范
准备:静态变量分配空间,设置默认值
解析:将常量池的符号引用解析为直接引用
初始化
- 执行初始化方法
<clinit>()方法
- 执行初始化方法
卸载
- 堆不存在该类的实例对象
- 该类没有在其他任何地方被引用
- 该类的类加载器的实例已被GC
双亲委派机制
优先委派上级类加载器进行加载
避免重复加载类
保护程序安全,防止核心API被随意修改
沙箱安全机制
将Java代码限定在JVM特定的运行范围中且严格限制代码对本地系统资源访问,保证对代码的有限隔离,防止对本地系统造成破坏
沙箱主要限制系统资源访问
内存参数
- 从年代的角度划分JVM内存结构
graph TD
subgraph JVM
subgraph New
subgraph Eden
end
subgraph Survivor
subgraph From
end
subgraph To
end
end
end
subgraph Old
end
end
参数
-Xms:初始内存,默认为物理内存的1/64-Xmx:最大内存,默认为物理内存的1/4- 通常建议将
-Xms与-Xmx设置为大小相等
- 通常建议将
-Xmn:设置新生代大小- 相当于同时设置
-XX:NewSize与-XX:MaxNewSize并且取值相等
- 相当于同时设置
-XX:NewRatio:新生代与老年代在堆结构的占比,赋的值即为老年代的占比,剩下的1给新生代-XX:SurvivorRatio=4:1:设置新生代的占比- 新生代分成六份,伊甸园占四份,
from和to各占一份
- 新生代分成六份,伊甸园占四份,
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄超过此值仍未被回收的话则进入老年代
默认值为15,0表示年轻代对象直接进入老年代
内存分配
优先分配到伊甸园
大对象直接分配到老年代
长期存活的对象分配到老年代
动态年龄判断
分代为了优化垃圾回收性能
垃圾回收
四种引用
强引用:不回收
- 通过GC Root的引用链,如果强引用不到该对象,该对象才能被回收
软引用:内存不足即回收
仅有软引用该对象时,首次垃圾回收不会回收该对象,如果内存仍不足,再次回收时才会释放对象
需要配合引用队列来释放
弱引用:发现即回收
仅有弱引用引用该对象时,只要发生垃圾回收就会释放该对象
需要配合引用队列来释放
虚引用:对象回收跟踪
- 不会决定对象的生命周期,任何时候都可能被垃圾回收器回收
GC Roots
- 虚拟机栈,方法区中静态属性、常量引用的对象,本地方法栈的根集合体,从这些对象作为垃圾回收扫描的起始点
垃圾标记阶段
引用计数算法
有对象引用目标对象,则目标对象的引用计数器加1,引用失效时之减1,到0时可进行回收
优点:
简单
回收没延迟
缺点:
增加存储开销和时间开销
循环引用
解除循环引用
手动接触
使用弱引用
可达性分析法
选定活动对象为GC Roots,跟踪引用链条,如果对象和 GC Roots间不可达,则不存在引用链则可回收
简单,解决循环引用,防止内存泄漏
垃圾回收阶段
标记清除法
解释
找到GC Root对象,即那些一定不会被回收的对象,如正执行方法内局部变量引用的对象、静态变量引用的对象
标记阶段:沿着GC Root对象的引用链找,在直接或间接引用到的对象加上标记
清除阶段:释放未加标记的对象占用的内存
要点
- 缺点是会产生内存碎片
标记整理法
解释
标记阶段、清理阶段与标记清除法类似
多了整理动作:将存活对象向一端移动,避免内存碎片产生
特点
避免内存碎片产生
性能较慢
标记复制法
解释
将整个内存分成两个大小相等的区域,
from和to,其中to总是处于空闲,from存储新创建的对象标记阶段与前面的算法类似
在找出存活对象后,将它们从
from复制到to区域,复制的过程中自然完成了碎片整理复制完成后,交换
from和to的位置
特点
不会产生碎片
空间浪费
分代回收法
回收区域是堆内存,不包括虚拟机栈
GC大都采用了分代回收思想
大部分对象朝生夕灭,用完立刻就可以回收,少部分对象会长时间存活,每次很难回收
根据这两类对象的特性将回收区域分为新生代和老年代,新生代采用标记复制法、老年代一般采用标记整理法
根据GC的规模可以分成Minor GC,Mixed GC,Full GC
过程
伊甸园
eden,最初对象都分配到这里,与幸存区survivor(分成from和to)合称新生代伊甸园内存不足,标记伊甸园与
from(现阶段没有)的存活对象将存活对象采用复制算法复制到
to中,复制完毕后,伊甸园和from内存都得到释放将
from和to交换位置经过一段时间后伊甸园的内存又出现不足
标记伊甸园与
from(现阶段没有)的存活对象将存活对象采用复制算法复制到
to中复制完毕后,伊甸园和
from内存都得到释放将
from和to交换位置老年代
old,当幸存区对象熬过几次回收(最多15次),晋升到老年代(幸存区内存不足或大对象会导致提前晋升)
GC规模
Minor GC:新生代的垃圾回收,暂停时间短
Mixed GC:新生代+老年代部分区域的垃圾回收,G1收集器特有
Full GC:新生代+老年代完整垃圾回收,暂停时间长,应尽力避免
- 通过
System.gc()的调用,会显式触发Full GC,
- 通过
三色标记
黑色:已标记
灰色:标记中
白色:还未标记
垃圾回收器
Serial GC:串行回收
- 它为单线程环境设计并且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境
ParNew GC:并行回收
Parallel GC:吞吐量有限
- 多个垃圾回收线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景
CMS GC:注重响应时间
- 用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行
G1 GC:响应时间与吞吐量兼顾
- G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收
STW
Stop-the-World ,GC时整个应用程序线程都会被暂停,没有任何响应
所有GC都有这个事件
并行并发
并行:多条垃圾收集线程并行工作,用户线程仍处于等待状态
- ParNew、Parallel Scavenge、Parallel Old
串行:若内存不够则程序暂停,启动JVM垃圾回收器进行垃圾回收。回收完再启动程序的线程
并发:用户线程与垃圾收集线程同时执行(可能会交替执行),垃圾回收时不会停顿用户程序的运行
- CMS、G1
泄漏溢出
内存溢出OOM:没有空闲内存,并且垃圾收集器也无法提供更多内存
申请的内存超出了JVM能提供的内存大小,垃圾回收器也已经没有空间可回收时会出现
常见错误
Java.lang.StackOverflowError:栈溢出错误,深度方法调用导致出不来栈,栈爆了Java.lang.OutOfMemoryError:Java heap space:堆内存不够Java.lang.OutOfMemeoryError:GC overhead limit exceeded:GC回收时间过长Java.lang.OutOfMemeoryError:Direct buffer memory:内存不够Java.lang.OutOfMemeoryError:unable to create new native thread:非root用户测试Java.lang.OutOfMemeoryError:Metaspace:元空间溢出
内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存
JavaEE
Web
JSP
与
Servlet的区别- JSP的本质就是
Servlet,JSP经编译后就变成Servlet - JSP更擅长页面显示,
Servlet更擅长逻辑控制
- JSP的本质就是
内置对象
request:封装客户端的请求,包含来自GET或POST请求的参数response:封装服务器对客户端的响应pageContext:页面上下文,可以获取页面其他对象session:封装用户会话的对象application:封装服务器运行环境的对象out:输出服务器响应的输出流对象config:Web应用的配置对象page:JSP页面本身,相当于Java程序中的thisexception:封装页面抛出异常的对象
作用域
page:一个页面相关的对象和属性request:客户端发出的一个请求相关的对象和属性session:客户端与服务器建立的一次会话相关的对象和属性application:整个Web应用程序相关的对象和属性
组件
Servlet
用来接收处理请求,完成响应的一段小程序
使用
doGet(),doPost()组成
SevletConfig:封装当前Servlet的配置信息ServletContext:四大域对象之一HttpServletRequest:每次发送请求的详细信息会被Tomcat自动封装成一个request对象HttpServletResponse:响应首行、响应头(对浏览器的一些命令)和响应体(浏览器收到要解析的数据)
Listener
ServletContextListener:生命周期监听器,监听ServletContext的创建和销毁(服务器的启动和停止)
Filter
- 放行后通过
chain.doFilter(request,response)进行后续调用
- 放行后通过
Cookie
浏览器端保存少量数据的一种技术
纯文本且不支持中文
浏览器每次访问网站都会携带该网站下保存的Cookie
Session
服务器端保存当前会话大量数据的一种技术
存在服务器上结构类似于
HashMap的散列表格文件,key存储用户的sessionID,用户向服务器发送请求时会带上这个sessionID,服务器就可以从中取出对应的值和Cookie的区别
Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中
Cookie是客户端保存用户信息的一种机制,也是实现Session的一种方式
令牌机制
第一次访问页面时生成令牌,只要不刷新页面,重发请求时令牌不会变化
Servlet第一次收到令牌进行比对,比对完毕后更换或者删除令牌
防止表单重复提交
禁止Cookie后实现Session的方式
手动通过URL传值、隐藏表单传递SessionID
用文件、数据库等形式保存SessionID,在跨页过程中手动调用
Spring
- 一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架
AOP
- 面向切面(方面)编程,对业务各个部分进行隔离,降低业务各部分之间的耦合度,提高程序可重用性,提高开发效率
- 不通过修改源代码方式在主干功能里面添加新功能
IOC
将原本在程序中手动创建对象的控制权,交由Spring框架来管理
控制:指的是对象创建的权力
反转:控制权交给外部环境
通过DI(依赖注入)实现
Bean
被IOC容器所管理的对象
线程不安全
将一个类声明为Bean的注解
@Component:通用的注解@Repository:持久层@Service:服务层@Controller:控制层
@Component与@Bean@Component- 作用于类
@Bean- 作用于方法
- 自定义性更强
作用域
singleton:容器中只有唯一的Bean实例,默认prototype:每次获取都会创建一个新的Bean实例request:每次HTTP请求都会产生一个新的Bean实例,该Bean仅在当前HTTP Request内有效session:每次新会话的HTTP请求都会产生一个新的Bean实例,该Bean仅在当前HTTP Session内有效application:每个Web应用在启动时创建一个Bean实例,该Bean仅在当前应用启动时间内有效websocket:每次WebSocket会话产生一个Bean实例
生命周期
Bean容器找到配置文件Spring Bean的定义Bean容器利用Java Reflection API创建一个Bean的实例- 如果涉及到一些属性值利用
set()方法设置一些属性值 - 如果
Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字 - 如果
Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例 - 如果
Bean实现了BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例 - ……
- 要销毁
Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法 - 当要销毁
Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性执行指定的方法
Spring MVC
- 通过将业务逻辑、数据、显示分离来组织代码
组件
DispatcherServlet:中央控制器,把请求给转发到具体的控制类Controller:具体处理请求的控制器HandlerMapping:映射处理器,负责映射中央处理器转发给Controller时的映射策略ModelAndView:服务层返回的数据和视图层的封装类ViewResolver:视图解析器,解析具体的视图Interceptors:拦截器,负责拦截我们定义的请求然后做处理工作
执行流程
客户端发送请求,
DispatcherServlet拦截请求DispatcherServlet根据请求信息调用HandlerMappingHandlerMapping根据URI去匹配查找能处理的Handler(Controller控制器),并会将请求涉及到的拦截器和Handler一起封装
DispatcherServlet调用HandlerAdapter适配执行HandlerHandler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServletViewResolver会根据逻辑View查找实际的ViewDispaterServlet把返回的Model传给View渲染视图把
View返回给请求者
统一异常处理
- 使用到
@ControllerAdvice+@ExceptionHandler - 当
Controller中的方法抛出异常的时候,由被@ExceptionHandler注解修饰的方法进行处理
设计模式
- 工厂设计模式:使用工厂模式通过
BeanFactory、ApplicationContext创建Bean对象 - 代理设计模式:AOP功能的实现
- 单例设计模式:Bean默认都是单例的
- 模板方法模式:
JdbcTemplate - 包装器设计模式:动态切换不同的数据源
- 观察者模式:Spring事件驱动模型
- 适配器模式:MVC中也是用到了适配器模式适配
Controller
事务
使用代理对象来控制
管理事务
- 在XML配置文件中配置
- 基于注解
- 基于
@Transactional的全注解方式使用最多
- 基于
传播行为
REQUIRED:无论如何这个方法都必须在事务内运行,常用- 需要一个事务
- 外层有事务:用已经存在的事务(包括设置)
- 外层无事务:创建一个事务
- 需要一个事务
REQUIRES_NEW:总是需要创建新的事务,常用- 无论外层有没有存在事务,都自己创建一个事务,在自己的事物内进行运行
SUPPORTS:支持事务- 如果外层有事务,就在该事务内运行,否则就可以不以事务的方式运行
MANDATORY:强制运行在已经存在的事务内- 必须在事务内运行,如果外层已存在事务,就在这个事务内运行,如果外层没有事务,就将抛出异常
NOT_SUPPORTED:不支持运行在事务内- 必须以非事务的方式运行。如果外层已经有事务了,就把外层的事务暂停
NEVER:必须以非事务方式运行- 如果外层有事务,就会抛出异常,否则就会正常运行
NESTED:嵌入式的事务- 基于存档点的事物
失效问题
可能导致失效的传播行为
SUPPORTSNOT_SUPPORTEDNEVER
本地设置失效问题
被同一个
Service里的方法所调用的方法的任何设置都无效(与调用方法共用一个事务)原因
- 绕过了代理对象
- 不能使用
this
解决
引入并开启
AspectJ动态代理功能以后的所有动态代理都由
AspectJ创建没有接口也能创建动态代理
用代理对象本类调用:
AopContext.currentProxy()
Spring Boot
- 是一个服务于框架的框架,简化了Spring众多框架所需的大量繁琐的配置文件,使编码,配置,部署,监控,变简单
自动配置
过程
- Spring Boot从主方法里启动,主要加载
@SpringBootApplication注解主配置类 - 这个注解主配置类里边最主要的功能就是Spring Boot开启了一个
@EnableAutoConfiguration注解的自动配置功能 @EnableAutoConfiguration利用EnableAutoConfigurationImportSelector选择器给Spring容器中来导入一些组件- 把
spring.factories文件中的所有配置EnableAutoConfiguration的值加入到Spring容器中 - 为每个自动配置类进行自动配置
- 根据当前条件判断,决定
HttpEncodingProperties配置类是否生效
- Spring Boot从主方法里启动,主要加载
不使用
@Import直接引入自动配置类- 直接用
@Import引入的配置解析优先级较高 - 自动配置类的解析应该在主配置没提供时作为默认配置
- 直接用
Spring Cloud
用来开发一种由很多小的服务来组成应用系统的架构,是微服务的协调者
服务与服务之间可以采用不同的技术栈,并通过一些轻量级的通信机制来进行交互
Spring Boot专注于快速,方便的开发单个微服务个体,Spring Cloud关注全局的服务治理框架
CAP
- C:Consistency,一致性
- A:Availability,可用性
- P:Partition tolerance,分区容错性
- CA:单点集群,扩展性不强
- CP:满足一致性,性能不高
- AP:满足可用性,一致性要求低
注册发现
Eureka:AP
Consul:CP
Zookeeper:CP
服务调用
模式
服务直连:无法保证的可用性
客户端发现:客户端从注册列表查询,自行用负载均衡算法从中选出一个
服务端发现:独立部署在服务器,由该服务器判断并转发
常见消费者
httpclient:用来提供高效以及功能丰富的HTTP协议工具包ribbon:内置负载均衡工具,基于HTTP和TCP来实现客户端负载均衡的经常会与Eureak结合来使用简单轮询
加权时间轮讯
区域感知轮询
随机负载均衡
feign:内置的声明式的Web服务客户端,可插拔,支持注解
统一入口
介于客户端和服务器端之间的中间层,统一的API入口组件
缺点
- 可能引发单点故障
优点
避免泄漏内部信息
易于监控:可以在网关收集监控数据并将其推送到外部系统进行分析
易于认证:可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证
减少客户端与各个微服务之间的交互次数
Nginx
HTTP和反向代理的服务器,高并发强大
反向代理:对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源
负载均衡:将原先请求集中到单个服务器上的情况改为将请求分发到多个服务器上,将负载分发到不同的服务器
轮询(默认):后端服务器down掉能自动剔除
ip_hash:请求按访问IP的hash结果分配,每个访客固定访问一个后端服务器,可以解决session共享问题fair(第三方):按后端服务器的响应时间来分配请求,响应时间短的优先分配
动静分离:动态请求跟静态请求分开,使用Nginx处理静态页面,Tomcat处理动态页面
静态文件独立成单独的域名,放在独立的服务器上(主流使用)
动态跟静态文件混合在一起发布,通过nginx分开
集群:client的请求被master收到会通知给worker们,由worker们争抢,由争抢到的woker干具体工作
- 每个woker是独立的进程,如果有其中的一个woker出现问题,其他woker不受影响而继续进行争抢实现请求过程,不会造成服务中断
Gateway
基于异步非阻塞模型
路由:基本模块,由ID、目标URI、断言和过滤器组成,如断言为true则匹配该路由
断言:可以通过匹配HTTP请求中的所有内容(请求头或请求参数),如果请求与断言相匹配则进行路由
过滤:可以在请求被路由前或者之后对请求进行修改
路由转发 + 过滤器链
配置管理
集中化配置
可按来源环境等进行分类配置
Config:Server端可以在所有管理中管理应用程序的外部属性,默认使用git
消息驱动
Stream
作用
屏蔽底层消息中间件的差异
降低切换成本
统一消息的编程模型
通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离
INPUT对应于消费者
OUTPUT对应于生产者
遵循发布-订阅模式
Topic主题进行广播
在RabbitMQ就是Exchange
在Kakfa中就是Topic
目前仅支持RabbitMQ和Kakfa
降级熔断
提高系统稳定、减少性能损耗、及时响应、阀值可控
服务降级
- 从整体负荷考虑,在客户端实现,与服务器没有关系,调用
fallback方法给用户友好提示
- 从整体负荷考虑,在客户端实现,与服务器没有关系,调用
服务熔断
应对雪崩效应的链路保护机制
某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路
跳过
fallback拒绝访问
hystrix
路由跟踪
- Sleuth
- 类似于tracert
Nacso
- 服务发现、服务配置、服务元数据及流量管理
- Nacos就是注册中心+配置中心的组合Nacos = Eureka + Config + Bus
- AP/CP
Seata
解决分布式事务
- 分布式事务是由一批分支事务组成的全局事务,通常分支事务只是本地事务
在业务类上加
@GlobalTransactional组成
全局唯一事务的XID:只要在同一ID下不管几个库,都能证明是一套的全局下面的统一体
事务协调器TC:维护全局和分支事务的状态,驱动全局事务提交或回滚
事务管理器TM:定义全局事务的范围:开始全局事务、提交或回滚全局事务
资源管理器RM:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
过程
TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
XID在微服务调用链路的上下文中传播
RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
TM向TC发起针对XID的全局提交或回滚决议
TC调度XID下管辖的全部分支事务完成提交或回滚请求
Sentinel
- 流量治理组件,流量控制、流量路由、熔断降级、系统自适应保护
- 懒加载
- 单独一个组件,可以独立出来
- 直接界面化的细粒度统一配置
- 核心库不依赖任何框架/库
- 控制台基于 Spring Boot 开发,打包后可以直接运行
雪花算法
每秒能够产生26万个自增可排序的ID
能够按照时间有序生成
生成ID的结果是一个64bit大小的整数
分布式系统内不会产生ID碰撞,效率高
依赖机器时钟,如果机器时钟回拨,会导致重复ID生成
结构
1bit符号位:不用
因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为041bit时间戳位:69年(到2039年)
10bit工作进程位:5位DataCenterId和5位Workerld,用来记录工作机器ID
12bit序列号为:记录同毫秒内产生的不同id
分布式锁
- 对于分布式系统,通常使用分布式锁来控制多个服务对共享资源的访问
- 一般会选择基于Redis或者ZooKeeper实现分布式锁
分布式事务
- 2PC
- TCC
- 最大努力通知
- 可靠消息 + 最终一致性
MyBatis
Dao接口里的方法可以重载,但是Mybatis的XML里的ID不允许重复能执行一对一、一对多的关联查询和延迟加载
#{}与${}
#{}:预编译处理,处理时会将其替换为?,调用PreparedStatement的set()方法来赋值,可以防止SQL注入${}:字符串替换,处理时会将其替换为变量的值
分页
- SQL分页
- 插件分页
- RowBounds分页
动态sql
- 在XML映射文件内,以标签的形式编写动态SQL,完成逻辑判断和动态拼接SQL的功能
- 标签
<if></if><where></where><choose></choose><foreach></foreach><bind/>
缓存
一级缓存
本地缓存,作用域默认:
sqlSession是一直开启的
SqlSession级别的一个HashMap当Session
flush或close后,该Session中的所有Cache将被清空本地缓存不能被关闭,可以调用
clearCache()清空本地缓存或改变缓存的作用域
二级缓存
全局作用域缓存
默认不开启,需要手动配置
MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现
Serializable接口一个
namespace对应一个二级缓存二级缓存在
SqlSession关闭或提交之后才会生效
执行器
SimpleExecutor- 每执行一次
update或select就开启一个Statement对象,用完立刻关闭Statement对象
- 每执行一次
ReuseExecutor执行
update或select时以SQL作为key查找Statement对象,存在就使用,不存在就创建,用完后不关闭Statement对象,放在Map内供下次使用重复使用
Statement对象
BatchExecutor- 执行
update时将所有sql都添加到批处理中等待统一执行,它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后等待逐一执行executeBatch()批处理,与JDBC批处理相同
- 执行
中间件
MySQL
三范式
属性不可分割
不能存在部分函数依赖
不能存在传递函数依赖
ACID
Atomicity:原子性
- 事务是最小的执行单位,不允许分割
Consistency:一致性
- 执行事务前后,数据保持一致
Isolation:隔离性
各并发事务 之间数据库是独立的
问题
脏读:读已修改未提交
幻读:读新增未提交
不可重复读:读已提交
事务隔离级别
读未提交:最低隔离级别,事务未提交前就可被其他事务读取
- 脏读、幻读、不可重复读
读提交:一个事务提交后才能被其他事务读取到
- 幻读、不可重复读
可重复读:默认级别,保证多次读取同一个数据时,其值和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据
- 幻读
串行化:序列化,代价最高最可靠的隔离级别
Durability:持久性
- 一个事务被提交之后,对数据库中数据的改变是持久的
DROP/DELETE/TRUNCATE
DROP:直接将表都删除掉DELETE:删除一(多)行的数据TRUNCATE:清空表中数据,自增ID又从1开始速度:
DROP>TRUNCATE>DELETE
索引
帮助数据库高效获取数据(查找快,排好序)的数据结构
主流的数据库引擎的索引由B+树实现,B+树的搜索效率可以到达二分法的性能
还有哈希索引(Hash Index)
需要建立索引的情况
主键自动建立唯一索引
频繁作为查询条件的字段
查询中与其它表关联的字段,外间关系建立索引
高并发下倾向创建组合索引
查询中排序的字段
统计或分组字段
不要建立索引的情况
表记录太少
频繁更新的字段:每次更新数据时还会更新索引
WHERE条件里用不到的字段数据重复且分布平均的字段
失效情况
范围条件右边的列
SELECT *!=或<>通配符开头
%X字符串不加单引号
用
OR连接
验证索引是否满足需求的方法
- 使用
EXPLAIN查看SQL是如何执行查询语句
- 使用
加载顺序
FROM,ON,JOIN…,LIMIT
引擎
InnoDB
默认引擎
支持事务,行级锁和外键约束
支持表锁和行锁,默认为行锁
MyIASM
- 不支持事务,行级锁和外键
- 只支持表锁
主从复制
一主多从
slave会从master读取binlog进行数据同步
master将更改记录到二进制日志中
slave将master的日志拷贝到自己的中继日志
slave重做中继日志中的事件,将改变应用到自己的数据库中
复制是异步且串行化的
最大的问题是时延
锁
行锁与表锁
表级锁(偏读)
开销小,加锁快,不会出现死锁
锁定粒度大,发生锁冲突的概率最高,并发量最低
针对非索引字段加的锁
行级锁(偏写)
开销大,加锁慢,会出现死锁
锁力度小,发生锁冲突的概率小,并发度最高
针对索引字段加的锁
页锁:性能和粒度介于表锁行锁之间
优化
问题排查的方法
使用
SHOW PROCESSLIST命令查看当前所有连接信息使用
EXPLAIN命令查询SQL语句执行计划使用
SHOW PROFILE分析当前会话语句执行资源消耗情况开启慢查询日志,查看慢查询的SQL
性能优化
为搜索字段创建索引
最好是全值匹
遵守最左前缀要
LIKE,%写最右尽量不使用不等空值还有
OR字符串要加引号
避免使用
SELECT *,列出需要查询的字段垂直分割分表
选择正确的存储引擎
IN与EXISTIN- 只会执行一次,会把符合条件的记录全部查出来
- 适用:外表大,子查询表小
EXIST- 表示子查询是否存在,如果存在则为
true不存在为false,不返回任何结果 - 适用:外表小,子查询表大
- 表示子查询是否存在,如果存在则为
分区分表分库
分区
- 物理上将表对应的三个文件分割许多小块,可以分配到不同磁盘中
- 优化查询提高吞吐量
分库
把原本存储于一个库的表拆分到多个数据库上
减少增量数据写入时锁对查询的影响
无法节约表单数量太大的问题
分表
垂直拆分:把主要的字段放在一起作为主要表,其他的放入次要表,主次表关系是一对一的
水平拆分:容量大于500W时再建议水平拆分
Redis
概述
使用单线程 + 多路IO复用技术
命令不区分大小写,
key区分大小写支持发布订阅
CP
数据类型
string最基本的数据类型,
value最多512M二进制安全,可以包含任何数据,比如jpg图片或者序列化的对象
功能
获取/设置一个或多个字段值
数字增减(可自定义步长)
获取字符串长度
分布式锁
应用场景:文章点赞
list单键多值
简单的字符串列表,按照插入顺序排序,可以添加一个元素到列表的头部或尾部
底层是双向链表,对两端的操作性能很高,通过索引下标操作中间节点的性能会较差
功能
向列表左右添加一个或多个元素
查看列表
获取列表中元素个数
应用场景:公众号订阅
hash是一个
String类型的field和value的映射表,hash适合用于存储对象类似Java里面的Map<String, Object>功能
获取/设置一个或多个字段值
获取所有字段值
获取目标Key的全部数量
应用场景:购物车
set与
list类似,set可以自动排重,适合存储不重复的数据,提供判断某个成员是否在set集合内的重要接口是
String类型的无序集合,底层是value为null的hash表,添加,删除,查找的复杂度都是O(1)功能
获取集合中所有元素
判断元素是否在集合中
随机弹出一个元素(
pop/push)集合运算(交集、并集、差集)
应用场景:点赞,社交关系
z-set与set类似,是有序集合,每个元素都关联了一个评分(score),这个评分被用来排序,集合的成员唯一,但评分可以重复
可以很快的根据评分(score)或者次序(position)来获取一个范围的元素
功能
设置/获取元素分数
元素排序并返回指定范围元素或个数
获取元素排名
按排名范围删除元素
应用场景:热度排序
bitmaps本身不是一种数据类型, 实际是字符串(key-value) ,可以对字符串的位进行操作
类似以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在
bitmaps叫偏移量
hyperloglog- 基数统计的算法,在输入元素的数量或者体积非常大时,计算基数所需的空间总是固定且是很小的
grospatial元素的2维坐标,在地图上就是经纬度
提供经纬度设置,查询,范围查询,距离查询,经纬度
hash等常见操作Java客户端
- Redisson、Jedis、lettuce等
事务
不保证原子性:如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
multi:开启事务watch:监听键值,若该key再事务执行前被修改则回滚unwatch:取消监听exec:执行事务,若监听Key再事务执行前被修改则回滚否则执行discard:回滚
持久化
RDB(RedisDatabase)
在指定的时间间隔内将内存中的数据集快照写入磁盘,恢复时是将快照文件直接读到内存里
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,RDB比AOF更高效
缺点是最后一次持久化后的数据可能丢失
命令
save:主线程执行,会阻塞主线程bgsave:子线程执行,不会阻塞主线程,默认
AOF(AppendOnlyFile)
- 以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录)
- 根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
- 策略
appendfsync always:每次有数据修改发生时都会写入AOF文件- 这样会严重降低Redis的速度
appendfsync everysec:每秒钟同步一次,显式地将多个写命令同步到硬盘appendfsync no:让操作系统决定何时进行同步
建议同时开启
Redis重启时优先载入AOF,而RDB更适合备份数据库(AOF不断在变化)
集群中只在Slave上持久化RDB,15分钟备份一次
缓存
SoR(system-of-record):记录系统,存储原始数据的系统
Cache:缓存,是SoR的快照数据,访问速度比SoR要快
回源:Cache没有命中需要回到SoR读取数据这叫做回源
模式
Cache-Aside:业务代码围绕Cache编写,由业务代码维护缓存
读:先从缓存中获取,没有查询SoR,再放入缓存
写:先将数据写入SoR,执行成功后立即同步写入缓存(双写模式)
Cache As SoR:把Cache当做SoR,所有操作都是对Cache进行,然后Cache委托SoR进行数据的真实读写
read-through:先调用Cache,Cache不命中,由Cache回源到SoR- MyBatis
get()缓存机制
- MyBatis
write-through:调用Cache写数据,然后由Cache负责写缓存和写SoR- MyBatis
update()缓存机制
- MyBatis
write-behind:异步写,异步成功后可以实现批量写、合并写、延时写等
Copy-Pattern(缓存数据复制方式)
Copy-On-Read读时复制:读取到的缓存的值,复制内容封装一个新的对象Copy-On-Write写时复制:给缓存中写的值,复制一个新的对象写入
保证缓存和数据库数据的一致性
合理设置缓存的过期时间
数据库增删改时同步更新Redis,使用事务保证数据的一致性
问题
缓存穿透:大量访问不存在的
key- 缓存无效
key - 布隆过滤器
- 缓存无效
缓存击穿:大量访问过期的
key预先设置热门数据
实时调整
使用锁
缓存雪崩:大量访问大量过期的
key- 问题
- 与缓存击穿相似区别是缓存中的大量key失效,对底层系统的冲击非常可怕
- 问题
解决
构建多级缓存架构
- nginx缓存 + redis缓存 + 其他缓存(ehcache等)
随机设置缓存的失效时间
集群
主从复制
主写从读
读写分离,容灾恢复
原理
- slave启动成功连接到master后会发送一个sync命令
master接到命令启动后台的存盘进程,收集所有接收到的用于修改数据集命令
后台进程执行完毕,master将传送整个数据文件到slave完成一次完全同步(全量复制)
全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要是重新连接master,就会进行一次完全同步(全量复制)
- slave启动成功连接到master后会发送一个sync命令
哨兵模式
监控master是否故障,若故障了根据投票自动将slave转为master
可以监控多个master
分布式锁
使用场景:保证多个服务间同一时刻同一用户只能有一个请求,防止出现并发攻击
使用Lua脚本是为了保证解锁操作的原子性(不能保证服务器挂掉后锁的释放)
使用Redisson客户端
需要同时满足四个条件
互斥性:在任意时刻,只有一个客户端能持有锁
不会发生死锁:即使一个客户端持有锁时崩溃没有主动解锁,也能保证后续其他客户端能加锁
解铃还须系铃人:加锁和解锁必须是同一个客户端
加锁和解锁必须具有原子性
内存
- 过期时间
- 有助于缓解内存的消耗
- 保证内存实时性
- 删除策略
- 惰性删除:只会在取出
key的时候才对数据进行过期检查 - 定期删除:每隔一段时间抽取一批
key执行删除过期key操作
- 惰性删除:只会在取出
- 淘汰策略
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰volatile-random:从已设置过期时间的数据集中任意的数据淘汰allkeys-lru:从数据集中挑选最近最少使用的数据淘汰allkeys-random:从数据集中任意的数据淘汰no-enviction:禁止驱逐数据
RabbitMQ
重要角色
生产者:消息的创建者,负责创建和推送数据到消息服务器
消费者:消息的接收方,用于处理数据和确认消息
代理:RabbitMQ本身,不生产消息
重要组件
ConnectionFactory(连接管理器):应用程序与Rabbit之间建立连接的管理器,程序代码中使用
Channel(信道):消息推送使用的通道
Exchange(交换器):用于接受和分配消息
Queue(队列):用于存储生产者的消息
RoutingKey(路由键):用于把生成者的数据分配到交换器上
BindingKey(绑定键):用于把交换器的消息绑定到队列上
virtual host
- 类似于namespace,多个不同的用户使用同一个RabbitMQ server提供的服务时,可划分出多个vhost,每个用户在自己的vhost创建exchange/queue等
广播类型
fanout(扇出):所有绑定到此Exchange的Queue都可以接收消息direct(直连):routingKey全值匹配的Queue可以接收消息topic(主题):符合规则的routingKey的Queue可以接收消息#:多个词*:一个词
消息
发送
- 客户端和服务器之间只有一个连接,一个连接中有多条信道
保证消息稳定性的方法
事务
将Channel设置为
Confirm(确认)模式
避免消息丢失
消息持久化
发送端确认:保证每个消息成功发送
publisher-confirm:确认消息抵达Brokerpublisher-returns:确认消息正确抵达Queue
消费端确认:保证每个消息被正确消费
- 投递可靠:ACK确认模式为手动
- 将每一条发送的消息都记录在数据库,定期发送失败的消息
避免消息重复
将业务逻辑设置成幂等性的
设置防重表,消息被处理过了,就去防重表里面记录
持久化
成功的条件
声明队列必须设置持久化durable设置为
true消息推送投递模式必须设置持久化,deliveryMode设置为
2(持久)消息已经到达持久化交换器
消息已经到达持久化队列
缺点
- 降低了服务器的吞吐量,因为使用的是磁盘而非内存存储
延迟消息队列
消息过期后进入死信交换器,再由交换器转发到延迟消费队列
使用RabbitMQ-delayed-message-exchange插件实现
集群
优点
高可用:某个服务器出现问题,整个RabbitMQ还可以继续使用
高容量:集群可以承载更多的消息量
节点类型
磁盘节点:消息会存储到磁盘
内存节点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型
集群中唯一一个磁盘节点崩溃了会发生的情况
- 唯一磁盘节点崩溃时集群可以保持运行,但不能更改任何东西
集群节点停止顺序
- 先关闭内存节点,最后再关闭磁盘节点。否则可能会造成消息的丢失
Zookeeper
基于观察者模式设计的分布式服务管理框架,文件系统 + 通知机制
全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的
更新请求顺序进行:来自同一个Client的更新请求按其发送顺序依次执行
数据更新原子性:一次数据更新要么成功,要么失败
实时性:在一定时间范围内,Client能读到最新数据
集群中只要有半数以上节点存活,Zookeeper集群就能正常服务
安装奇数台
树状结构:每个节点能存储1MB的数据
使用场景
统一命名服务
统一配置管理
统一集群管理
服务器节点动态上下线
软负载均衡
选举机制
- 总原则:每台机器都投票,交换选票得到每台机器的最终得票, 得票数超过半数即为leader
第二次启动
- EPOCH大 > 事务ID大 > 服务器ID大
- Epoch:每个Leader任期代号
其他
Netty
- 异步的、基于事件驱动的网络应用框架
NIO三大组件
Channel与BufferChannel类似于Stream,是读写数据的双向通道,比Stream更底层
Selector- 配合一个线程来管理多个
Channel获取这些Channel上发生的事 - 不会让线程吊死在一个
Channel上
- 配合一个线程来管理多个
组件
EventLoop事件循环对象,本质是一个单线程执行器(同时维护了一个Selector)
EventLoopGrop事件循环组
Channel一般会绑定其中一个EventLoop,该Channel上的IO事件都由EventLoop处理
保证IO事件处理时的线程安全
优雅关闭
会先切换
EventLoopGroup到关闭状态从而拒绝新的任务的加入在任务队列的任务都处理完成后,停止线程的运行
Stream与ChannelStream不会自动缓冲数据
只支持阻塞API
Channel利用系统提供的发送/接收缓冲区
支持阻塞/非阻塞API
可配合
Selector实现多路复用
Future与Promise异步处理时常用的两个接口
Netty Future:可以同步等待任务结束得到结果,也可以异步方式得到结果,但都是要等任务结束Netty Promise:不仅有Netty Future的功能,而且脱离了任务独立存在,只作为两个线程间传递结果的容器
Handler与PipelineChannelHandler用来处理Channel上的各种事件,分为入站和出站所有
ChannelHandler被连成一串就是PipelinePipeline为双向链表,入站从head开始,出站从tail开始- head ⇋ h1 ⇋ h2 ⇋ h3 ⇋ h4 ⇋ tail
ByteBuf对字节数据的封装
优点
池化:可以重用池中
ByteBuf实例,更节约内存,减少内存溢出的可能读写指针分离:不需要像
ByteBuffer一样切换读写模式可以自动扩容
支持链式调用:使用更流畅
很多地方体现零拷贝
创建位置
堆内存
直接内存
默认
创建和销毁的代价昂贵,但读写性能高,配合池化功能一起用
不受JVM垃圾回收的管理,但要注意及时主动释放
零拷贝
不会拷贝重复数据到JVM内存中
更少的用户态与内核态的切换
不利用CPU计算,减少CPU缓存伪共享
粘包与半包
粘包:发送abc def,接收abcdef
应用层:接收方ByteBuf设置太大
- Netty默认1024
滑动窗口(TCP层面):接收方处理不及时且窗口大小足够大,滑动窗口中缓冲了多个报文
Nagle算法(TCP层面)会造成粘包
半包:发送abcdef,接收abc def
应用层:接收方ByteBuf小
滑动窗口(TCP层面):接收方的窗口小
发送的数据超过MSS限制(数据链路层)
解决
固定分隔符
预设长度:每条消息分为head和body,head中包含body的长度(约定表示长度的定长字节的位置)
网络
七层模型
- 应用层:网络服务与最终用户的一个接口
- 表示层:数据的表示、安全、压缩
- 会话层:建立、管理、终止会话
- 传输层:定义传输数据的协议端口号,以及流控和差错校
- 网络层:进行逻辑地址寻址,实现不同网络之间的路径选择
- 数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验等功能
- 物理层:建立、维护、断开物理连接
TCP与UDP
TCP
面向连接,有状态
点对点
可靠传输,无差错,不丢失,不重复,且按序到达
- 有确认、窗口、重传、拥塞控制机制
面向字节流
对系统资源要求较多,效率低
UDP
面向无连接,无状态
一对一,一对多,多对一和多对多
尽最大努力交付,不可靠传输
具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信
面向报文
对系统资源要求较少,效率高
DHCP、DNS
三次握手与四次挥手
- 三次握手
- 建立可靠的通信信道
- 双方确认自己与对方的发送与接收是正常
- 四次挥手
- TCP是全双工通信,可以双向传输数据
- 任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态
- 当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭TCP连接
HTTP状态码
- 1XX:信息状态码
- 2XX:成功状态码
- 3XX:重定向状态码
- 4XX:客户端错误状态码
- 5XX:服务端错误状态码
HTTP与HTTPS
- HTTP
- 默认80端口
- 运行在TCP之上
- 所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份
- 安全性低
- HTTPS
- 默认443端口
- SSL/TLS之上
- 有传输的内容都经过对称加密,对称加密的密钥用服务器方的证书进行了非对称加密
- 资源消耗高
转发与重定向
转发
服务器收到请求后,从一次资源跳转到另一个资源的操作
浏览器地址栏没有变化
一次请求
共享
Request域中的数据可以转发到
WEB-INF目录下不可访问工程外的资源
重定向
浏览器地址会发生变化
两次请求
不共享
Request域中的数据不能访问
WEB-INF下的资源可以访问工程外的资源
URI与URL
- URI:统一资源标志符,可以唯一标识一个资源,像身份证号
- URL:统一资源定位符,可以提供该资源的路径,像家庭住址
- 是一种具体的URI,URL可以用来标识一个资源,而且还指明了如何
locate这个资源
- 是一种具体的URI,URL可以用来标识一个资源,而且还指明了如何
GET与POST
GET
在浏览器回退时是无害的
浏览器会主动Cache请求
请求只能进行URL编码
参数通过URL传递,有长度限制
更不安全,不能用来传递敏感信息
POST
- 会再次提交请求
- 支持多种编码方式
- POST放在
RequestBody中