java全集持续更新维护
2021-01-20 20:35:51 0 举报
AI智能生成
java 全家桶
作者其他创作
大纲/内容
Nginx
限制域名访问
子主题
常见问题
幂等性
概念
一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变
场景
重复提交、重复请求,重复支付
查询操作、删除操作、唯一索引
产生问题
前端重复提交表单
在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求
用户恶意进行刷单
例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符
接口超时重复提交
很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次
消息进行重复消费
当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费
解决方式
数据库唯一主键
数据库唯一主键的实现主要是利用数据库中主键唯一约束的特性,一般来说唯一主键比较适用于“插入”时的幂等性,其能保证一张表中只能存在一条带该唯一主键的记录。
使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式 ID 充当主键,这样才能能保证在分布式环境下 ID 的全局唯一性
使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式 ID 充当主键,这样才能能保证在分布式环境下 ID 的全局唯一性
主要流程如下:
客户端执行创建请求,调用服务端接口。
服务端执行业务逻辑,生成一个分布式 ID,将该 ID 充当待插入数据的主键,然
后执数据插入操作,运行对应的 SQL 语句。
服务端将该条数据插入数据库中,如果插入成功则表示没有重复调用接口。如果抛出主键重复异常,则表示数据库中已经存在该条记录,返回错误信息到客户端。
客户端执行创建请求,调用服务端接口。
服务端执行业务逻辑,生成一个分布式 ID,将该 ID 充当待插入数据的主键,然
后执数据插入操作,运行对应的 SQL 语句。
服务端将该条数据插入数据库中,如果插入成功则表示没有重复调用接口。如果抛出主键重复异常,则表示数据库中已经存在该条记录,返回错误信息到客户端。
数据库乐观锁
数据库乐观锁方案一般只能适用于执行更新操作的过程,我们可以提前在对应的数据表中多添加一个字段,充当当前数据的版本标识。
这样每次对该数据库该表的这条数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据中的版本标识的值。
这样每次对该数据库该表的这条数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据中的版本标识的值。
子主题
防重 Token 令牌
1、针对客户端连续点击或者调用方的超时重试等情况,例如提交订单,此种操作就可以用 Token 的机制实现防止重复提交。
2简单的说就是调用方在调用接口的时候先向后端请求一个全局 ID(Token),请求的时候携带这个全局 ID 一起请求(Token 最好将其放到 Headers 中),后端需要对这个 Token 作为 Key,用户信息作为 Value 到 Redis 中进行键值内容校验,如果 Key 存在且 Value 匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的 Key 或 Value 不匹配就返回重复执行的错误信息,这样来保证幂等操作
2简单的说就是调用方在调用接口的时候先向后端请求一个全局 ID(Token),请求的时候携带这个全局 ID 一起请求(Token 最好将其放到 Headers 中),后端需要对这个 Token 作为 Key,用户信息作为 Value 到 Redis 中进行键值内容校验,如果 Key 存在且 Value 匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的 Key 或 Value 不匹配就返回重复执行的错误信息,这样来保证幂等操作
1、服务端提供获取 Token 的接口,该 Token 可以是一个序列号,也可以是一个分布式 ID 或者 UUID 串。
2、客户端调用接口获取 Token,这时候服务端会生成一个 Token 串。
3、然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。
4、将 Token 返回到客户端,客户端拿到后应存到表单隐藏域中。
5、客户端在执行提交表单时,把 Token 存入到 Headers 中,执行业务请求带上该 Headers。
6、服务端接收到请求后从 Headers 中拿到 Token,然后根据 Token 到 Redis 中查找该 key 是否存在。
7、服务端根据 Redis 中是否存该 key 进行判断,如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。
2、客户端调用接口获取 Token,这时候服务端会生成一个 Token 串。
3、然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。
4、将 Token 返回到客户端,客户端拿到后应存到表单隐藏域中。
5、客户端在执行提交表单时,把 Token 存入到 Headers 中,执行业务请求带上该 Headers。
6、服务端接收到请求后从 Headers 中拿到 Token,然后根据 Token 到 Redis 中查找该 key 是否存在。
7、服务端根据 Redis 中是否存该 key 进行判断,如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。
下游传递唯一序列号
1、所谓请求序列号,其实就是每次向服务端请求时候附带一个短时间内唯一不重复的序列号,该序列号可以是一个有序 ID,也可以是一个订单号,一般由下游生成,在调用上游服务端接口时附加该序列号和用于认证的 ID。
2、当上游服务器收到请求信息后拿取该 序列号 和下游 认证ID 进行组合,形成用于操作 Redis 的 Key,然后到 Redis 中查询是否存在对应的 Key 的键值对,根据其结果:
3、如果存在,就说明已经对该下游的该序列号的请求进行了业务处理,这时可以直接响应重复请求的错误信息。
2、当上游服务器收到请求信息后拿取该 序列号 和下游 认证ID 进行组合,形成用于操作 Redis 的 Key,然后到 Redis 中查询是否存在对应的 Key 的键值对,根据其结果:
3、如果存在,就说明已经对该下游的该序列号的请求进行了业务处理,这时可以直接响应重复请求的错误信息。
下游服务生成分布式 ID 作为序列号,然后执行请求调用上游接口,并附带唯一序列号与请求的认证凭据ID。
上游服务进行安全效验,检测下游传递的参数中是否存在序列号和凭据ID。
上游服务到 Redis 中检测是否存在对应的序列号与认证ID组成的 Key,如果存在就抛出重复执行的异常信息,然后响应下游对应的错误信息。如果不存在就以该序列号和认证ID组合作为 Key,以下游关键信息作为 Value,进而存储到 Redis 中,然后正常执行接来来的业务逻辑
上游服务进行安全效验,检测下游传递的参数中是否存在序列号和凭据ID。
上游服务到 Redis 中检测是否存在对应的序列号与认证ID组成的 Key,如果存在就抛出重复执行的异常信息,然后响应下游对应的错误信息。如果不存在就以该序列号和认证ID组合作为 Key,以下游关键信息作为 Value,进而存储到 Redis 中,然后正常执行接来来的业务逻辑
对比图
对象克隆
必要条件
深克隆
浅克隆
Nginx
限制域名访问
子主题
子主题
JVM虚拟机
JRE/JDK/JVM关系
JDK
JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件
JRE
JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可
JVM
原理
JVM是Java Virtual Machine(Java虚拟机)的缩写,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能实现的。java虚拟机包括一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行
关系
JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台
JVM模式
简图
配置
Java\jre1.8.0_181\lib\amd64\jvm.cfg,64位支持server模式
Java\jre1.8.0_181\lib\i386\jvm.cfg, 32位支持server 、 client 模式切换
配置模式:
-server KNOWN
-client IGNORE
-server KNOWN
-client IGNORE
客户端模式
Client 模式启动时,速度较快,启动之后不如 Server,适合用于桌面等有界面的程序
服务器模式
-Server 模式启动时,速度较慢,但是启动之后,性能更高,适合运行服务器后台程序
JVM内存划分
程序计数器(线程私有)(当前正在执行字节码地址)
作用
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制
多线程情况下,程序计数器只对当前线程执行指令的位置,线程切换会读取线程上次执行程序计数器来依次执行
可以看做是当前线程所执行的字节码的行号指示器
描述
是一块较小的内存空间
线程私有,每条线程都有自己的程序计数器
随着线程的创建而创建,随着线程的结束而销毁
是唯一一个不会出现OutOfMemoryError的内存区域
堆(线程共享)(class对象)
所有线程共享
Java堆在虚拟机启动时创建,是Java虚拟机所管理的内存中最大的一块。Java堆的唯一目的就是存放对象实例和数组
结构
新生代
流程图
Eden:Java新对象的出生地(如果新创建的对象占用内存很大则直接分配给老年代)。当Eden区内存不够的时候就会触发一次MinorGc,对新生代区进行一次垃圾回收
ServivorFrom(幸存区0):上一次GC的幸存者,作为这一次GC的被扫描者
ServivorTo(幸存区1):保留了一次MinorGc过程中的幸存者,回收一次未被回收
老年代
触发MinorGC的条件:
1 在进行MajorGC之前,一般都先进行了一次MinorGC,使得有新生代的对象进入老年代,当老年代空间不足时就会触发MajorGC。
2 当无法找到足够大的连续空间分配给新创建的较大对象时,也会触发MajorGC进行垃圾回收腾出空间
1 在进行MajorGC之前,一般都先进行了一次MinorGC,使得有新生代的对象进入老年代,当老年代空间不足时就会触发MajorGC。
2 当无法找到足够大的连续空间分配给新创建的较大对象时,也会触发MajorGC进行垃圾回收腾出空间
设置堆内存大小
-Xms:java Heap初始大小。 默认是物理内存的1/64
-Xmx:java heap最大值。默认是物理内存的1/4,建议设为物理内存的一半。不可超过物理内存
永久代(jdk1.8称为元空间)
JDK1.8之前
-XX:PermSize=64m 设置初始大小
-XX:MaxPermSize=256m 设置最大大小
JDK1.8(包含)之后
-XX:MetaspaceSize :初始空间大小
-XX:MaxMetaspaceSize :最大空间大小,默认没有限制
设置参数
-Xmn2g 设置年轻代为2G空间
–XX:NewRatio,–XX:SurvivorRatio、-XX:NewSize and -XX:MaxNewSize : 新生代转老年代、占用比例设置
-XX:+MaxTenuringThreshold :Survivor区中的对象被复制次数才进入老年代
Java 虚拟机栈(线程私有)(正在执行的栈帧-方法)
结构
局部变量表(Local Variable Table)
解释1:存放的是java编译器生成的各种java的基本数据类型(boolean,byte,char,short,float,long,double),对象的引用
解释2:局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。局部变量表的容量以变量槽(Variable Slot)为最小单位,Java虚拟机规范并没有定义一个槽所应该占用内存空间的大小,但是规定了一个槽应该可以存放一个32位以内的数据类型
解释3:局部变量表随着栈帧的创建而创建,它的大小在编译时确定,创建时只需分配事先规定的大小即可。在方法运行过程中,局部变量表的大小不会发生改变
描述
1、一个局部变量可以保存一个类型为boolean、byte、char、short、int、float、reference和returnAddress类型的数据
2、reference类型表示对一个对象实例的引用
1、一个局部变量可以保存一个类型为boolean、byte、char、short、int、float、reference和returnAddress类型的数据
2、reference类型表示对一个对象实例的引用
操作数栈(Operand Stack)
解释1:操作数栈是一个后入先出(Last In First Out,LIFO)栈容器
解释2:操作栈字节码执行过程中指令存取容器,负责存储正在执行的指令序列
动态链接
解释1:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接
解释2:在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池
描述:
Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态连接(Dynamic Linking)。
这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接
Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态连接(Dynamic Linking)。
这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接
方法返回地址
正常完成出口
异常完成出口
描述
Java 虚拟机栈会出现两种异常:
1、StackOverFlowError 若 Java 虚拟机栈的大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度时,抛出 StackOverFlowError 异常。
2、OutOfMemoryError 若允许动态扩展,那么当线程请求栈时内存用完了,无法再动态扩展时,抛出 OutOfMemoryError 异常。
1、StackOverFlowError 若 Java 虚拟机栈的大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度时,抛出 StackOverFlowError 异常。
2、OutOfMemoryError 若允许动态扩展,那么当线程请求栈时内存用完了,无法再动态扩展时,抛出 OutOfMemoryError 异常。
Java 虚拟机栈也是线程私有,随着线程创建而创建,随着线程的结束而销毁
每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程
无GC
调整栈的深度:-Xss1m
本地方法栈(线程私有)(native)
虚拟机栈操作的是java方法的服务栈
本地方法栈操作非虚拟机栈以外的window服务栈
方法区(线程共享)(字节码-.class文件内容)
方法区也有一个别名叫做Non-Heap(非堆),用于与Java堆区分
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等
JMM(Java Memory Model)内存模型
概念:
JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。
主要讲述:线程、工作内存、主内存 之间的关系
1、主内存:主要讲述的是java堆内存中实例的数据部分,也是物理硬件上的内存
2、工作内存:主要讲述虚拟机栈的数据部分
JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。
主要讲述:线程、工作内存、主内存 之间的关系
1、主内存:主要讲述的是java堆内存中实例的数据部分,也是物理硬件上的内存
2、工作内存:主要讲述虚拟机栈的数据部分
线程工作内存 <-> 主内存 八种操作
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
简图
- 关键字
volatile
会使得#Lock指令(汇编指令)基于CPU的缓存锁和MESI的协议实现缓存一致性
防止内存重排:内存屏障
volatile对复合操作无法保证原子性。对单操作可以保证原子性
synchronized
原子性(synchronized )
Synchronized、Lock来保证代码块内的操作是原子的
可见性(volatile、synchronized 、final)
在Java中可以使用volatile关键字来保证多线程操作时变量的可见性
volatile的功能是被其修饰的变量在被修改后可以立即同步到主内存,而被其修饰的变量在每次使用之前都会从主内存刷新。除此之外,synchronized和final两个关键字也可以实现可见性 。volatile也可以看作是轻量级的锁,在其内部使用了Lock指令来解决可见性问题。
volatile的功能是被其修饰的变量在被修改后可以立即同步到主内存,而被其修饰的变量在每次使用之前都会从主内存刷新。除此之外,synchronized和final两个关键字也可以实现可见性 。volatile也可以看作是轻量级的锁,在其内部使用了Lock指令来解决可见性问题。
有序性
在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。只是实现方式有所区别: volatile关键字会禁止指令重排,synchronized关键字保证同一时刻只允许一个线程的操作
volatile 、synchronized
volatile可以禁止指令重排序,synchronized是在同一时刻只允许一个线程操作
volatile可以禁止指令重排序,synchronized是在同一时刻只允许一个线程操作
主要解决的问题:
主内存与工作内存 之间的关系
内存溢出
原因:
java.lang.OutOfMemoryError: Java heap space异常
java.lang.OutOfMemoryError: PermGen space异常
java.lang.StackOverflowError异常
内存参数配置说明
-server:一定要作为第一个参数,在多个CPU时性能佳 ,表示以服务模式启动,启动速度会稍 微慢一点,但性能会高很多。不加这个参数,默认是以客户端模式启动
-Xms:java Heap初始大小。 默认是物理内存的1/64
-Xmx:java heap最大值。默认是物理内存的1/4,建议设为物理内存的一半。不可超过物理内存
-XX:PermSize:设定内存的永久保存区初始大小,缺省值为64M
-XX:MaxPermSize:设定内存的永久保存区最大 大小,缺省值为64M
-XX:SurvivorRatio=2 :生还者池的大小,默认是2,如果垃圾回收变成了瓶颈,您可以尝试定制生成池设置
-XX:NewSize: 新生成的池的初始大小。 缺省值为2M
-XX:MaxNewSize: 新生成的池的最大大小。 缺省值为32M
内存溢出生成文件
-XX:+HeapDumpOnOutOfMemoryError:堆内存溢出生成dump文件
-XX:HeapDumpPath=D:/ : 堆内存溢出生成文件存放位置
常见问题
初始内存太小,无法触发GC回收
堆内存溢出代码
堆内存垃圾回收配置
内存调优工具
MAT(Memory Analyzer)
JProfiler
类加载机制(实例化、半实例化、初始化 )
概念:
1、虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型的过程
2、类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。
类加载器
概念
Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一个部件,负责动态加载Java类到Java虚拟机的内存空间中。类通常是按需加载,即第一次使用该类时才加载。由于有了类加载器,Java运行时系统不需要知道文件与文件系统。对学习类加载器而言,掌握Java的委派概念是很重要的。
每个Java类必须由某个类加载器装入到内存
引导(Bootstrap)类加载器。由原生代码(如C语言)编写,不继承自java.lang.ClassLoader。负责加载核心Java库,存储在<JAVA_HOME>/jre/lib目录中(rt.jar等等基础包)
扩展(Extensions)类加载器。用来在<JAVA_HOME>/jre/lib/ext,[6]或java.ext.dirs中指明的目录中加载 Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。该类由sun.misc.Launcher$ExtClassLoader实现
Apps类加载器(也称系统类加载器)。根据 Java应用程序的类路径(java.class.path或CLASSPATH环境变量)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。该类由sun.misc.Launcher$AppClassLoader实现
可自定义类加载器,继承classLoader 重写
双亲委派机制
流程图:
委派流程
委派图
源码
类委派流程
特点
JVM内存中判断Class对象,依据:包名、类名、类加载器实例相同才认为同一个class对象
破坏双亲委派机制方法,重写classLoader,tomcat是破坏了双亲委派机制
加载过程
加载(类加载器)(内存:方法区)
概念:指的是把class字节码文件从各个来源通过类加载器装载入内存中
区域:字节流所代表的静态存储结构转化为方法区的运行时数据结构
流程
读取:通过一个类的全限定名来获取定义此类的二进制字节流
全限定类名:包名+类型 => java.lang.String
非限定类名:也叫短名 =>String
转换:将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
定义:在Java堆内存中生成一个代表这个类的java.lang.Class的对象,作为方法区中这个类的各种数据的访问入口
结构图
子主题
链接
验证
概念:确保 Class 文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全
作用
文件格式验证
比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?
元数据验证
该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?
字节码验证
保证程序语义的合理性,比如要保证类型转换的合理性
符号引用验证
比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问
准备
概念:主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值(变量默认值,非程序中的使用值)
描述:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易混淆的概念需要强调一下。首先,这时候进行内存分配的只是类变量,而不包括实例变量,实例变量会在对象实例化时随着对象一起分配在 Java 堆中
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。这个阶段中有两个容易混淆的概念需要强调一下。首先,这时候进行内存分配的只是类变量,而不包括实例变量,实例变量会在对象实例化时随着对象一起分配在 Java 堆中
解析
概念:将常量池内的符号引用替换为直接引用的过程
符号引用
即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息
直接引用
可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量
举例:
举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用
举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用
初始化(内存:堆区(class对象))
概念:主要是对类变量初始化(赋予真正程序中值),是执行类构造器的过程
描述:
换句话说,只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
换句话说,只对static修饰的变量或语句进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
垃圾回收
算法
标记-清楚算法(Mark-Sweep):最基础的垃圾回收算法
描述
标记和清楚分为两个阶段:
1、将需要清楚的对象进行标记出来
2、清楚所有打了标记的对象,将空间释放出来
1、将需要清楚的对象进行标记出来
2、清楚所有打了标记的对象,将空间释放出来
优点:
实现简单,不需要进行对象进行移动
缺点
会产生大量的内存碎片,如果需要分配大的内存空间无法分配
简图:
复制算法(Copying)
描述
将内存分为两个相同大小的内存块,每次使用的时候都会在其中的一个内存块中使用,使用完后,内存进行复制,将还在存活的对象复制到另外一个内存块中,然后清除另外一个内存块全部清除,这样内存不会产生碎片
优点
实现简单,效率高,不容易产生碎片
缺点
浪费一部分内存空间,当对象的存活率高的时候会频繁的操作内存复制
简图
标记整理算法(Mark-Compact)(标记压缩算法、标记清除压缩算法)
描述
标记整理算法与标记-清楚算法标记类似:
标记出需要清除的对象,所有存活对象向一端进行移动,将另外一段空闲出来的空间进行统一清理,
可以达到一个完整连续的内存空间
标记出需要清除的对象,所有存活对象向一端进行移动,将另外一段空闲出来的空间进行统一清理,
可以达到一个完整连续的内存空间
优点
解决了标记-清理算法存在的内存碎片问题
仍需要进行局部对象移动,一定程度上降低了效率
缺点
简图
分代收集算法(Generational Collection)
描述
新生代、老年代 分别采用不同的算法进行垃圾回收
分代回收
新生代可以采用 复制算法,只需要牺牲少部分存活对象的复制成本
老年代可以采用 标记清楚算法 或 标记整理算法,存活率对象高,无法快速回收
垃圾收集器
serial收集器:
概念:单线程收集器,在垃圾收集的任务中,其他任务全部停止,等待serial收集器收集完成才恢复
特点:阻塞性垃圾收集,效率低
算法:(新生代 - 标记算法、老年代 - 标记整理算法)
参数设定:-XX:+UseSerialGC
ParNew收集器
概念:
多线程收集器,多个线程并发进行垃圾回收工作(新生代并行回收,老年代串行回收)
ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量
多线程收集器,多个线程并发进行垃圾回收工作(新生代并行回收,老年代串行回收)
ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量
特点:阻塞性垃圾收集,效率低
算法:(新生代 - 标记算法、老年代 - 标记整理算法)
参数设定:
-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制线程数量
parallel收集器
概念:Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。可以通过参数来打开自适应调节策略,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或最大的吞吐量;也可以通过参数控制GC的时间不大于多少毫秒或者比例;新生代复制算法、老年代标记-压缩
特点:关注点系统吞吐量
算法:(新生代 - 标记算法、老年代 - 标记整理算法)
参数控制:-XX:+UseParallelGC
Parallel Old收集器?
概念:
特点
CMS收集器
概念:CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短
标记过程
初始化标记
并发标记
重新标记
并发清除
特点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞吐量
参数控制
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
G1收集器
概念:
G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器
G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器
特点:
空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC
可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了
收集步骤
标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成
Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)
Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)
复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域
触发机制
当前程序空闲,垃圾回收器会自动触发
堆内存空间不足,触发垃圾回收,先轻量GC(新生待) ,后 重量GC(老年代)
对象引用
强引用
概念:强引用(Strong Reference)是最普通的引用,以=进行赋值,例如String s="ni hao"、User user=new User()中的s就是一个强引用
特点:只要有引用存在,那么堆中数据不会被回收,哪怕是OOM
软引用(类:SoftReference)
概念:软引用(SoftReference)是一种比强引用弱的引用
特点:在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象
关键说明:
当发生GC时,虚拟机可能会回收SoftReference对象所指向的软引用,如果空间足够,就不会回收,还要取决于该软引用是否是新创建或近期使用过
在虚拟机抛出OutOfMemoryError之前,所有软引用对象都会被回收
场景:可以是在排除过期数据的情况下。JDK中有个类叫ResourceBundle,内部会使用ConcurrentMap缓存ResourceBundle对象,这里就是使用的软引用机制
弱引用(WeakReference)
概念:是比软引用更弱的一种引用
特点:无论内存空间是否充足,都会将堆中的弱引用的数据空间回收。
场景:较为复杂的数据结构中,为了避免内存泄露而使用的一种引用方式
示例:
ThreadLocal的存在是提供了一种线程内全局的上下文容器,类似Spring中的Context,只不过使用范围是在当前线程内。
它可以简化同一个线程内多个方法直接参数的重复传递,隔离其他线程的干扰。
原理是把ThreadLocal变量以弱key的形式存放在java.lang.ThreadLocal.ThreadLocalMap中。
java.lang.ThreadLocal.ThreadLocalMap.Entry就是继承了WeakReference,把ThreadLocal作为一种弱引用存在于map中的key中,一旦ThreadLocal变量的引用被回收或者被置为null值的时候,JVM就会将数据中的key也回收掉并置为null值。
可以写个例子来证明上面的结论
ThreadLocal的存在是提供了一种线程内全局的上下文容器,类似Spring中的Context,只不过使用范围是在当前线程内。
它可以简化同一个线程内多个方法直接参数的重复传递,隔离其他线程的干扰。
原理是把ThreadLocal变量以弱key的形式存放在java.lang.ThreadLocal.ThreadLocalMap中。
java.lang.ThreadLocal.ThreadLocalMap.Entry就是继承了WeakReference,把ThreadLocal作为一种弱引用存在于map中的key中,一旦ThreadLocal变量的引用被回收或者被置为null值的时候,JVM就会将数据中的key也回收掉并置为null值。
可以写个例子来证明上面的结论
虚引用
概念:虚引用是使用PhantomReference创建的引用,虚引用也称为幽灵引用或者幻影引用,是所有引用类型中最弱的一个。一个对象是否有虚引用的存在,完全不会对其生命周期构成影响,也无法通过虚引用获得一个对象实例
特点:
GC随时回收,无需考虑任务情况
虚引用是最弱的引用
虚引用对对象而言是无感知的,对象有虚引用跟没有是完全一样的
虚引用不会影响对象的生命周期
虚引用可以用来做为对象是否存活的监控
GC随时回收,无需考虑任务情况
虚引用是最弱的引用
虚引用对对象而言是无感知的,对象有虚引用跟没有是完全一样的
虚引用不会影响对象的生命周期
虚引用可以用来做为对象是否存活的监控
场景:
使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。这个虚引用对于对象而言完全是无感知的,有没有完全一样,但是对于虚引用的使用者而言,就像是待观察的对象的把脉线,可以通过它来观察对象是否已经被回收,从而进行相应的处理。
事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。
使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。这个虚引用对于对象而言完全是无感知的,有没有完全一样,但是对于虚引用的使用者而言,就像是待观察的对象的把脉线,可以通过它来观察对象是否已经被回收,从而进行相应的处理。
事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。
JIT编译
前言
热点代码
概念:
当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”
反复解释执行肯定很慢,JVM 在运行程序的过程中不断优化,用JIT编译器编译那些热点代码,让他们不用每次都逐句解释执行
当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”
反复解释执行肯定很慢,JVM 在运行程序的过程中不断优化,用JIT编译器编译那些热点代码,让他们不用每次都逐句解释执行
热点代码的分类
被多次调用的方法:
一个方法被调用得多了,方法体内代码执行的次数自然就多,成为“热点代码”是理所当然的
一个方法被调用得多了,方法体内代码执行的次数自然就多,成为“热点代码”是理所当然的
被多次执行的循环体:
一个方法只被调用过一次或少量的几次,但是方法体内部存在循环次数较多的循环体,这样循环体的代码也被重复执行多次,因此这些代码也应该认为是“热点代码”
一个方法只被调用过一次或少量的几次,但是方法体内部存在循环次数较多的循环体,这样循环体的代码也被重复执行多次,因此这些代码也应该认为是“热点代码”
字节码:
Java 代码通过 javac 命令编译成字节码
字节码是不能直接运行的,需要经过 JVM 解释或编译成机器码才能运行
机器码和本地代码:可以直接识别运行的代码,也就是机器指令
java
Java为了实现“一次编译,处处运行”的特性,把编译的过程分成两部分,首先它会先由javac编译成通用的中间形式——字节码,然后再由解释器逐条将字节码解释为机器码来执行
概念:
为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler),简称 JIT 编译器
为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler),简称 JIT 编译器
编译器、解释器
编译器:把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快;
解释器:只在执行程序时,才一条一条的解释成机器语言给计算机来执行,所以运行速度是不如编译后的程序运行的快的;
1、必须指出的是,不管是解释执行,还是编译执行,最终执行的代码单元都是可直接在真实机器上运行的机器码,或称为本地代码
2、JIT是一种提高程序运行效率的方法。通常,程序有两种运行方式:静态编译与动态解释。静态编译的程序在执行前全部被翻译为机器码,而动态解释执行的则是一句一句边运行边翻译
2、JIT是一种提高程序运行效率的方法。通常,程序有两种运行方式:静态编译与动态解释。静态编译的程序在执行前全部被翻译为机器码,而动态解释执行的则是一句一句边运行边翻译
解决热点代码预先编译,不会对热点代码重新解释成机器代码执行,将编译的热点代码直接存储下来,每次执行的时候直接调用机器码
优化工具
java基础
反射
生命周期[不确定]
编译阶段
从源文件.java
到编译后.class
装配阶段
从.class文件加载JVM内存的结构体Class类型
实例化阶段
运行环境中根据Class类型进行实例化的对象
概念(文件.class >> JVM(class类型) >>实例 )
什么是类:类是一个模板,它描述一类对象的行为和状态(类是抽象的模板,所有的 ".class " 文件,加载到JVM中都被定义为Class类型)
JVM中对class文件的描述和定义的类型(类型 )
什么是对象:对象是java类的定义,有状态和行为(例如:User类、Patient类)
什么是实例:是承载数据的载体,对象的具体实例
具体抽象事物描述(张三)
class类
什么是反射
反射(Reflection)是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
通过反射机制,可以在运行时访问 Java 对象的属性,方法,构造方法等
反射的本质就是:在运行时,把 Java 类中的各种成分映射成一个个的 Java 对象
类型获取方式
Class.forName("类全路径")
对象.class
实例对象.getClass()
类的结构
构造器:Constructor
获取方式
修饰符为:public类型
获取所有的构造方法,无修饰符限制
实例化方式
实例化:无参数构造器
实例化:有参数构造器
实例化:class实例化对象
private 修饰的构造器,需要设置安全检查参数setAcessible(true)取消安全检查,才可以调用,否则将会抛出无权限调用
字段: Field
获取方式
修饰符为:public的字段
获取所有字段,无修饰符限制
设置方式
修饰符为public可直接调用方法进行设置内容
修饰符为private : 先设置安全检查 setAccessible(true),然后在进行设置内容
方法:Method
获取方式
修饰符为:public的方法
获取所有方法,无修饰符限制
调用方式
修饰符为private的方法 : 先设置安全检查 Method.setAccessible(true),然后在调用,否则会抛出IllegalAccessException
方法调用
其他结构:注解
实例化的方式(5种)
new 对象
Class.newInstance()
Constrator.newInstance()
实现接口Cloneable,调用clone方法
反序列化方式
反射核心接口和类(java.lang.reflect)
Member 接口:反映关于单个成员(字段或方法)或构造函数的标识信息
Field 类:提供一个类的域的信息以及访问类的域的接口
Method 类:提供一个类的方法的信息以及访问类的方法的接口
Constructor 类:提供一个类的构造函数的信息以及访问类的构造函数的接口
Array 类:该类提供动态地生成和访问 JAVA 数组的方法
Modifier 类:提供了 static 方法和常量,对类和成员访问修饰符进行解码
Proxy 类:提供动态地生成代理类和类实例的静态方法
应用场景:
开发通用框架 -
反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。
反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。
动态代理 -
在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了
在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了
注解 -
注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用
注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用
可扩展性功能 -
应用程序可以通过使用完全限定名称创建可扩展性对象实例来使用外部的用户定义类
(动态创建对象,实现外部扩展的功能)
应用程序可以通过使用完全限定名称创建可扩展性对象实例来使用外部的用户定义类
(动态创建对象,实现外部扩展的功能)
缺点
性能开销 -
由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中避免频繁调用反射动态解析
由于反射涉及动态解析的类型,因此无法执行某些 Java 虚拟机优化。因此,反射操作的性能要比非反射操作的性能要差,应该在性能敏感的应用程序中避免频繁调用反射动态解析
破坏封装性 -
反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题
反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题
内部曝光 -
由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为
由于反射允许代码执行在非反射代码中非法的操作,例如访问私有字段和方法,所以反射的使用可能会导致意想不到的副作用,这可能会导致代码功能失常并可能破坏可移植性。反射代码打破了抽象,因此可能会随着平台的升级而改变行为
面向对象的特征
封装
定义:隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别
目的:
增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,一特定的访问权限来使用类的成员。
把所有的属性私有化,对每个属性提供getter和setter方法,如果有一个带参的构造函数的话,那一定要写一个不带参的构造函数。在开发的时候经常要对已经编写的类进行测试,所以在有的时候还有重写toString方法,但这不是必须的。
主要特点
隐藏对象内部实现细节
增强安全性和简化编程
统一提供对外调用接口,标准化操作
抽象
概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
抽象方法的意义在于,规定了他的所有子类必须拥有一个这样子的方法,或者提供一个这样子的服务,但是实现这个方法的方式,会因为子类的不同的结构而有所不同。 之所以需要继承,是因为这个方法是这些子类的一个共同属性。或者说,父类要通过这些抽象的方法,提供某种特定的服务,但是实现方式在子类中有所不同,所以在父类中写了抽象方法强制子类实现,保证能够提供这种服务
抽象类是对一种事物的抽象,即对类抽象。是对通用、基础封装
主要特点
是为了抽取公共行为的定义
是为了规范化、标准化的定义
是为了抽取公共行为的标准方法
抽象类更多的是对通用的、基础的方法封装,让子类复用,避免在子类开发重复的代码
抽象类是对一种事物的抽象,即对类抽象
与接口的区别
接口是对行为的抽象
解耦
抽象类更多的是对通用的、基础的方法封装,让子类复用,避免在子类开发重复的代码
通用
抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象
继承
概念
继承为了扩展和复用父类的行为和属性
提高代码的复用性
让类与类之间产生了关系,是多态的前提
特点
单继承,不支持多继承
super: 当前子类对父类的引用
this:对象的引用(谁调用就代表谁)
多态
概念:
对象在不同时刻表现出来的不同状态
父类引用指向子类对象
前提
要有继承
要有方法的重写
实现关系
代理
概念
功能增强:代理可以在不破坏原有的功能基础功能之上,进行功能的扩展即增强
访问控制:可以在访问源功能的时候进行限流或控制访问
动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,很多场景都是利用类似机制做到的,比如用来包装 RPC 调用、面向切面的编程(AOP)
实现动态代理的方式很多,比如 JDK 自身提供的动态代理,主要利用反射机制实现
实现代理的方式
JDK代理、ASM、cglib(基于 ASM)、Javassist
静态代理
代码实现
优点:
目标与代理类接口层级清晰简单
结构简单
缺点:
1、目标接口发生变更会影响实现类与代理类的变更
2、目标接口增加代理类也会随之增加,代理类随着业务增加变得越来越臃肿
3、代理目标类对象,增强代码冗余代码多,无法很好的利用
动态代理
特点
目标接口发生变化不会影响代理类的变更
动态代理可以在不改变目标业务或方法的基础之上,动态增加自己的业务功能
动态代理是通过java的反射机制创建的代理类
JDK代理
1、基于接口创建动态代理对象,可以只有实现接口没有实现类(例如:mybatis),jdk 代理也可以生成代理对象
**** JDK代理是通过java 反射实现接口生成字节码的方式,加载ClassLoad中实例化代理对象
2、java.lang.reflect.Method类invoke(Object obj, Object... args)
1、通过java反射Class获取Method类的对象,获取到所有实现该方法的类调用权限
2、obj : 目标对象
3、args:方法参数值
3、InvocationHandler 接口 invoke(Object proxy, Method method, Object[] args)
代理增强接口,实现业务功能增强专用接口
proxy:代理目标对象
Method:目标类中的方法
args:目标类中的参数
4、Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
创建代理对象,将目标对象重新封装创建代理对象
loader:目标对象的ClassLoader,可以通过反射获取ClassLoader
inerfaces:目标对象实现的接口,可以通过反射获取
InvocationHandler:代理类所需要实现的功能增强
通过代理类调用目标对象方法:proxy.method顺序
1、先调用InvocationHandler.invoke方法
2、在invoke方法中进行调用目标对象的目标方法
之前:可以在调用目标对象的方法之前对功能进行增强
调用目标方法:Method.invoke(target,args)
之后:调用完成目标方法后,可以对其进行增强
代理逻辑
生成字节码文件配置:System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
字节码
CGLIB代理
Cglib:即可以代理类、也可以强制代理接口(代理接口的时候需要自己实现MethondInterceptor.intercept放中的逻辑)
Cglib 代理不能代理final类型的方法和类,因为在 java 中final被标识的方法及类不能被覆盖和重写
Cglib 代理是对字节码的操作,会生成新代理字节码(ASM插件生成字节码)
类: 字节码会继承所要代理的类,覆盖所有实现的方法,重新生成新的代理类
接口:字节码会实现所要代理的接口重写调用的方法,重新生成新的代理类
MethodInterceptor接口intercept
代理增强接口:实现业务功能增强专用接口
目标类调用方式:methodProxy.invokeSuper(targetProxy, objects);
代码
Enhancer字节码增强类
setSuperclass 方法 :用于设置增强字节码的类类型(类.class,接口.class)
setCallback方法:设置增强类的回调方法实现(MethodInterceptor)
代码
生成字节码文件配置:System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/");
字节码内容:
并发编程
线程:Thread
进程
一个进程可以包含一个或多个线程,但至少会有一个线程
线程是计算机最小的执行单位
创建方式3种
继承Thread类重写run方法
实现Runnable 接口,实现run方法,无返回值
实现Callable接口,实现call方法,有返回值
线程的状态
1、New:新创建的线程,尚未执行
2、Runnable:运行中的线程,正在执行run()方法
3、Blocked:运行中的线程,因为某些操作被阻塞而挂起
4、Waiting:运行中的线程,因为某些操作在等待中
5、Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待
6、Terminated:线程已终止,因为run()方法执行完毕
状态之间的转换关系
关系图
线程主要方法
activeCount():统计当前线程所在的线程组中活动的线程数量
join():强制执行该线程,直到当前线程执行完毕
join(ms):强制执行该线程指定的毫秒范围内,如果没有执行完毕则让出执行权,由其他线程执行
getId():获取当前线程的唯一ID ,long类型
getState():返回当前线程的状态
getName():返回线程名称
getThreadGroup():返回当前线程所在的组对象
yield:让出可执行的时间片,让其他线程执行
interrupt:中断线程
interrupted:清除线程的中断状态
子主题
线程优先级:Thread.setPriority(int n)
范围:1~10, 默认值5
优先级高的线程被操作系统调度的优先级较高,
操作系统对高优先级线程可能调度更频繁,
但我们决不能通过设置优先级来确保高优先级的线程一定会先执行
操作系统对高优先级线程可能调度更频繁,
但我们决不能通过设置优先级来确保高优先级的线程一定会先执行
守护线程
守护线程是为其他线程所服务的,不能持有任何需要处理的资源
JVM退出时,不必关心守护线程是否已结束
创建守护线程:在现场启动之前设置 setDaemon(true) 即可设置为守护线程
isDaemon 判断是否为守护线程
线程终止原因
线程正常终止:run()方法执行到return语句返回;
线程意外终止:run()方法因为未捕获的异常导致线程终止
对某个线程的Thread实例调用stop()方法强制终止(强烈不推荐使用)
interrupt 方法表示中断正在运行的现场,
volatile
概念 : 内存可见性(Memory Visibility):所有线程都能看到共享内存的最新状态
主内存
访问:用关键字 volatile 修饰的变量每次线程访问该变量都会访问主内存值,不会读取本地副本
变更:volatile 修饰的是变量,在每次更改其值的内容,都会立刻同步到主内存中
副本内存
访问 : 非 volatile 关键字修饰的变量,线程访问变量首先会读取本地副本,不会立刻读取主内存的值
变更 :非 volatile 关键字修饰的变量,在更改其变量的内容,不会立刻同步到主内存中,所以其他线程读取不到最新的变更内容
变量存放图
图片
子主题
子主题
sychronized
修饰作用域
修饰实例方法
作用于:实例对象锁,对象之间互相独立锁
实现原理:ACC_SYNCHRONIZED标示符实现
修饰静态方法
作用于:class类锁,多个实例对象相互互斥
暂无
修饰代码块
作用于:取决于参数传递对象或class类锁
实现原理:采用monitor监视器
实现原理
锁状态:
无锁状态
偏向锁
单线程操作,获取锁标记
多线程竞争锁,升级为轻量锁
关闭偏量锁JVM方式:UseBiasedLocking = false
轻量锁
多线程竞争锁,采用CAS方式实现
CAS(compant and swap)
概念:比较替换
java.util.concurrent.atomic中的很多类,如(AtomicInteger AtomicBoolean AtomicLong等)都使用了CAS
缺点
循环时间长开销很大:我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销
只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性
ABA问题:java提供了AtomicStampedReference类解决了
Unsafe
自旋锁无法获取锁状态,升级重量级锁
重量锁
对象头标记指针地址
采用monitor方式进行实现
monitor->OjbectMonitor实现
进入阻塞:entryList
运行状态:owner
等待状态:waitSet
自旋锁
对象存储结构
对象头(Header)
对象hashCode
锁状态
线程持有的锁
对象分代年龄
指针地址
实例数据(Instance Data)
对齐填充(Padding)
特点:保证内存可见性、操作原子性
volatile
概念
共享变量的可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
禁止指令重排序
主要解决的问题:
可见性
当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,会去主内存中读取最新新值
禁止指令重排序(有序性)
概念:保证程序在执行期间不会对指令进行优化操作,
阶段
编译器优化的重排序
编译源代码时,编译器依据对上下文的分析,对指令进行重排序,以之更适合于CPU的并行执行
运行阶段
CPU在执行过程中,动态分析依赖部件的效能,对指令做重排序优化
编译代码会生成lock指令
加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令
实现分析:
JVM 底层采用“内存屏障”来实现 volatile
LoadLoad:保证load1的读取操作在load2及后续读取操作之前执行
StoreStore:在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
LoadStore:在stroe2及其后的写操作执行前,保证load1的读操作已读取结束
StoreLoad:保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行
CAS(Compare and Swap)
概念:Java 主要利用 Unsafe 这个类提供的 CAS 操作。Unsafe 的 CAS 依赖的是 JVM 针对不同的操作系统实现的硬件指令 Atomic::cmpxchg。Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 CPU 提供的 lock 信号保证其原子性
特点:CAS 比锁性能更高。因为 CAS 是一种非阻塞算法,所以其避免了线程阻塞和唤醒的等待时间
缺点:
ABA 问题
描述:如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过
解决办法:AtomicStampedRefrence类
循环时间长开销大
描述:自旋 CAS (不断尝试,直到成功为止)如果长时间不成功,会给 CPU 带来非常大的执行开销
解决方法:自旋锁
只能保证一个共享变量的原子性
问题描述:当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁
解决办法:从 Java 1.5 开始 JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作。
ThreadLocal
是什么
ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
ThreadLocal就是为每个线程提供给一个属于自己的局部变量,不受其他线程影响
原理
内部实现
内部类:ThreadLocal- > ThreadLocalMap - > Entity extends WeakReference<ThreadLocal<?>>
设计思想
1、ThreadLocal仅仅是个变量访问的入口
2、每一个Thread对象都有一个ThreadLocalMap对象,这个ThreadLocalMap持有对象的引用
3、hreadLocalMap以当前的threadlocal对象为key,以真正的存储对象为value。get时通过threadlocal实例就可以找到绑定在当前线程上的对象
目的:
一是可以保证当前线程结束时相关对象能尽快被回收;
二是ThreadLocalMap中的元素会大大减少,我们都知道map过大更容易造成哈希冲突而导致性能变差
内存溢出点
原因:外部强引用:ThreadLocal local = new ThreadLocal() 不会被回收,内部ThreadLocalMap存在Entity应用关系:Key为弱引用,value强引用,弱引用随时可能被回收,一旦Key被回收,value值一直会存在,不会被回收,所以当线程越来越多,会引发内存溢出
处理结果:线程结束后,一定要进行手动remove方法,清除当前线程应用的局部变量ThreadLocal,被垃圾回收器回收
概念
可重入锁
子主题
注解
注解的形式
注解是以 @ 字符开始的修饰符
如果注解没有属性,则称为标记注解。如:@Override
什么是注解
从本质上来说,注解是一种标签,其实质上可以视为一种特殊的注释,如果没有解析它的代码,它并不比普通注释强
解析一个注解往往有两种形式:
1、编译期直接的扫描 - 编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。这种情况只适用于 JDK 内置的注解类。
2、运行期的反射 - 如果要自定义注解,Java 编译器无法识别并处理这个注解,它只能根据该注解的作用范围来选择是否编译进字节码文件。如果要处理注解,必须利用反射技术,识别该注解以及它所携带的信息,然后做相应的处理。
1、编译期直接的扫描 - 编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。这种情况只适用于 JDK 内置的注解类。
2、运行期的反射 - 如果要自定义注解,Java 编译器无法识别并处理这个注解,它只能根据该注解的作用范围来选择是否编译进字节码文件。如果要处理注解,必须利用反射技术,识别该注解以及它所携带的信息,然后做相应的处理。
Annotation接口
概念:是一个接口,程序可以通过反射来获取指定程序元素的注解对象,然后通过注解对象来获取注解里面的元数据。
子主题
元注解
概念:元注解是可以注解到注解上的注解,或者说元注解是一种基本注解,但是它能够应用到其它的注解上面
@Retention
概念:
作用于注解,表示注解的生命周期
生命周期
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们
@Documented
概念
作用是能够将注解中的元素包含到 Javadoc
@Target
概念
指定注解运用的范围
范围
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
@Inherited
概念
Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解
@Repeatable
概念
注解是用于声明其它类型注解的元注解,
来表示这个声明的注解是可重复的。
@Repeatable的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解
来表示这个声明的注解是可重复的。
@Repeatable的值是另一个注解,其可以通过这个另一个注解的值来包含这个可重复的注解
内置注解
@Override
概念:
标记式注解,仅被编译器可知,编译器在对 java 文件进行编译成字节码的过程中,一旦检测到某个方法上被修饰了该注解,就会去匹对父类中是否具有一个同样方法签名的函数,如果不是,自然不能通过编译
用于表明被修饰方法覆写了父类的方法。
子主题
@Deprecated
概念:
标记式注解,用于标明被修饰的类或类成员、类方法已经废弃、过时,不建议使用
JDK 已经不再推荐使用当前的方法或者类了,建议你使用某个替代者
@SuppressWarnings
概念
用来压制 java 的警告
用于关闭对类、方法、成员编译时产生的特定警告
参数说明
deprecation - 使用了不赞成使用的类或方法时的警告
unchecked - 执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
fallthrough - 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path - 在类路径、源文件路径等中有不存在的路径时的警告;
serial - 当在可序列化的类上缺少 serialVersionUID 定义时的警告;
finally - 任何 finally 子句不能正常完成时的警告
all - 所有的警告。
@FunctionalInterface
概念
Java 8 开始支持,标识一个匿名函数或函数式接口。
用于指示被修饰的接口是函数式接口
函数式接口的特点:
接口有且只能有个一个抽象方法(抽象方法只有方法定义,没有方法体)。
不能在接口中覆写 Object 类中的 public 方法(写了编译器也会报错)。
允许有 default 实现方法。
接口有且只能有个一个抽象方法(抽象方法只有方法定义,没有方法体)。
不能在接口中覆写 Object 类中的 public 方法(写了编译器也会报错)。
允许有 default 实现方法。
@SafeVarargs
概念
告诉编译器,在可变长参数中的泛型是类型安全的。可变长参数是使用数组存储的,而数组和泛型不能很好的混合使用。
JDK7
简单的说,数组元素的数据类型在编译和运行时都是确定的,而泛型的数据类型只有在运行时才能确定下来。因此,当把一个泛型存储到数组中时,编译器在编译阶段无法确认数据类型是否匹配,因此会给出警告信息;即如果泛型的真实数据类型无法和参数数组的类型匹配,会导致 ClassCastException 异常。
@SafeVarargs 注解使用范围:
@SafeVarargs 注解可以用于构造方法。
@SafeVarargs 注解可以用于 static 或 final 方法。
简单的说,数组元素的数据类型在编译和运行时都是确定的,而泛型的数据类型只有在运行时才能确定下来。因此,当把一个泛型存储到数组中时,编译器在编译阶段无法确认数据类型是否匹配,因此会给出警告信息;即如果泛型的真实数据类型无法和参数数组的类型匹配,会导致 ClassCastException 异常。
@SafeVarargs 注解使用范围:
@SafeVarargs 注解可以用于构造方法。
@SafeVarargs 注解可以用于 static 或 final 方法。
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。
自定义注解
注解的定义
public @interface 注解名 {定义体}
注解属性
[访问级别修饰符] [数据类型] 名称() default 默认值;
在注解中,我们定义属性时,属性名后面需要加 ()
注解属性只能使用 public 或默认访问级别(即不指定访问级别修饰符)修饰
注解属性的数据类型有限制要求
所有基本数据类型(byte、char、short、int、long、float、double、boolean)
String 类型
Class 类
enum 类型
Annotation 类型
以上所有类型的数组
String 类型
Class 类
enum 类型
Annotation 类型
以上所有类型的数组
注解属性必须有确定的值,建议指定默认值。注解属性只能通过指定默认值或使用注解时指定属性值,相较之下,指定默认值的方式更为可靠。注解属性如果是引用类型,不可以为 null。这个约束使得注解处理器很难判断注解属性是默认值,或是使用注解时所指定的属性值。为此,我们设置默认值时,一般会定义一些特殊的值,例如空字符串或者负数
操作注解方式
所有的Class/Field/Method对象都可以调用上述方法
getAnnotations(): 获取所有的注解,包括自己声明的以及继承的
getAnnotation(Class< A > annotationClass) 获取指定的注解,该注解可以是自己声明的,也可以是继承的
getDeclaredAnnotations() 获取自己声明的注解
泛型
概念:java 泛型是java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法
特点:
编译时的强类型检查
消除强制类型转换带来的问题
泛型编程可以实现通用算法
分类
泛型类(class className<T1, T2, ..., Tn>)
概念:
一般将泛型中的类名称为原型,而将 <> 指定的参数称为类型参数
类结构是面向对象中最基本的元素,如果我们的类需要有很好的扩展性,那么我们可以将其设置成泛型的
className:原型
T1,T2,...:类型参数
泛型接口(public interface Content<T> {T text();})
实现接口的子类明确声明泛型类型
实现接口的子类不明确声明泛型类型
泛型方法(public <T> T func(T obj) {})
概念:
泛型方法是引入其自己的类型参数的方法。泛型方法可以是普通方法、静态方法以及构造方法。
泛型方法既可以存在于泛型类中,也可以存在于普通的类中。如果使用泛型方法可以解决问题,那么应该尽量使用泛型方法
是否拥有泛型方法,与其所在的类是否是泛型没有关系
<T>:
泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回类型之前。对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前。类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际类型参数的占位符
类型擦除
概念
Java 语言引入泛型是为了在编译时提供更严格的类型检查,并支持泛型编程。不同于 C++ 的模板机制,Java 泛型是使用类型擦除来实现的,使用泛型时,任何具体的类型信息都被擦除了。
编译期间擦除:
把泛型中的所有类型参数替换为 Object,如果指定类型边界,则使用类型边界来替换。因此,生成的字节码仅包含普通的类,接口和方法
擦除出现的类型声明,即去掉 <> 的内容。比如 T get() 方法声明就变成了 Object get() ;List<String> 就变成了 List。如有必要,插入类型转换以保持类型安全
生成桥接方法以保留扩展泛型类型中的多态性。类型擦除确保不为参数化类型创建新类;因此,泛型不会产生运行时开销
泛型的继承
泛型不能用于显式地引用运行时类型的操作之中,例如:转型、instanceof 操作和 new 表达式。因为所有关于参数的类型信息都丢失了。当你在编写泛型代码时,必须时刻提醒自己,你只是看起来好像拥有有关参数的类型信息而已。
正是由于泛型时基于类型擦除实现的,所以,泛型类型无法向上转型。
向上转型是指用子类实例去初始化父类,这是面向对象中多态的重要表现。
正是由于泛型时基于类型擦除实现的,所以,泛型类型无法向上转型。
向上转型是指用子类实例去初始化父类,这是面向对象中多态的重要表现。
Integer 继承了 Object;ArrayList 继承了 List;但是 List<Interger> 却并非继承了 List<Object>。
这是因为,泛型类并没有自己独有的 Class 类对象。比如:并不存在 List<Object>.class 或是 List<Interger>.class,Java 编译器会将二者都视为 List.class。
这是因为,泛型类并没有自己独有的 Class 类对象。比如:并不存在 List<Object>.class 或是 List<Interger>.class,Java 编译器会将二者都视为 List.class。
类型边界(<T extends XXX>)
概念:可以对泛型的类型参数设置限制条件
类型通配符
概念:类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是 List<String> ,List<Integer> 等所有 List<具体类型实参> 的父类。
上界通配符(<? extends Number>)
可以使用**上界通配符**来缩小类型参数的类型范围。
?表示任意参数类型,但是参数类型必须是Number类型的子类
下界通配符(<? super Number>)
**下界通配符**将未知类型限制为该类型的特定类型或超类类型。
注意:上界通配符和下界通配符不能同时使用。
?表示任意参数类型,参数类型必须是Number类型的子类、本类、多层级继承类都可以
无界通配符<?>
概念:
无界通配符有两种应用场景:
可以使用 Object 类中提供的功能来实现的方法。
使用不依赖于类型参数的泛型类中的方法。
无界通配符有两种应用场景:
可以使用 Object 类中提供的功能来实现的方法。
使用不依赖于类型参数的泛型类中的方法。
类似于任意类型<Object>
通配符和向上转型
概念:
泛型不能向上转型。但是,我们可以通过使用通配符来向上转型。
泛型不能向上转型。但是,我们可以通过使用通配符来向上转型。
泛型的约束
泛型类型的类型参数不能是值类型
Pair<int, char> p = new Pair<>(8, 'a'); // 编译错误
不能创建类型参数的实例
public static <E> void append(List<E> list) {
E elem = new E(); // 编译错误
list.add(elem);
}
E elem = new E(); // 编译错误
list.add(elem);
}
不能声明类型为类型参数的静态成员
public class MobileDevice<T> {
private static T os; // error
}
private static T os; // error
}
类型参数不能使用类型转换或 instanceof
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) { // 编译错误
}
}
if (list instanceof ArrayList<Integer>) { // 编译错误
}
}
List<Integer> li = new ArrayList<>();
List<Number> ln = (List<Number>) li; // 编译错误
List<Number> ln = (List<Number>) li; // 编译错误
不能创建类型参数的数组
List<Integer>[] arrayOfLists = new List<Integer>[2]; // 编译错误
不能创建、catch 或 throw 参数化类型对象
// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ } // 编译错误
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // 编译错误
class MathException<T> extends Exception { /* ... */ } // 编译错误
// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // 编译错误
public static <T extends Exception, J> void execute(List<J> jobs) {
try {
for (J job : jobs)
// ...
} catch (T e) { // compile-time error
// ...
}
}
try {
for (J job : jobs)
// ...
} catch (T e) { // compile-time error
// ...
}
}
仅仅是泛型类相同,而类型参数不同的方法不能重载
public class Example {
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { } // 编译错误
}
public void print(Set<String> strSet) { }
public void print(Set<Integer> intSet) { } // 编译错误
}
其他:
BIO\NIO\AIO
IO(阻塞/非阻塞/非阻塞复用/信号驱动/异步IO)模型
阻塞式 I/O
应用进程被阻塞,直到数据复制到应用进程缓冲区中才返回
流程图
非阻塞式 I/O
应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成,这种方式称为轮询(polling)。 由于 CPU 要处理更多的系统调用,因此这种模型是比较低效的
流程图
I/O 复用
使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读,这一过程会被阻塞,当某一个套接字可读时返回。之后再使用 recvfrom 把数据从内核复制到进程中。 它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O
流程图
信号驱动 I/O
应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。 相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高
流程图
异步 I/O
进行 aio_read 系统调用会立即返回,应用进程继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。 异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。
流程图
五大 I/O 模型比较
前四种 I/O 模型的主要区别在于第一个阶段,而第二个阶段是一样的: 将数据从内核复制到应用进程过程中,应用进程会被阻塞
流程图
I/O 模型比较
同步 I/O 与异步 I/O
同步 I/O: 应用进程在调用 recvfrom 操作时会阻塞
异步 I/O: 不会阻塞
阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,虽然非阻塞式 I/O 和信号驱动 I/O 在等待数据阶段不会阻塞,但是在之后的将数据从内核复制到应用进程这个操作会阻塞
BIO
概念:传统IO,同步阻塞IO,数据读写方式:面向流
关键概念
包目录:java.io
阻塞:单线程模式,线程一旦阻塞,不能再做其他任务使用
客户端读写:每一个客户端独立分配一个线程处理,过多的客户端会导致线程数量增加
创建流程
服务端
客户端
特点
优点
专门用于处理有限的客户端连接
结构简单,易懂
缺点
不适用于大量客户端连接,线程数量有限,开销大
多线程切换消耗CPU资源
改善方式:采用线程池
NIO
概念:基于IO多路复用技术的“非阻塞式IO”。简单来说,内核将可读可写事件通知应用,由应用主动发起读写操作
数据的读写方式面向:缓存区
数据的读写方式面向:缓存区
关键概念
Java 1.4 java.nio之后引入
Channel
通道
既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的
通道可以异步地读写
通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入
Buffer
缓存区
开辟了一块内存空间,可以读写数据
all
属性
capacity:总空间容量
position:
写:指针指向最新写入的位置+1
读:
limit
写:最大容量 为capacity
读:实际数据数量的位置
selector
选择器
一个线程可处理多个客户端的连接通道
基于事件驱动方式进行处理模式
详细说明
创建流程
服务端
客户端
特点
面向缓冲区
采用IO多路复用模式处理多客户端数据
事件驱动
基于事件驱动:等发生了各种事件,系统可以通知我,我再去处理
子主题
AIO
概念:非阻塞异步IO模型。简单来说,内核将读完成事件通知应用,读操作由内核完成,应用只需操作数据即可;应用做异步写操作时立即返回,内核会进行写操作排队并执行写操作
关键概念
Java 1.7 之后引入的一个包
它提供了异步和非阻塞的IO操作模式,所以人们称之为AIO(异步IO),异步IO是基于事件和回调机制的,即应用操作后直接返回,不会被阻塞在那里. 当后台处理完成后,操作系统会通知相应的线程进行后续操作
设计模型
reactor模型
概念:Reactor模型是基于事件驱动的线程模型,可以分为Reactor单线程模型、Reactor多线程模型、主从Reactor多线程模型,通常基于在I/O多路复用实现
关键词
Dispatcher 负责事件分发
Acceptor 负责客户端建立连接
Handler 负责客户端数据的读写
原理
Reactor 单线程模型
Reactor多线程模型
Reactor 主从多线程模型
proactor:基于AIO技术,读完成时通知应用,写操作应用通知内核
Cookie与Session区别
Cookie
存储在浏览器端
以明文的方式存储,存在很大风险,可能被篡改
有:有效期
浏览器禁用Cookie,则Cookie不能使用
支持跨域名
Session
存储在服务器端
并发访问的用户非常多,会产生很多的 Session,消耗大量的内存
有效期
取决于Cookie中的JESSIONID 是否过期
正常浏览器关闭,session失效
不支持跨域
Token与JWT区别
Token
无状态
支持跨域
需要验证Token,查库或查缓存验证
无法携带用户信息,需要进行二次查询才可以
JWT
无状态
支持跨域
支持自动校验解码,不需要查库或缓存校验
支持携带用户信息,进行加密返回
JWT(JSON Web Token)
基本概念
JWT(JSON Web Token) 是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息
无状态
支持跨域
支持自动校验解码
支持携带数据,暂无限制,尽可能不要超过HTTP请求携带的8K大小
组成结构
JWT由三部分组成,它们之间用圆点(.)连接。
头部:Header
申明JWT类型: type,默认jwt
申明JWT算法类型:alg,通常直接使用 HMAC SHA256
数据载体:Payload
标准注册声明(registered claims)
子主题
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
公共的声明(public claims)
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息
客户端可解密
私有的声明(private claims)
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
签证信息:Signature
头部且Base64加密后:header (base64后的)
数据载体且Base64加密后:payload (base64后的)
密文信息:secret
代码示例
资源网址
https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
组件技术
Jsoup解析Xml
概念:专注于解析标准Html工具,可以基于节点或样式等进行解析,标准的XML文档也可以采用此工具
Document对象创建
doc = Jsoup.parse(html);
doc = Jsoup.parseBodyFragment(html);
doc = Jsoup.connect("http://example.com/").get();
doc = Jsoup.parse(input, "UTF-8", "http://example.com/");
doc解析数据
方法:method
Element : 获取节点
Element : getElementById(String id)
获取父元素:parent()
获取子元素:children()
获取子元素中的第几个子元素,返回单个子元素,数组超出会报异常:child(int index)
Elements : 获取节点列表
标签名 : getElementsByTag(String tag)
样式名 : getElementsByClass(String className)
属性名 : getElementsByAttribute(String key)
相邻节点获取 :
同级元素:siblingElements()
第一个同级元素 : firstElementSibling()
最后一个同级元素:lastElementSibling();
下一个同级元素 : nextElementSibling()
上一个同级元素 :previousElementSibling()
Element : 获取节点数据
attr
属性的值 : attr(String key)
设置属性值 : attr(String key, String value)
所有属性 : attributes()
样式值 :className()
样式值名称 :classNames()
ID属性值 :id()
获取所有匹配子元素及以下所有文本值 : text()
设置文本值 : text(String value)
html()获取元素内HTMLhtml(String value)设置元素内的HTML内容
outerHtml()获取元素外HTML内容
作用于script、style、css内部数据:data()
获取标签对象及标签名称 :tag() and tagName()
选择器:Selector(规则)
单规则
标签名称 : tag
命名空间标签查找 *|name、div|name
ID标识符 #id
属性名 [class]
属性名及属性值 [class=nb]
属性名前缀查找 [^data-]
属性以什么结尾的元素"$="[data-$=xx]
属性包含的元素“*=”[data-*=xx]
样式类查找
总结
组合规则
多元化查询
查询div标签id等于name的元素:div#name
查询div下所有li标签元素: div li
查询div标签包含data属性的所有元素 :div[^data]
多标签组合,可以任意发挥
多标签查找(tagName tagName)
大于号查询(div>ul>li)
大于号所有子元素查找(div>*)
加号查询(div+span)
波浪线查询(div~span)
逗号查询(#id,div,[name=a])
筛选器
同级元素索引号查询元素:小于(:lt(n))、大于(:gt(n))、等于:eq(1)
包含指定元素的结果查找::has(seletor)
不包含指定元素的结果查找::not(selector)
其他筛选器,可在真实应用场景中研究
占位符解析
PropertyPlaceholderHelper解析
String.format格式化
转换符
补充
StringSubstitutor解析
pom引入
默认占位符符号 "${" 开头,“}”结尾
使用方式
系统属性
变量占位符
指定占位符号
人脸识别
地址&下载位置:https://ai.arcsoft.com.cn/
下载
子主题
tomcat
常见问题
闪退查看错误日志在控制台
1、打开 tomcat/bin/startup.bat
2、找到最后一行: call "%EXECUTABLE%" start %CMD_LINE_ARGS%
3、更改 start 为 run
4、将错误信息打印到当前cmd命令窗口,退出任务为tomcat任务,保留错误日志
命令窗口 更改名称
1、打开 tomcat/bin/catailina.bat
2、找到 :if "%TITLE%" == "" set TITLE=Tomcat
3、将tomcat 名称更改为你需要的名称即可
命令窗口 乱码
1、打开 tomcat/conf/logging.properties
2、找到显示控制台日志编码位置进行更改 : java.util.logging.ConsoleHandler.encoding = UTF-8
3、将 UTF-8 或 其他编码更改为 GBK 或其他编码进行尝试,直到命令窗口乱码正常
框架
spring
spel
pom包
核心类、接口解释
接口:ExpressionParser
类:SpelExpressionParser
接口 :EvaluationContext
类 : StandardEvaluationContext
应用示例
字符串
字符串显示
字符串拼接
字符串长度计算
字符串长度判断
字符串必须用上单引号标记,否则认为是一个动态变量
对象变量
动态变量上下文 : StandardEvaluationContext
获取对象变量内容
对象值动态判断
定义变量及应用,用#应用EvaluationContext定义的变量
多数据源
重写切换路由器抽象类:AbstractRoutingDataSource
存储线程安全的数据源关系
如何切换数据源
切换数据源采用AOP技术进行动态切换
概念介绍
IOC(Inverse of Control):控制反转
创建对象的权利由spring框架完成
DI(Dependency Injection):依赖注入
在Spring框架负责创建Bean对象时,将对象依赖关系动态的注入到Bean组件中
IOC容器
承载Bean对象的数据结构的Map
AOP(Aspect Oriented Programming):面向切面编程
在不修改目标对象的源代码情况下,增强IoC容器中Bean的功能
ApplicationContext 接口实现
ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件
FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建Spring容器。它用来读取注解
创建IOC容器
java
手动创建:ApplicationContext context=new ClassPathXmlApplicationContext(xml路径);
子主题
web
1、web.xml中配置ContextLoaderListener接口,并配置ContextConfigLocation参数
2、web容器启动之后加载web.xml,此时加载ContextLoaderListener监听器(实现了ServletContextListener接口
3、调用链:ContextLoaderListener->initWebApplicationContext->createWebApplicationContext->referesh()初始化容器
验证组件(Validation)
概念
Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现。hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等。Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验。接下来,我们以spring-boot项目为例,介绍Spring Validation的使用
JavaAPI JSR303 -> Hibernate Validation 实现 -> Spring Validation 二次封装
请求方式
POST
requestBody 参数传递
抛出 MethodArgumentNotValidException 异常 400 Bad Request
GET
requestParam/PathVariable 参数传递
抛出 ConstraintViolationException 异常
统一处理异常
抛出MethodArgumentNotValidException或者ConstraintViolationException 针对异常统一进行处理
代码实现
配置验证模式
手动验证
自定义验证注解
定义注解
解析注解类
@Validated
作用于类,类级别验证器
子主题
@Valid
作用于 方法参数和字段
子主题
优秀文章
https://blog.csdn.net/f641385712/article/details/97270786
子主题
验证创建Bean进入容器是否合法:BeanValidationPostProcessor
文章链接
https://blog.csdn.net/f641385712/article/details/97270786
创建bean之前拦截
创建Bean之后拦截
缓存(cache)
官方文档
https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#cache
子主题
底层实现接口
缓存接口:org.springframework.cache.Cache
缓存管理器:org.springframework.cache.CacheManager
缓存Key生成接口:
自定义生成器:org.springframework.cache.interceptor.KeyGenerator
默认生成器:org.springframework.cache.interceptor.DefaultKeyGenerator
基于声明性注解的缓存
添加:@Cacheable
清除:@CacheEvict
更新:@CachePut
组合:@Caching支持三种组合
Cacheable
CachePut
CacheEvict
配置类:@CacheConfig 表示整个类启用缓存
开启缓存:@EnableCaching
子主题
spring Mvc
处理程序方法参数解析器 HandlerMethodArgumentResolver
讲解文章
https://www.jianshu.com/p/f4653fe8c935
子主题
将请求提交的数据化转换为方法的对象,经过解析器处理处理转化
实例1:创建实例过程示例
实例2:
子主题
springboot
配置参数说明:https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties
常见问题
数据源:未配置数据源启动服务报错
数字类型精度丢失
状态码错误
400错误
方式1:异常接收
方式2:springboot 配置 -> 请求参数或请求路径出现特殊服务符号处理方式
方式3:tomcat 部署war配置
404错误
设置未找到的资源抛出异常:spring.mvc.throw-exception-if-no-handler-found=true
设置不添加默认映射:
spring.resources.add-mappings=false
spring.web.resources.add-mappings=false
处理异常:@ControllerAdvice
获取所有URL地址列表
启动、停止项目方式
命令窗口:bat
(win+R) cmd 进入命令窗口
(title 项目名称)设置命令窗口标题
(java -jar 项目名称) 启动项目
(直接关闭命令窗口即)关闭项目
后台命令运行:java -jar xxx.jar
(javaw.exe,jre/xxx.exe,jdk/xxx.exe) 复制更改为项目名称
(xxx.exe -jar xxx.jar) 启动项目
(linux = skill -9 xxx.exe , win=taskkill -f -t -im xxx.exe)关闭项目
静态资源
默认静态资源位置
classpath:/META-INF/resources/, classpath:/resources/, classpath:/static/, classpath:/public/
未找到处理资源
未找到访问的资源,是否抛出异常,默认Flase : spring.mvc.throw-exception-if-no-handler-found=false(默认)
是否启用静态资源映射
加载配置顺序
当前项目目录下的一个/config子目录:file:/config/
当前项目目录:file:/
项目的resources即一个classpath下的/config包:classpath:/config/
项目的resources即classpath根路径(root):classpath:/
如果在不同的目录中存在多个配置文件,它的读取顺序是:
1、config/application.properties(项目根目录中config目录下)
2、config/application.yml
3、application.properties(项目根目录下)
4、application.yml
5、resources/config/application.properties(项目resources目录中config目录下)
6、resources/config/application.yml
7、resources/application.properties(项目的resources目录下)
8、resources/application.yml
总结:
存在相同的配置内容,高优先级的内容会覆盖低优先级的内容
存在不同的内容的时候,高优先级和低优先级的配置内容取并集
1、config/application.properties(项目根目录中config目录下)
2、config/application.yml
3、application.properties(项目根目录下)
4、application.yml
5、resources/config/application.properties(项目resources目录中config目录下)
6、resources/config/application.yml
7、resources/application.properties(项目的resources目录下)
8、resources/application.yml
总结:
存在相同的配置内容,高优先级的内容会覆盖低优先级的内容
存在不同的内容的时候,高优先级和低优先级的配置内容取并集
加载文件顺序
bootstrap.yml (.properties) > application.yml (.properties) > application-dev(prod).yml(.properties)
异常处理
描述文章地址:https://reflectoring.io/spring-boot-exception-handling/
注解使用
@ControllerAdvice
作用于:控制层全局异常拦截,可以控制所有RequestMapping触发的异常信息
配合
@ExceptionHandler
处理指定异常@ExceptionHandler(Exception.class)
@ResponseStatus
返回异常的状态进行匹配:@ResponseStatus(HttpStatus.BAD_REQUEST)
mybatis Plus
pom.xml
MapperScan扫描
xml手动编写sql配置
基于Plus注解扩展Controller基础接口
基础CRUD实现
mapper
service
controller
关系结构图
子主题
通用字段填充
类型转换(typeHandler)
mybatis3+springboot
Plus
Sentinel
springboot集成Sentinel功能
pom
参数配置[application.properties]
流控异常处理:BlockExceptionHandler
dashboard管理平台
流控规则
概念:对于访问资源在单位时间得请求数量的控制
配置说明
参数图片
资源名称:访问资源名称:A
针对来源:
default:默认default(不区分来源,全部限制)
自定义:指定微服务名调用进行流控
阈值类型
QPS:资源每秒请求数量
并发线程数:资源并发请求数量
单机阈值:根据“阀值类型”请求的数量设置
流控模式
直接:超过阀值立刻抛出错误信息
关联:关联资源(B)出现限流,被关联资源(A)立刻限流
关联资源:B
链路:资源A->B->C->D形成一个链路【未彻底明白】
流控效果
快速失败:请求被限流,直接返回异常
Warm Up:逐渐释放请求数量,不会立刻将请求释放出来避免系统瞬间把系统压垮,
排队等待:
请求数量达到阀值时,请求会被排队等待
超时时间:所有等待的请求超过超时时间,会被拒绝立刻返回失败
熔断规则
概念:资源内部处理过程的响应进行控制
响应慢:RT
异常比例
异常数
配置说明
资源名称:指定资源路径或对应资源名称
熔断策略
统计时长:单位时间(毫秒)内,符合熔断策略(在指定时间内进行统计)
熔断时长:服务熔断后,等待多长时间恢复
最小请求数:请求总数,在此数量范围内进行统计异常数量比例
慢调用比例:
请求调用响应时间超过指定RT时间
异常比例 =( 请求总数量 / (异常数量=请求总数量响应时间>RT超时响应时间)
熔断 = 异常比例 > 比例阈值
熔断时间 = 熔断时长
异常比例
异常比例 =( 请求总数量 / 请求产生的异常数)
熔断时间 = 熔断时长
异常数
总请求数中产生异常的数量
热点规则
概念:即经常访问的数据,例如:某商品搞活动,指定商品ID进行限流
定义热点资源
@SentinelResource
标注资源,协助参数进行限流控制
value:定义资源名称
blockHandlerClass:限流后回调的类
blockHandler:限流后回调的方法名,必须是static ,参数与限流方法参数必须保持一致
SentinelResourceAspect
处理SentinelResource注解逻辑,实现源码
SentinelAspectConfiguration
将SentinelResourceAspect配置启作用
配置说明
资源名:指定资源路径或对应资源名称
限流模式:QPS
参数索引:指定入参索引,以下标0开始
单机阈值:请求数量
统计窗口时长:在指定时间范围内进行限流
高级参数说明(参数例外项):
概念:除了基础配置说明外,针对个别参数进行特殊限流
参数类型:限定入参数据类型->基础类型 + 字符串类型
参数值:限定 具体入参值,例如:商品ID,用户ID 等等
限流阈值:入参值限制访问数量
系统规则\授权规则【待完善处理】
遗留问题FAQ
持久化限流规则(nacos\redis\db)
子主题
nacos
源码&编译
源码下载
https://github.com/alibaba/nacos/releases
GitHub 下载编译后的代码非常慢,需要很长时间,所以下载源码,进行编译
构建项目
git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
ls -al distribution/target/
// change the $version to your actual path
cd distribution/target/nacos-server-$version/nacos/bin
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U
ls -al distribution/target/
// change the $version to your actual path
cd distribution/target/nacos-server-$version/nacos/bin
启动注册中心
startup.cmd -m standalone
注册中心
导入POM依赖
配置注册中心地址
开启服务注册注解
配置中心
导入POM依赖
设置项目启动配置内容
开启项目配置注解
Gateway
概念:微服务架构提供一种简单有效的统一的 API 路由管理方式
执行顺序:谓词 -> 过滤器 -> 路由地址(微服务应用)
底层采用Netty的通讯框架基于Reactor模型处理各种路由交互
Netty基于NIO技术,可读可写时通知应用
Reactor特点
关键词
路由(Router)
网关的基本构建块
组成部分
ID
转发目标URL地址(uri)
谓词集合(predicate),布尔值结果(判断组合条件:与,或,非)
过滤器集合(filters):匹配内容进行过滤跳转,实现 GatewayFilter 进行请求之前和之后进行处理
谓词(Predicate): 匹配请求中任何内容的参数是否满足请求规则
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
内容
过滤器(Filter):将路由之前或之后进行修改制定数据
子主题
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
谓词(默认11种)
自定义
注意点
默认不重写name方法,关键词采用类名前缀 XXX RouterPredicateFactory
数据填充 Config 对象设置,重写shortCutFieldOrder 方法,将对象属性添加到list中,用于捕获设置的内容
实现方式
1、继承 => AbstractRoutePredicateFactory
2、实现方法 => apply(),返回值判断是否通过谓词判断,true 通过,false 不通过
3、重写 => name() ,用于定义谓词关键词(- 关键词=tt,bb),默认采用类名(Item)
4、重写 => shortcutFieldOrder,用于定义接受对象属性名称
默认(11个)
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
过滤器(默认31种)
自定义过滤器:
注意点
GatewayFilter 用于路由指定过滤器
GlobleFilter 全局使用定义
基于Router过滤器GatewayFilter
1、继承 => AbstractGatewayFilterFactory
2、实现方法 => apply
3、重写 => shortcutFieldOrder,用于定义接受对象属性名称
全局Router过滤器GlobleFilter
1、实现 GlobalFilter接口的filter,针对请求之前与之后进行处理
2、实现,Ordered接口的getOrder方法,用于定义filter的执行顺序,以满足调用的先后
3、
默认(31个)
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
OpenFeign
概念:远程调用
POM依赖
版本管理
导入jar
开启客户端调用:@EnableFeignClients
定义公共接口调
知识点收集
全知识面:
Java 相关所有知识面
https://github.com/dunwu/blog
https://www.cnblogs.com/crazymakercircle/p/11704077.html
https://pdai.tech/md/java/io/java-io-nio-select-epoll.html
技术性网站
精彩网站荟萃
https://www.kancloud.cn/sstd521/design
https://qicoder.com/source-scg/source-scg-handler/
中间件
redis基础
linux 安装过程
解压文件:tar -zxvf redis-6.2.6.tar.gz
进入目录: cd redis-6.2.6
编译源代码: make
编译完成后,默认在解压目录
编译完成后,默认在解压目录
运行:./src/redis-server ./redis.conf
编译完成的文件可以集中存放,方便启动
cp redis-benchmark redis-check-aof redis-check-rdb redis-cli redis.conf redis-sentinel redis-server redis-trib.rb sentinel.conf /目录/bin
直接就可以启动
redis-server redis.conf
cp redis-benchmark redis-check-aof redis-check-rdb redis-cli redis.conf redis-sentinel redis-server redis-trib.rb sentinel.conf /目录/bin
直接就可以启动
redis-server redis.conf
配置文件说明(redis.conf)
后台运行:daemonize (默认为no,修改为yes)
导入配置文件:include 文件名
相同配置会存在覆盖的情况,请注意
相同配置会存在覆盖的情况,请注意
日志文件:logfile /目录/文件名
持久化数据
设置存放目录:dir /home/install/redis6/data/
设置存放名称:dbfilename redis6666.rdb
主从复制
新版本:replicaof ip port
旧版本: slaveof ip port
从机(默认只读模式):replica-read-only yes
900秒内1个key被修改就进行快照保存:save 900 1
子主题
子主题
面试题
JVM基础题(JVM虚拟机)
Java基础题
面向对象的特性
https://www.runoob.com/java/java-inheritance.html
继承
服用父类的方法和属性
提高代码的复用性
也是多态的一种表现
封装
隐藏内部实现
增加可维护性
复用性
多态
多态是同一个行为具有多个不同表现形式或形态的能力
多态就是同一个接口,使用不同的实例而执行不同操作
可移植性、健壮性、可维护性
ArrayList & linkList
ArrayList
数组实现
数据具有连续性
性能
插入、删除数据慢:数据需要整体移动
访问数据 快
linkList
链表实现
每一个数据节点都会存储下一个数据的指针
性能
插入、删除数据快:数据不需要移动,只要将数据的指针指向下一个地址即可
访问数据 慢
JDK1.8新特性
接口运行编写默认方法
面向函数式编程的Lamdb表达式
函数式的接口
新接口
predicate
function
suppline
consumer
optional
stream
filter
时间类
LocalTime
什么时候用:接口&抽象类
接口
基本特点
接口只能单继承(extends)
不能存在构造方法
接口只能存在默认方法(default 类型)
优点
降低了耦合,提高了拓展性
隐藏内部的实现
场景
定义的是一种标准,子类隐藏封装内部实现,增强安全性
可以实现接口当中可扩展子类,不确定的实现类
抽象类
基本特点
可存在构造方法 & 普通方法
抽象类可以实现多个接口(implements)
优点
可被实例化(多态)
抽象类是用来捕捉子类的通用特性的,包括属性及行为
场景
多个子类复用功能,可以采用抽象类
多个子类复用一些功能,同时子类在继承实现的时候需要进行扩展,可以使用抽象类
通用实现需要被复用且在复用中需要关键性判断,在子类中必须实现一些方法,可以使用抽象类
HashMap 底层实现原理
数组 、 链表
1、根据key 计算hash值,通过hash取模((n - 1) & hash)计算出数组下标值进行存储
2、存储数据超过默认初始大小,默认扩展当前大小的2倍
3、hash值相同,存储数据会变成链表形式存储,最新数据存储在头部
2、存储数据超过默认初始大小,默认扩展当前大小的2倍
3、hash值相同,存储数据会变成链表形式存储,最新数据存储在头部
JDK1.7:相同的hash值已链表形式存储
JDK1.8:相同的hash值已链表形式存储,链表超过8个会采用红黑树形式存储
JDK1.8:相同的hash值已链表形式存储,链表超过8个会采用红黑树形式存储
红黑树
红黑树的平均查找长度是log(n),如果长度为8,平均查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,而log(6)=2.6,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。
红黑树的平均查找长度是log(n),如果长度为8,平均查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,而log(6)=2.6,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。
1、HashMap线程不安全,多线程下扩容死循环
2、线程安全采用HashTable、Collections.synchronizedMap、以及 ConcurrentHashMap 可以实现线程安全
2.1 HashTable 是在每个方法加上 synchronized 关键字,粒度比较大
2.2 Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个 SynchronizedMap 对象,内部定义了一个对象锁,方法内通过对象锁实现
2.3 ConcurrentHashMap 在jdk1.7中使用分段锁,在jdk1.8中使用CAS+synchronized
2、线程安全采用HashTable、Collections.synchronizedMap、以及 ConcurrentHashMap 可以实现线程安全
2.1 HashTable 是在每个方法加上 synchronized 关键字,粒度比较大
2.2 Collections.synchronizedMap 是使用 Collections 集合工具的内部类,通过传入 Map 封装出一个 SynchronizedMap 对象,内部定义了一个对象锁,方法内通过对象锁实现
2.3 ConcurrentHashMap 在jdk1.7中使用分段锁,在jdk1.8中使用CAS+synchronized
hashcode & equeal 区别
hashcode
计算出对象的哈希码,作用是确定对象在哈希表中的索引位置
equeal
默认:Object.equest比较的是两个对象的应用相同
重写:一般比较的是内容是否相同
区别:
1、HashCode相同,对象不一定相同,有可能存在同一个链表中
2、equeal 相同,hashCode一定相同,存储位置在同一个位置
1、HashCode相同,对象不一定相同,有可能存在同一个链表中
2、equeal 相同,hashCode一定相同,存储位置在同一个位置
http与https区别
http
超文本传输协议,基于TCP/IP传输
以明文传输,容易被窃听截取 、篡改
数据真实性容易被伪造
默认端口80
无加密过程,资源消耗较少
https
SSL 数字证书,资源消耗较多
密文进行传输,不易被篡改和监听
默认端口 443
深拷贝&浅拷贝
浅拷贝:
1、复制对象的引用,指向同一块空间
2、原始对象值发生改变,新拷贝的对象值也发生改变
1、复制对象的引用,指向同一块空间
2、原始对象值发生改变,新拷贝的对象值也发生改变
深拷贝:
1、完整的复制对象的内容,新开辟一块内存地址空间进行存储,引用不同
2、原始对象值发生改变,新拷贝对象不发生改变
3、Cloneable 接口并且实现 clone 方法
1、完整的复制对象的内容,新开辟一块内存地址空间进行存储,引用不同
2、原始对象值发生改变,新拷贝对象不发生改变
3、Cloneable 接口并且实现 clone 方法
BIO、NIO、AIO 区别(java基础)
零拷贝(mmap、sendfile)
mmap:
1、mmap 通过内存映射,将文件映射到内核缓冲区,减少内核空间到用户空间的拷贝次数
2、内存拷贝(从 4 次变成了 3 次),适合 小数据 传输
3、java NIO MappedByteBuffer 实现
1、mmap 通过内存映射,将文件映射到内核缓冲区,减少内核空间到用户空间的拷贝次数
2、内存拷贝(从 4 次变成了 3 次),适合 小数据 传输
3、java NIO MappedByteBuffer 实现
sendFile:
1、内核缓冲区进入到 Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换
2、sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)
1、内核缓冲区进入到 Socket Buffer,同时,由于和用户态完全无关,就减少了一次上下文切换
2、sendFile 可以利用 DMA 方式,减少 CPU 拷贝,mmap 则不能(必须从内核拷贝到 Socket 缓冲区)
使用场景
KafKa
生产者 ->broker管道 采用MMAP 技术
消费者->读取broker管道数据采用SendFile 技术(客户端和 broker 进行数据传输时,会使用 transferTo 和 transferFrom 方法,即对应 Linux 的 sendFile)
生产者 ->broker管道 采用MMAP 技术
消费者->读取broker管道数据采用SendFile 技术(客户端和 broker 进行数据传输时,会使用 transferTo 和 transferFrom 方法,即对应 Linux 的 sendFile)
rocketMQ 在消费消息时,使用了 mmap
子主题
?IO复用、Epoll 区别
值传递、引用传递 的区别
基本类型的时候,用的是值传递
参数类型是对象、数组 、集合 是拷贝引用的值,所以能修改引用数据指向同一个对象
Redis基础题
资源链接:
https://github.com/doocs/technical-books#database
下载地址
https://download.redis.io/releases/redis-6.0.16.tar.gz
https://github.com/doocs/technical-books#database
下载地址
https://download.redis.io/releases/redis-6.0.16.tar.gz
redis 持久化有哪几种方式
持久化:就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失
持久化机制 :RDB(默认:快照) 和 AOF(写模式、日志模式) :
RDB(周期存储快照)
RDB是Redis默认的持久化方式。按照一定的时间将内存的数据以快照的形式保存到硬盘中,对应产生的数据文件为dump.rdb。通过配置文件中的save参数来定义快照的周期
优点
只有一个文件 dump.rdb,方便持久化
容灾性好,一个文件可以保存到安全的磁盘
性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO 最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis 的高性能
相对于数据集大时,比 AOF 的启动效率更高
缺点
数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
AOF(Append-only file)持久化方式:是指所有的命令行记录以 redis 命令请 求协议的格式完全持久化存储)保存为 aof 文件。
AOF(每一个写指令都进行存储)
AOF持久化(即Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复
优点
数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次 命令操作就记录到 aof 文件中一次。
通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof 工具解决数据一致性问题
AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令 进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
缺点
AOF 文件比 RDB 文件大,且恢复速度慢。
数据集大的时候,比 rdb 启动效率低
总结:
AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF
AOF文件比RDB更新频率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF
Redis 过期策略和内存淘汰策略
过期策略
定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果
Redis key的过期时间和永久有效分别(EXPIRE和PERSIST命令)
Redis中同时使用了 惰性过期 和 定期过期 两种过期策略
内存淘汰策略
内存淘汰策略
1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
2. allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
4. allkeys-random:加入键的时候如果过限,从所有key随机删除
5. volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
8. allkeys-lfu:从所有键中驱逐使用频率最少的键
1. noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
2. allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
3. volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
4. allkeys-random:加入键的时候如果过限,从所有key随机删除
5. volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
6. volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
8. allkeys-lfu:从所有键中驱逐使用频率最少的键
FIFO 淘汰最早数据、LRU 剔除最近最少使用、和 LFU 剔除最近使用频率最低
Redis 热点数据如何保证
redis内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。
Redis支持哪几种数据类型
String、List、Set、Sorted Set、hashes
?Redis哨兵(高可用)、主从(复制)、集群
主从
info replication 查看主从模式详情
replicaof 设置主机连接地址
SLAVEOF NO ONE 主机出现故障,在从机手动切换主机
从机只能读取数据,无法设置数据
原理
首次从机与主机建立连接,会将主机上所有的数据(xx.rdb)
一次性同步给主机
一次性同步给主机
主机再次进行写操作时,先写入rdb文件,然后在主动向从机发送数据
1、Redis使用默认的异步复制,其特点是低延迟和高性能
哨兵
sentinel monitor mymaster 127.0.0.1 6379 2
monitor :监控程序
mymaster : 监控主服务的名称(起名)
127.0.0.1 6279 监控指定服务器 ip 与端口
2 表示在主从模式下只要有两个从服务确认,即可切换主服务器
monitor :监控程序
mymaster : 监控主服务的名称(起名)
127.0.0.1 6279 监控指定服务器 ip 与端口
2 表示在主从模式下只要有两个从服务确认,即可切换主服务器
故障自动迁移,根据配置的选举数量(2)进行迁移
核心功能
监控(Monitoring):
Sentinel 会不断地检查你的主服务器和从服务器是否运作正常
Sentinel 会不断地检查你的主服务器和从服务器是否运作正常
提醒(Notification):
当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover):
当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器
当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器
Redis 单线程、多线程、事务
总结:
1、Redis 4.0 之前 的执行原理,通过IO多路复用器监听来自客户端的socket网络连接,然后由主线程进行IO请求的处理以及命令的处理,所有操作都是线性的(单线程)
2、Redis 4.0版本引入了Lazy Free(耗时数据异步处理),目的是将慢操作异步化,Lazy Free:异步删除耗时大的任务,达到主线程不会阻塞的效果
3、Redis 6.0版本 多线程模型只用来处理网络IO读写请求,对于 Redis 的读写命令,依然是单线程处理。网络处理经常是瓶颈,通过多线程并行处理可提高性能 (利用多核cpu处理网络请求)
4、Redis6.0 默认禁用多线程,只使用主线程。如需开启需要修改 redis.conf 配置文件:io-threads-do-reads yes,开启多线程后,还需要设置线程数,否则是不生效的(io-threads 4)
事务:
1、基础命令:MULTI、EXEC、DISCARD、WATCH、UNWATCH
2、 Redis的事务无法实现原子性,无法保证数据一致
1、Redis 4.0 之前 的执行原理,通过IO多路复用器监听来自客户端的socket网络连接,然后由主线程进行IO请求的处理以及命令的处理,所有操作都是线性的(单线程)
2、Redis 4.0版本引入了Lazy Free(耗时数据异步处理),目的是将慢操作异步化,Lazy Free:异步删除耗时大的任务,达到主线程不会阻塞的效果
3、Redis 6.0版本 多线程模型只用来处理网络IO读写请求,对于 Redis 的读写命令,依然是单线程处理。网络处理经常是瓶颈,通过多线程并行处理可提高性能 (利用多核cpu处理网络请求)
4、Redis6.0 默认禁用多线程,只使用主线程。如需开启需要修改 redis.conf 配置文件:io-threads-do-reads yes,开启多线程后,还需要设置线程数,否则是不生效的(io-threads 4)
事务:
1、基础命令:MULTI、EXEC、DISCARD、WATCH、UNWATCH
2、 Redis的事务无法实现原子性,无法保证数据一致
Redis 缓存穿透、击穿、雪崩
缓存击穿
并发访问量比较大的key在某个时间过期,导致所有的请求直接打在DB上,缓存击穿会增大数据库的压力
并发访问量大,缓存中没有数据,大量请求进入查数据库,导致数据库负载增大直到崩溃
并发访问量大,缓存中没有数据,大量请求进入查数据库,导致数据库负载增大直到崩溃
解决办法:
1、查询数据库 代码逻辑上加锁 或 设置永不过期,更新缓存
2、设置异步更新即将过期的缓存数据
1、查询数据库 代码逻辑上加锁 或 设置永不过期,更新缓存
2、设置异步更新即将过期的缓存数据
缓存穿透
缓存穿透指的查询缓存和数据库中都不存在的数据,这样每次请求直接打到数据库,就好像缓存不存在一样
缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义
缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义
解决办法:
1、数据库查询不到,可以缓存一个空对象或者 null,使缓存有效防御(如果数据存在了及时更新缓存)
2、使用 布隆过滤器(概率问题)
1、数据库查询不到,可以缓存一个空对象或者 null,使缓存有效防御(如果数据存在了及时更新缓存)
2、使用 布隆过滤器(概率问题)
缓存雪崩
缓存某一个时刻失效,大量请求查询数据库,数据库出现宕机或崩溃,运维马上又重启数据库,马上又会有新的流量把数据库打死,这种现象叫做 雪崩
缓存中大量数据同时过期
缓存中大量数据同时过期
解决办法:
1、避免缓存在同一时间失效
2、热点数据尽量设置不过期
3、可以使用 服务熔断或降级 进行优化,避免出现宕机
1、避免缓存在同一时间失效
2、热点数据尽量设置不过期
3、可以使用 服务熔断或降级 进行优化,避免出现宕机
mysql基础题
mysql架构
网络层
客户端与服务端建立连接,客户端发送SQL到服务端
服务层
系统管理:备份恢复、Mysql复制、集群
连接池:主要负责连接管理、授权认证、安全等等
SQL接口:接受用户的SQL命令,并且返回用户操作的结果
查询解析器:SQL命令传递到解析器的时候会被解析器验证和解析
查询优化器:SQL语句在查询之前会使用查询优化器对查询进行优化。它使用的是“选取-投影-联接”策略进行查询以此选择一个最优的查询路径
引擎层
负责数据的存储和读取,与数据库文件打交道。 服务器中的查询执行引擎通过API与存储引擎进行通信,通过接口屏蔽了不同存储引擎之间的差异。
系统(文件)层
层主要是将数据库的数据存储在文件系统之上,并完成与存储引擎的交互
事务四大特性(ACID)
你了解事务的几大特性吗
你了解事务的几大特性吗
原子性(整体):要么成功、要么失败,不允许有中间状态
一致性(数据):事务开始前和结束后,数据库的完整性约束没有被破坏
隔离性(并发提交):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
持久性(数据与故障):持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
你了解事务的几大特性
事务的并发问题
脏读:回滚的数据被读到
事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
不可重复读:事务提交之前与之后读取的数据不一致
事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果因此本事务先后两次读到的数据结果会不一致。
事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果因此本事务先后两次读到的数据结果会不一致。
幻读:其它事务插入破坏了当前事务读取数据(2)
1、幻读解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)
2、A事务读取了一个范围的内容,而同时B事务在此期间插入了一条数据
1、幻读解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)
2、A事务读取了一个范围的内容,而同时B事务在此期间插入了一条数据
char(n)、varchar(n) 区别
char(n) : 定长存储,存储内容不足自动补充空格,最大长度255
varchar(n) : 变长存储,存储内容<n 时,不会补充空格,只存储实际内容,最大长度16383
存储引擎
InnoDB(默认的存储引擎)
支持事务、行级锁、表级锁、外键
行级锁(锁粒度小并发能力高)
数据和索引是集中存储的(.ibd)
B+树索引,Innodb是索引组织表。
MyISAM
不支持事务、外键,支持表级别锁,并发差
表级别锁并发性能比较差,加锁比较快
数据和索引是分别存储的,数据(.MYD),索引(.MYI)
B+树索引,MyISAM是堆表
锁
有几种锁机制
表级锁(MYISAM引擎)
开销小, 加锁快; 不会出现死锁; 锁定粒度大, 发生锁冲突的概率最高, 并发度最低
子主题
行级锁(INNODB引擎)
开销大, 加锁慢; 会出现死锁; 锁定粒度最小, 发生锁冲突的概率最低, 并发度也最高
行级锁并不是直接锁记录,而是锁索引
页面锁(BDB引擎): 开销和加锁时间界于表锁和行锁之间; 会出现死锁; 锁定粒度界于表锁和行锁之间, 并发度一般
悲观锁、乐观锁 区别
悲观锁
特点:先获取锁,再进行业务操作,行级别锁
释放锁: 必须等待事务提交完成后自动释放锁,在此期间其他线程获取锁都是无效
索引:数据表没有添加索引或主键,所有扫描过的行都会被锁上。因此用悲观锁务必要确定走了索引,而不是全表扫描。
关键字:for update
乐观锁
特点:表结构增加字段(version)版本号管理控制
说明:根据版本号更新表结构数据
drop、delete与truncate
delete和truncate只删除表的数据不删除表的结构
delete语句是dml,这个操作会放到rollback segement中,事务提交之后才生效,如果有相应的trigger,执行的时候将被触发
truncate,drop是ddl, 操作立即生效,原数据不放到rollback segment中,不能回滚. 操作不触发trigger.
SQL优化
表结构字典尽量不要设置null值,应该设置空字符串或0
表关联不要超过3张表,关联查询应该遵循小表驱动大表
索引失效
尽量避免全表扫描,查询条件where是在索引的基础上
避免where 语句采用符号:null、 !=、OR 、in 、not in 、like 等判断出现,会导致全表扫描
避免 where 字段表达式 (+、连接)、函数 等出现,会导致全表扫描
字段类型是字符串,where时一定用引号括起来,否则索引失效
多用 explain 执行SQL检查是否落到索引上
优秀SQL优化
索引
索引是一种数据结构,可以帮助我们快速的进行数据的查找
使用联合索引时,注意索引列的顺序,一般遵循最左匹配原则
索引的数据结构和具体存储引擎的实现有关, 在MySQL中使用较多的索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引
Explain
显示执行计划详细信息:SHOW WARNINGS
输出格式
id 标记执行
ID 相同,从上到下执行
ID 不同,由大到小执行
ID 优先 数字越大越优先执行,遇到相同按照先后顺序执行
select_type SQL复杂类型
SIMPLE 简单SELECT(不使用 UNION或子查询)
PRIMARY 最外层SELECT
SUBQUERY 首先SELECT在子查询中
DERIVED 虚拟表 = 派生表
UNION 中的第二个或以后SELECT的语句 UNION
UNION RESULT 的结果UNION。
table
表名称
derivedN虚拟表名,N表示执行计划的ID号码
type 优化的级别
快到慢:system > const > eq_ref > ref > range > index > ALL
system :
1、表中只有一条记录
2、系统表,少量数据,往往不需要进行磁盘IO
1、表中只有一条记录
2、系统表,少量数据,往往不需要进行磁盘IO
const :(1对1)
1、表中某一列确定唯一,在整个表中根据列查询能够唯一确定一个行数据,被视为常量,const表非常快,只被读取一次
2、命中主键(primary key)或者唯一(unique)索引
3、常量连接
SELECT * FROM tbl_name WHERE primary_key=1;
1、表中某一列确定唯一,在整个表中根据列查询能够唯一确定一个行数据,被视为常量,const表非常快,只被读取一次
2、命中主键(primary key)或者唯一(unique)索引
3、常量连接
SELECT * FROM tbl_name WHERE primary_key=1;
eq_ref:(一对一)
1、join查询
2、命中 主键(primary key) 或者 非空唯一(unique not null) 索引
3、等值连接
4、主键索引(primary key)或者非空唯一索引(unique not null)等值扫描
SELECT * FROM `news` JOIN `content` ON news.id=content.news_id;
1、join查询
2、命中 主键(primary key) 或者 非空唯一(unique not null) 索引
3、等值连接
4、主键索引(primary key)或者非空唯一索引(unique not null)等值扫描
SELECT * FROM `news` JOIN `content` ON news.id=content.news_id;
ref:(1对多)
1、非主键非唯一索引等值扫描(返回大于多个数据)
SELECT * FROM news where title="test";
1、非主键非唯一索引等值扫描(返回大于多个数据)
SELECT * FROM news where title="test";
range:范围扫描
1、运算符中的任何 =一个 <>将 >键 >=列 <与 <=常量 IS NULL进行 <=>比较 BETWEEN时 LIKE使用 IN()
2、索引上扫码特定范围内的值
explain select * from user where id between 1 and 4;
explain select * from user where id in(1,2,3);
explain select * from user where id>3;
1、运算符中的任何 =一个 <>将 >键 >=列 <与 <=常量 IS NULL进行 <=>比较 BETWEEN时 LIKE使用 IN()
2、索引上扫码特定范围内的值
explain select * from user where id between 1 and 4;
explain select * from user where id in(1,2,3);
explain select * from user where id>3;
index:
1、扫描索引上的全部数据
2、InnoDB的count
explain SELECT count(*) FROM news
1、扫描索引上的全部数据
2、InnoDB的count
explain SELECT count(*) FROM news
ALL:
全表扫描,id上无索引
explain select * from content
全表扫描,id上无索引
explain select * from content
possible_keys 可被选择的索引,推荐使用的索引
Null 无可用所有,但并不代表Key 没有匹配的索引
根据WHERE 子句判断适合索引的某些列或列,从而提高查询的性能(推进使用的索引)
要查看表有哪些索引, SHOW INDEX FROM tbl_name
key 已使用的索引
可能key会命名值中不存在的索引 possible_keys。如果没有possible_keys索引适合查找行,但查询选择的所有列都是其他索引的列,则可能会发生这种情况。也就是说,命名索引覆盖了选定的列,因此虽然它不用于确定要检索哪些行,但索引扫描比数据行扫描更有效
字段是 MySQL 在当前查询时所真正使用到的索引.
key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度,在不损失精确性的情况下,长度越短越好
key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的
ref : 显示索引的那一列被使用了,如果可能的话,最好是一个常数。哪些列或常量被用于查找索引列上的值
rows : 所需的记录所需要读取的行数
Extra :
Using filesort : 无法利用索引完成的排序操作称为“文件内排序”(不采用索引排序,mysql内部重新排序)
Using temporary : 需要使用临时表来存储结果集,常见于排序和分组查询(查询出来的结果mysql内部存储在临时表,重新进行排序 & 分组)
Using index
表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。如果同时出现using where,表明索引被用来执行索引键值的查找;如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
Using join buffer 改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能
Impossible where 这个值强调了where语句会导致没有符合条件的行
Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
Using where:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
B+树、Hash表 区别
mybatis基础题
架构体系
接口层
提供给外部使用的接口API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理
数据处理层
负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等
基础层
负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。
原理
子主题
子主题
子主题
子主题
子主题
优点
简单易学:
容易上手、容易学习
集成简单
灵活
sql文件独立xml,便于统一管理和优化
无需过度关注底层实现
解耦
代码与SQL脚本解耦,方便扩展优化
提高可维护性
支持对象与数据库的orm字段关系映射
提供xml标签,支持编写动态sql
子主题
ORM
ORM (HIbernate)
全自动化映射关系
主要用于将java对象映射成数据库中的表
ORM(半自动化->mybatis)
半自动化映射关系
java方法与sql语句关联起来,而没有将java对象与数据库关联起来
分页实现方式
Interceptor
拦截器核心接口,可以拦截
拦截四种类型:
Executor:拦截执行器的方法
ParameterHandler:拦截参数的处理
ResultHandler:拦截结果集的处理
StatementHandler:拦截Sql语法构建的处理
PageHelper 也是采用interceptor方式实现
#{}和${}区别
#{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)
${} 就是字符串替换。直接替换掉占位符
为了防止 SQL 注入,能用 #{} 的不要去用 ${}
Mapper映射文件ID可以重复吗
不同mapper映射文件ID是可以重复的,因为Namespace不同
Namespace没有设置,则不能相同,因为Mybatis 解析方式使用Namespace + ID 的形式映射每一个statementMapper
同一个Mapper接口不能重载,因为是全名(Namespace)+方法名进行寻找策略对应MappedStatement
Mapper代理JDK动态代理
Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法
Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法
TypeHandler 基础类型转换
支持枚举类型,需要根据自己实现TypeHandler类型器处理
方法:
setParameter 将java类型数据转换为JDBC类型 参数
getResult 将jDBc类型参数转换为java对象类型
作用是javaType和jdbcType相互转换
缓存机制
缓存机制减轻数据库压力,提高数据库性能
一级缓存(sqlSession)
在创建sqlSession时默认开启,每个会话的缓存都是独立,其它会话是无法访问
相同会话多次相同查询,只有首次会查询DB,查询结果存入本地缓存,其余多次查询直接查询缓存
当会话内执行 insert、update、delete 后并且 commit 完成后,会话缓存会被清空,为了保持与db数据一致
缓存选项(localCacheScope):session 会话级别、statement当前查询
总结:
MyBatis一级缓存的生命周期和SqlSession一致。
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
MyBatis一级缓存的生命周期和SqlSession一致。
MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
二级缓存(Mapper)
开启缓存(cacheEnabled ):true
二级缓存实现底层数据共享,可以在多个sqlSession中共享,同时粒度更加的细
多表关联查询时会出现数据更新不及时,导致缓存失效
总结
1、MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
2、MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
3、在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
4、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 mapper 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异
1、MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
2、MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
3、在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。
4、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 mapper 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异
中间件基础题
spring基础题
收藏
0 条评论
下一页