学习总结
2021-04-12 00:03:58 63 举报
AI智能生成
针对使用到的框架和一些基础知识的总结
作者其他创作
大纲/内容
假设依赖链路上某个对象多次出现第一次getBean时将对象工厂放入三级缓存第二次getBean时回调对象工厂获取对象,并将获取到的共享对象放入二级缓存后续每次getBean时直接从二级缓存获取对象最后由第一次getBean的方法完成Bean的初始化,并将初始化好的对象移入一级缓存
ApplicationContext是BeanFactory的一个包装器实现添加额外功能,配置解析,环境变量支持,多语和事件驱动机制
IoC DI
首先创建好原始对象并完成依赖注入之后initializeBean阶段,在BeanPostProcessor中创建代理对象调用时调用了代理方法,切面责任链通过递归调用和目标方法穿插执行
AOP
从IoC容器获取相应的Bean去填充成员变量
第一次调用首先初始化九大组件
根据请求得到Handler链路执行器该执行器内部存储的Handler一般为Controller的某个method
找到能够处理该handler的handler适配器
对于REST接口使用RequestResponseBodyAdviceChain
handler.handleReturnValue
writeWithMessageConverters将反回结果经由消息转换器写入response
此时请求端收到返回结果
选择一个返回值处理器进行处理
由该适配器处理handler
对于非Rest请求,会返回mav
processDispatchResult处理请求结果
doDispatch
MVC
启动流程和自动加载机制原理https://www.processon.com/view/link/6075c6841e08534f371c2f0f
Boot
Spring
解析配置 -> 全局配置文件和Mapper配置文件
解析存储resultMaps、parameterMaps
解析存储mappedStatements:namespace+id到MappedStatement(SQL信息)的映射
根据namespace将接口类型type与MapperProxyFactory进行绑定注册至MapperRegistry用于后续创建代理对象时使用(JDK代理对象使用MapperProxy作为拦截器)(接口类型非必须,存在时才注册,在于用户是否使用接口的方式执行SQL)
存在接口类型的情况则通过MapperAnnotationBuilder解析接口上的注解
解析完毕返回Configuration创建DefaultSqlSessionFactory
创建会话工厂
拿到TransactionFactory去创建一个TX注意:TXManager为全局配置文件的必配属性,由该配置生成TXFactory
根据执行器类型和TX去创建一个执行器
全局开启代表会使用CachingExecutor包装实际的执行器使Executor拥有了和二级缓存交互的能力
Mapper中使用Cache标签代表针对Mapper生成一个二级缓存此时执行器还没有和二级缓存交互的能力只有全局和Mapper同时开启时二级缓存才生效
若全局开启二级缓存则用CachingExecutor包装原有的执行器
插件链通过JDK动态代理层层包装执行器Executor
返回DefaultSqlSession
创建会话
通过MapperRegistry获取MapperProxyFactory创建代理对象MapperProxyFactory由创建会话工厂的第四步完成注入MapperProxy作为方法拦截器
MapperProxy包含:SqlSession用于执行SQL;mapperInterface接口类型用于和方法名称共同组成唯一ID找到SQL信息;methodCache用于缓存提高性能。
获取代理对象
第一次调用时构建MapperMethodInvoker并缓存然后委托内部的MapperMethod执行
在MapperMethod执行方法内会解析方法参数拿到保存的SQL的ID后交给SqlSession执行此时相当于体现了接口的功能,即不需要手动通过设置ID和参数去调用SqlSession
SqlSession根据Configuration拿到MS交由执行器执行语句
执行Executor的插件逻辑
组成:Id、Offset、Limit、Sql、value、环境ID注意:通过Hash提高了比较性能
创建CacheKey
当Mapper.xml中配置了Cache标签则走二级缓存逻辑
注意:因为二级缓存跨会话,所以需要与事务绑定
二级缓存写入时首先会放入TransactionalCache的entriesToAddOnCommit当事务提交时才会写入实际缓存
执行CachingExecutor逻辑
查询本地缓存,本地缓存与执行器Executor1对1绑定,执行器与Session1对1绑定
创建ParameterHandler,会被插件包装
创建ResultSetHandler,会被插件包装
创建StatementHandler,会被插件包装
根据StatementHandler创建Statement
通过ParameterHandler对Statement设置参数
调用ps.execute();执行查询
通过ResultSetHandler处理结果集
实际执行
本地缓存为STATEMENT级别则清除缓存
执行器执行SQL
执行器执行SQL假设此时为Select
SQL执行
Mybatis
线程模型原理https://www.processon.com/view/link/603c8acd07912913b4f26903
Vertx
详细总结https://www.processon.com/view/link/602cbfa8e0b34d208a81c015#map
入口:bind()
openServerSocketChannel 创建 JDK ServerSocketChannel
id
unsafe -> NioMessageUnsafe
HeadContext
TailContext
pipeline -> DefaultChannelPipeline
SelectionKey.OP_ACCEPT 保存感兴趣的事件
ChannelConfig
new NioServerSocketChannel()
设置各种参数
用于处理新连接接入
pipeline添加ServerBootstrapAcceptor
init(channel)
由ThreadPerTaskExecutor创建一个线程执行任务
SingleThreadEventExecutor.this.run()此时NioEventLoop正式启动,开始无限循环
executor.execute
第一次执行startThread()
unsafe.register-> AbstractChannel.this.eventLoop = eventLoop此时保存Channel和EventLoop的关系
register0(p)
NioServerSocketChannel内部的JDK Channel注册至NioEventLoop内部的JDK Selector注意:此时尚未监听任何感兴趣的事件
doRegister()
ChannelInitializer.handlerAdded-> initChannel(ctx)
pipeline.invokeHandlerAddedIfNeeded()
pipeline.fireChannelRegistered()
addTask(task)
NioEventLoop.execute
bossGroup.next().register(channel)根据Chooser选择下一个NioEventLoop交给channel
initAndRegister()
HeadContext.bind-> unsafe.bind-> doBind此时JDK Channel与端口进行绑定
AbstractChannelHandlerContext.invokeChannelActive(head)
HeadContext.channelActive
channel.read()-> tail.read()-> HeadContext.read()
unsafe.beginRead()
selectionKey.interestOps(interestOps | readInterestOp)开始对之前保存的感兴趣事件进行监听,此时为Accept事件
Channel.doBeginRead
当传播结束时,调用readIfIsAutoRead();
注意:对于Inbound事件,从头部往尾部传播;对于Outbound事件,从尾部往前传播;Inbound为被动事件OutBound为主动事件
pipeline.fireChannelActive()通道激活传播事件
channel.bind()-> pipeline.bind-> tail.bindbind事件从尾到头传播
doBind0(channel)
服务端启动
EventExecutor[] children
chooser
可以认为,Group是一个拥有固定核心线程数的一个线程池NioEventLoop为内部的一个核心线程一个Group下的NioEventLoop共用一个ThreadPerTaskExecutor进行线程创建NioEventLoop执行任务时,会委托内部的Executor执行,此时会创建一个线程,开启无限循环NioEventLoop内部会包含这个创建好的线程,用来判断提交任务的线程是否为EventLoop线程
伴随NioEventLoopGroup创建
第一次启动时此时内部保存的线程为null,启动NioEventLoop
executor.execute由ThreadPerTaskExecutor分配一个线程执行启动逻辑
this.thread = Thread.currentThread();NioEventLoop内部保存这个创建好的线程
SingleThreadEventExecutor.this.run()-> NioEventLoop.run()正式启动NioEventLoop,即无限循环
doStartThread()
启动
阻塞式Select,当有穿插任务时,会被唤醒
本应该阻塞实际未发生阻塞并且select次数超过512次触发rebuildSelector,将旧的selector上注册的channel迁移至新的selector上
解决空轮训Bug
select
处理IO事件
processSelectedKeys
默认处理IO事件与任务的事件比为1比1即任务并不一定能够一次处理完毕
循环获取定时任务中应该执行的任务,将其放入taskQueue
在deadline之前不断取出task执行
当执行过任务时,执行尾部任务
afterRunningAllTasks
runAllTasks
执行逻辑
NioEventLoop
NioSocketChannel同样保存类似于NioServerSocketChannel的信息
不断的accept连接创建NioSocketChannel-> 放入readBuf这个List中
从Head节点向后传播
ServerBootstrapAcceptor.channelRead处理新连接
unsafe().register
doRegister将Netty Channel内部的JDK Channel与Reactor内部的Selector绑定注意:此时未监听任何事件
pipeline.invokeHandlerAddedIfNeeded()触发Child的ChannelInitializer的initChannel方法
Active从Head节点开始传播Head节点传播结束后,调用readIfIsAutoRead()
unsafe.beginRead()设置感兴趣的事件为SelectionKey.OP_READ
pipeline.fireChannelActive()注意:对于SocketChannel来说,accept后已经处于激活状态,所以此时直接触发active事件Server的Channel需要bind才会处于激活状态
register0
childGroup.register(child)给Channel分配一个Reactor线程
循环调用pipeline.fireChannelRead(NioSocketChannel)
pipeline.fireChannelReadComplete()
unsafe.read()
新连接接入
BIO
MappedByteBuffer用于内存映射文件
所能打开的最大连接数
FD巨增的情况
消息传递方式
NIO
AIO
对比
IO
Netty
连接器,管理连接,验证身份和权限
解析器,词法语法分析,生成解析树
预处理器,检查,表,字段是否存在
优化器,SQL语句优化,生成执行计划,通过explain可以查看
执行引擎,执行执行计划,返回数据
innodb
myisam
常见存储引擎,提供读写接口
组成
按页读取放入缓冲池,页默认大小16K预读机制,加载更多的页
修改数据写入缓冲池,此时数据不一致为脏页由后台线程进行刷脏
Buffer Pool包含Change buffer
重做日志,属于物理日志,记录数据页上的操作顺序写,当写满时触发buffer pool刷脏用于崩溃恢复,如buffer pool尚未刷脏,即满足D(持久性)需求
0 延迟写,log buffer每秒1次写入file并flush至磁盘
1 默认,实时写,实时刷,每次事务提交写入file并flush至磁盘
2 实时写,延时刷,实时写入file,每隔1秒flush
log buffer写入时机
Log Buffer(redo log)
回滚日志,属于逻辑日志,记录事务发生前的状态记录反向操作,insert记录delete,update记录原来的值为满足A(原子性)需求
undo log
内存结构
数据字典,undo logs
双写缓冲区由于操作系统page大小为4K,innoDB默认为16K需4次写入,可能导致部分写失效故写入磁盘page时先顺序写一份page的副本
系统表空间
ibd,存放表的索引和数据
独占表空间
共享表空间,临时表空间
redo log
属于逻辑日志,记录了所有的操作,DDL,DML用于主从复制和数据恢复文件内容可追加,没有大小限制
bin log由Server层提供
磁盘结构
客户端发送更新语句至Server层
执行引擎执行更新语句后调用存储引擎接口进行更新后数据的存储
InnoDB将修改结果写入内存中
InnoDB记录undo log和redo log,此时redo log状态设为prepare
返回执行器,代表此时可以发生提交
执行器记录bin log日志
执行器调用接口提交事务
InnoDB将redo log设为commit状态
语句执行完毕
更新语句执行
InnoDB
解决了平衡二叉树节点对Page页利用不足的情况InnoDB按页读取数据所以通过B Tree在一个节点(16K)上存储足够多的索引数据B Tree其实就是平衡多叉树,节点空间利用足够多,减少磁盘IO频繁更新的列上建索引会导致树不断的分裂和合并
多路平衡查找树(Balanced Tree)
节点内关键字和路数相等关键字存储的磁盘位置为关键字到下一个关键字的左闭右开区间磁盘位置引用:[关键字,下一个关键字)
根节点和枝节点不存储数据,只有叶子节点存储数据叶子节点之间构成双向链表
优势:非叶子节点不再存储数据,存储更多关键字,树深更低,减少磁盘IO范围查找优势,通过底层双向链表遍历IO性能稳定,都需要从子节点获取数据
B+树
数据结构
数据存在.MYD
索引存在.MYI所有的索引叶子节点都为数据的物理磁盘地址,从MYD查找数据
聚集(簇)索引 - 索引顺序与物理磁盘顺序一致
二级索引查询,首先找到主键值,在主键索引查询叶子节点未存放物理地址,因为物理地址可能发生变化
若有主键索引,则选择主键索引作为聚集索引,叶子节点存放数据其他索引为二级索引,叶子节点存放主键值,需要回表
若没有主键索引,则选择第一个不包含null的唯一索引作为主键索引,选其为聚集索引
若没有主键索引也没有非空唯一索引则innoDB生成一个默认6字节长的_rowid作为隐藏的聚集索引,随着插入单调递增
存储引擎存储方式
离散度
联合索引最左匹配原则
当数据列所需的字段在二级索引中就能获得,则不需要回表这种使用索引的方式称为覆盖索引注意:当查询未使用索引时,但是优化器发现使用索引树查询列数据更快时仍然会发生覆盖索引extra:'Using index' 覆盖索引
覆盖索引
针对二级索引,未使用索引仍由索引过滤数据称为索引条件下推,默认开启如 %xxx 未使用索引(未根据索引匹配数据,但有可能通过索引树查数据,根据列字段判断)发生条件下推时由二级索引过滤数据然后回表,返回Server层的数据在某种情况下可以骤减相当于在存储引擎层完成了数据过滤extra:Using index condition 发生索引条件下推,即由索引过滤数据extra:Using where 返回的数据不满足全部条件,由Server层过滤
索引条件下推(ICP)
根据列字段全表扫描发生在索引树的情况,我们需要关注rows字段这种情况为使用了索引但是全表扫描了索引
全表扫描索引
索引使用原则
数据匹配类型
type
可能用到的索引
possible_keys
实际使用到的索引
key
扫描行数
rows
'Using index' 覆盖索引,无需回表,直接使用索引树
Using index condition 发生索引条件下推,即由索引过滤数据
Using where 返回的数据不满足全部条件,由Server层过滤
extra
常用字段说明
索引
操作共同失败成功undolog保证
atomicity
分为数据库一致性和用户自定义一致性数据库一致性:由其他三个属性保证,保证数据的合法状态用户自定义一致性:能量守恒,数据不会凭空多/少,或非法如余额<0的状态(通常在代码中控制)
consistency
并发干扰注意:事务的原子性和并发编程中原子性的区别
isolation
只要事务提交,结果就为永久性的redolog和双写缓冲保证
span style=\
特性
脏读:一个事务读到另一个事物未提交的数据此时第二个事务回滚时会导致数据不一致
不可重复读:一个事务内读到另一个事务提交的修改后的数据
幻读:一个事务内的范围查询读到另一个事务提交插入的数据注意:幻读特指新插入的行
读不一致:未发生更新数据时事务内两次读到的数据不一致
解决方案,由数据库的隔离性解决
并发的三大问题即读一致性问题
加锁,不允许并发读写,即一个事务读时不允许其他事务写
LBCC
基于数据版本
能看到:第一次查询前已提交的事务的修改本事务的修改不能看到:比当前事务id大的事务,即当前事务后创建的事务未提交的事务
当前事务ID,创建版本号
DB_TRX_ID
回滚指针,删除版本号
DB_ROLL_PTR
隐藏字段
m_ids当前系统活跃(未提交)事务ids
min_trx_id 最小m_ids
max_trx_id 系统分配的下一事务id
creator_trx_id 生成RV的事务id
trxid = c-trx-id,当前事务修改,可见
id <min_id,生成RV时已提交的数据,可见
id>max_id,生成RV后提交的数据,不可见
min<id<max,在活跃id中则不可见,否则可见
由最新的版本往前查找,若可见则停止
RR第一次快照读时建立RVRC每次快照读时均建立RV
可见性算法
Read View
MVCC
读一致性解决方案
事务
共享锁/读锁
增删改会自动加互斥锁
排它锁/写锁
当事务提交时释放锁
锁的释放
用于表示有数据行已发生加锁行为便于加表锁时的判断,不需要扫描行是否加锁用于提交加锁效率
意向共享/排他锁
对于主键索引和二级索引来说,锁的是主键索引对于全表扫描来说,锁的是整个表
根据索引精确匹配时只会锁住一行
Record记录锁
等值/范围查询未命中数据行时,使用间隙锁
间隙锁用于阻塞insert,相同的间隙锁之间不冲突
锁区间
Gap间隙锁
对于间隙,阻塞插入对于记录,阻塞获取锁,即无法修改或带锁查询
锁住当前区间和下一个左开右闭区间阻塞了insert操作,即不会出现幻读
Next-Key临键锁
行锁原理
锁
RU 不加锁
序列化 Select隐式加共享锁,与修改互斥
普通Select使用快照读,第一次生成RV,基于MVCC实现
加锁Select和更新使用当前读,底层使用记录锁,间隙锁,邻键锁
RR
普通Select使用快照读,每次生成RV
只使用记录锁,没有Gap锁并不会阻塞insert操作,无法解决幻读问题,修改时的受影响行数和看到的不一致
RC
RR间隙锁导致锁定范围增大
条件列未使用索引时,RR锁表,RC锁行测试结果:在RC下同样使用非索引字段会互斥,产生锁表效果,但一方使用非索引字段,一方使用索引字段,不会互斥(若同一行数据仍然互斥)表现结果为:锁非索引字段和锁行记录,不会阻塞索引字段上的非当前行加锁,使用索引字段加锁会导致非索引字段全表锁定RR任何时候未匹配索引,会导致锁表,即使一方使用非索引字段,一方使用索引字段
RC半一致性读增加update效率
RR和RC区别
innoDB隔离级别的实现
增加可用连接数
及时释放不活动的连接,防止客户端假死导致连接未释放
客户端层面对连接复用,使用连接池设置合适的连接池大小
连接
报表数据缓存
数据缓存,减少数据库复杂查询
缓存
主从,读写分离
分库分表
开启慢查询,设置慢查询参数
id不同时按id顺序逆序分析,id最大为最里的子查询
id相同为关联查询,正序分析小表驱动大表,先执行查询结果少的表
SIMPLE简单查询,不包含子查询
PRIMARY 主查询,外层查询SUBQUERY 子查询DERIVED 衍生查询,用到临时表UNION 联合查询
select type
full index scan查询全部索引中的数据,特殊的全表扫描
index
full table scan全表扫描
all
possible keys 可能用到的索引key 实际用到的索引
key_len 索引长度
rows 预估扫描行数
存储引擎返回的有效数据比例当比例很低时,代表返回了过多的数据需要在Server层过滤
filters
using index 覆盖索引无需回表
using where 需要Server层过滤数据
using index condition 索引条件下推
using filesort 无法使用索引排序,使用了额外的排序,需要优化
using temporary 使用了临时表
explain分析
慢查询优化
恰当的整数类型
固定长度使用char,varchar需要一个字节记录长度
非空,使用特殊值代替null
不要使用外键,触发器,存储过程,视图
大文件存储至文件服务器,数据库只存url
表拆分或字段冗余
字段定义
优化
假设两个并发事务共同开启两个事务共同查询发现余额为1,可消费假设第二个事务此时发生更新,那么由于锁定机制第一个事务的update阻塞第二个事务更新后发现库存减少到0,那么提交事务第一个事务在第二个事务提交后完成更新操作,之后继续读操作,发现库存减少到-1,回滚
利用事务的超卖解决方案
MySql
redisObject是对五大数据类型的抽象type为对外类型,encoding为编码类型指向实际的存储对象,此时为string
存储形式
C语言本身没有字符串通过字符数组存储字符,需要提前预先分配足够的空间本身是个字符数组,获取长度需要遍历C函数库操作字符串的方式通过\\0结尾,存储一些二进制文件可能不安全
SDS是一个结构体,可以近似认为一个对象提供了自动扩容,记录字符串长度的能力通过预分配和惰性释放防止每次重新分配内存通过len长度属性判断字符串结尾,二进制安全
SDSSimple Dynamic String
int 存储8字节长整型,当长度超出时转为embstr
一次连续内存分配,分配RedisObject和SDS释放只需要一次释放
只读,每次修改时转为raw
embstr 存储小于44字节的字符串设置时大于44字节时转为raw或每次修改时转为raw
两次内存分配,分别分配
raw 存储大于44字节的字符串
编码类型
缓存热点,报表数据
分布式session
基于setnx的分布式锁注意:并不推介使用这种方式,无法实现可靠稳定的锁推介使用lua脚本的方式实现
全局ID生成器 incrby
计数器 incr限流 如密码输错N次不允许再次输入
使用场景
String
String类型组成的Map
特殊的双向链表,本质为一个数组不存储指向pre和post的指针取代存储上个节点长度和当前节点长度时间换空间
当field < 512且所有k-v字符串长度和 < 64字节即字段个数小且字段值小的情况下使用ziplist
ziplist
数组+链表定义了两个hash表,用于扩容使用类似的有Netty使用的2个hash表chm内部也会多创建一个hash表
hashtable
存储对象,如JSON对象本质就是一个map
String能做的都可以做
key 用户ID
field 商品id
value 商品数量
hincr/hdecr 增加减少购买数量
hgetall 全选
hlen 商品数量
购物车
Hash / HSet
链表,按照String元素插入排序
双向链表+数组quicklist中的每个quickNode存储一个ziplist
编码类型:quicklist
存储有序内容
分布式阻塞队列/分布式队列
分布式锁排队
String类型组成的无序集合本质是个隐藏value的HashMap
当元素全为整形时
intset
不是整形/元素个数超出512
spop 抽奖,随机获取元素
sadd 点赞srem 取消点赞sismember 是否已经点赞smembers 获取所有点赞用户scard 点赞数
sadd 商品标签
sdiff 差集 可能认识的人sinter 交集 共同关注sunion 并集
Set
String类型元素组成的有序集合每个元素有一个score,由小到大排序
按照score递增排序,插入时需要移动之后的数据
元素个数大于等于128个或任一member字符串长度大于等于64字节
通过设置不同的节点高度,来减少查找数量首先从最高的节点找,当发现查找节点小于某个节点是回退去下一层节点找
skiplist
实现任务权重队列
点击数
获取今天点击量最高的数据
排行榜
ZSet / Sorted set
位图
BitMap
不精确的数据统计,内存占用非常小
HyperLogLog
二维记录
Geo
类似于kafka的消息队列
Stream
数据类型
服务注册与发现
配置中心
分布式锁的唤醒
基于发布订阅
其他使用场景
定时触发
shutdown触发
save 当前保存,阻塞redis处理线程bgsave 后台保存,fork子进程处理数据生成rdb由于OS的copy-on-write机制,两个进程使用同一份数据当父进程修改数据时会重新创建一份数据,所以子进程相当于拿了一个快照数据
redis down掉会丢失最后一次快照之后的数据
主从复制时触发
RDBredis database
通过日志的顺序写记录每个写操作,先写入磁盘缓存
从不fsync,由操作系统刷盘
每次写入刷盘
每秒刷盘 默认
持久化策略磁盘缓存
百分比参数 100当文件大小为上次重写后AOF文件的2倍进行重写
最小文件参数 64M
手动触发:BGREWRITEAOF
通过重写机制防止文件过大读取现有键值对,保留恢复当前数据的最小指令集
AOFappend only file
启动优先级:AOF > RDB
体积: AOF > RDB
恢复速度:RDB更快
数据安全:RDB丢失最后一次快照后的数据,AOF由策略决定
RDB每次进行全量同步,影响磁盘IO性能
比较
持久化机制
实时性不强的情况下,可以使用定时任务/过期时间令缓存数据失效从而达到最终一致性
更新缓存容易导致数据不一致,所以一般使用删除缓存
更新数据库,删除缓存,通过重试令这次删除成功
先删除缓存,后更新数据库此时需要对数据库访问线程加锁,防止更新数据库时有别的线程重置了缓存即当发现需要更新数据时,加锁,删缓存,更新数据库
数据一致性
加锁或使用队列,对同一个key只有一条数据访问数据库
定时预更新
添加随机数防止同时失效
永不过期
缓存雪崩
缓存空数据或特殊字符,设置较短的过期时间
当恶意请求,每次使用不同的ID访问,上面这种方式失效使用布隆过滤器,针对假阳性的数据进行特殊缓存
缓存穿透
特殊的缓存雪崩,对操作数据库的线程加锁即可
缓存击穿
缓存三大问题
scan模糊查询代替keys指令
海量数据查询某一固定前缀的key
批量发送命令
pipeline
slave发送sync命令至mastermaster启动后台进程,保存RDB文件master缓存接收到的后续写命令master完成写文件后将文件发送给slave使用新的rdb替换旧的rdb,进行数据恢复master将增量写命令发送至slave进行回放
全同步
master判断接受到的命令是否为写命令,即是否需要同步将操作记录追加至AOF中发送数据至slave
增量同步
主从同步原理
hash环倾斜,导致数据分布不均匀
虚拟节点
通过slot的分配方式
一致性hash
常见问题
Redis
Bootstrap ClassLoader-> Extendsion ClassLoader-> Application ClassLoader-> Customer ClassLoader
默认委派关系
loadClass(String name)
Verify 是用来校验加载进来的class文件是否符合class文件标准,如果不符合直接就会被拒绝了;
Prepare 是将class文件静态变量赋默认值而不是初始值,例如static int i =8;这个步骤并不是将i赋值为8,而是赋值为默认值0;
Resolve 是把class文件常量池中用到的符号引用转换成直接内存地址,可以访问到的内容;
初始化时才会执行静态代码块
ClassLoader
已被虚拟机加载的类信息常量池静态变量即时编译后的代码
java8之前处于永久代
java8之后处于元空间,堆外内存
方法区
存储实例对象
堆
线程共享区
存放方法运行时所需的数据,成为栈帧栈帧 = 局部变量表+操作数栈局部变量表包含本地变量
注意:逃逸分析作用域在方法内的小对象会进行栈上分配,并进行标量替换(将对象字段作为局部变量分配)
虚拟机栈
服务native方法
本地方法栈
记录当前线程所执行到的字节码行号
程序计数器
线程独占区
运行时数据区
将热点代码编译为本地平台相关的机器码
jit即时编译器
用于申请和回收垃圾
gc垃圾回收器
执行引擎
本地库接口
本地方法区
非堆
old
eden
s0
s1
young
内存划分
强引用
SoftReference
WeakReference
PhantomReference
弱引用和虚引用都不会影响对象的回收
虚引用用在直接内存的回收内存映射文件,当对象回收时,收到通知并清理直接内存
引用类型
对象生命周期
eden/survivor空间不足
老年代空间不足
方法区空间不足
System.gc()通知
回收时机
将内存划为相等的两块区域,每次只使用其中一块
直接将存活的对象复制到另一块区域,将使用过的内存区域一次清除
空间利用率低适合存活率低的场景,copy数量少,效率高分配内存只需要直接移动堆顶指针,按序分配若不按1比1分配空间,需要额外空间担保
复制(Copy)
标记:标记存活对象
清除:清除未标记的对象,释放出对应的内存空间
产生大量内存碎片
标记-清除(Mark-Sweep)
标记所有存活对象
让所有存活对象向一端移动,清理掉边界外的内存
标记-整理(Mark-Compact)
使用复制算法,大部分对象朝生夕死,由Old区作分配担保
Young区
使用标记清除或标记整理算法,old区对象存活时间长,复制算法效率低下
Old区
分代收集
引用计数
双色标记
三色标记
GC Root链路
标记算法
垃圾回收算法
垃圾回收算法的具体实现
复制算法,单线程收集,新生代
Serial
标记-整理算法,单线程收集,老年代
Serial Old
Serial的多线程版本,新生代
ParNew
类似ParNew,可以控制吞吐量和停顿时间-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间 -XX:GCTimeRatio直接设置吞吐量的大小。
Parallel Scavenge
Parallel Scavenge的老年代版本,标记-整理算法
Parallel Old
初始标记,GC ROOT直接可达对象
并发标记,由这些灰色节点出发标记
重新标记,对突变导致变回灰色的节点重新标记
清除不可达对象,注意finalize方法
并发清理
三色标记,减少STW时间由于并发清除,使用了标记清除算法concurrent mode failure导致其退化为serial old老年代空间不足,只能暂停用户线程进行清理
CMSConcurrent Mark Sweep
内存布局基于Region,新老不再物理隔离,分代收集H:当对象大小等于Region一半时认为大对象,放入这个区region之间使用复制算法,分配内存通过指针碰撞
G1使用了三色标记算法,标记整理算法
-XX: G1HeapReginSize 设置Region大小-XX:MaxGCPauseMillis 最大停顿时间-XX:ParallelGCThread 并行GC工作的线程数-XX:ConcGCThreads 并发标记的线程数-XX:InitiatingHeapOcccupancyPercent默认45%,代表GC堆占用达到多少的时候开始垃圾收集
G1Garbage First
垃圾收集器
停顿时间,垃圾收集器收集过程中用户线程暂停时间
Stop The World
运行用户代码时间 / (运行用户代码+垃圾收集时间)
吞吐量
方法区中的常量,静态变量栈中局部变量表的引用变量本地方法栈的引用变量被同步锁持有的对象
GC Root
用户线程只会运行至安全点暂停
SafePoint
术语
查看java进程
jps
实时查看修改JVM参数manageable的flag才能实时修改
jinfo
查看类装载信息
查看垃圾收集信息
jstat
查看线程堆栈信息,可以发现程序死锁
通过jstack解决CPU100的情况
jstack
生成Dump文件-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=\"D:/LYT/gc dump/dump.hprof\"指定参数,发送内存溢出是自动生成dump文件
jmap -heap PID 查看堆内存使用情况
jmap
可视化查看,还可以看mbeans
jconsole
监控本地/远程java进程
jvisualvm
常用命令
GC
JVM
可见性
有序性
memory = allocate()分配对象内存空间
initialize(memory)初始化对象
instance = memory设置instance指向刚分配好的内存空间此时instance != null
双重检查锁创建单例的安全问题
CPU多级缓存 - 解决CPU与主存之间读写速度不匹配的问题
高速缓存架构
疑问:若变为E才读取内存数据那While循环应该就是最终一致性的,但测试while循环无法停止
数据被修改,只存于当前缓存行中,与主存数据不一致缓存行必须时刻监听所有试图读该缓存行相对应的内存的操作其他缓存须在本缓存行写回内存并将状态置为E之后才能操作该缓存行对应的内存数据
Modify
数据只存在于当前缓存行中,与内存数据一致缓存行必须监听其他缓存读主内存中该缓存行相对应的内存的操作一旦有这种操作,该缓存行需要变成S状态
Exclusive
数据被多个CPU缓存,且各个缓存中的数据与主存一致监听缓存行的修改,将当前缓存行置为无效状态
Shared
表示当前缓存行已失效
前提:所有内存的传输都发生在一条共享的总线上
目标:CPU缓存不停嗅探总线上发生的数据交换,跟踪其他CPU缓存做了什么当发现数据变更时,且当前CPU缓存存在这份数据,则令其失效
总线嗅探机制
MESI协议
CPU写入Store Buffer,由SB发送通知接收ACK,然后写入缓存此时CPU不再需要等待invalid的ack执行下一个指令,导致执行重排序
Store Buffer
用于失效CPU缓存行
invalid queue
MESI优化
读屏障 - 执行invalid queue,后续从主存读取数据
写屏障 - 执行Store Buffer,将数据直接写入主存并且解决了指令重排,重排序其实优化了缓存行的使用
volatile根据不同操作系统和CPU架构生成不同的内存屏障指令
内存屏障
Thread-A发出LOCK#指令加锁
发出的LOCK#指令锁总线(或锁缓存行)1) 令Thread-B高速缓存中的缓存行内容失效2) Thread-A向主存回写最新修改的值
锁释放
Thread-A
发现对应地址的缓存行被锁了,等待锁的释放,缓存一致性协议会保证它读取到最新的值
Thread-B
总结
原理
volatile
重排序:源代码 -> 编译器优化重排序 -> 指令重排序 -> 内存系统重排序 -> 最终执行的指令序列
可见性:重排序导致了可见性问题
提供了可见性和有序性的解决方案
对于单个线程中的每个操作,前继操作happens-before于该线程中的任意后续操作
程序顺序规则注意:与as-if-serial的区别
对一个锁的解锁,happens-before于随后对这个锁的加锁
监视器锁规则
volatile变量的写操作,happens-before这个变量的读操作
volatile变量规则
如果A happens-before B,且B happens-before C,那么A happens-before C
传递性
在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见
线程启动原则
A调用B的interrupt,A对中断标识的修改对B可见
线程中断原则
线程中所有的操作都happens-before线程的终止检测,如Thread.join() Thread.isAlive()
线程终止规则
一个对象的初始化完成happens-before它的finalize()方法开始
对象终结规则
内存屏障保证
finally原则
简单的说就是我们只需要满足这个原则,而不用关心每个原则的底层实现是什么,如不用了解volatile的实现原理通过内存屏障保证可见性
happens before
JMM
满足原子性和可见性(happens before原则之一)
对象锁,类锁(锁的是类的Class对象)
组成部分,重量级锁需要使用ObjectMonitor,类似于AQS
Mark Word
monitorenter - 获取锁相当于调用lock方法
monitorexit - 释放锁相当于调用unlock方法
指令
Monitor
CAS获取锁的线程再次获取锁时只需检查锁标志位为偏向锁并比较当前线程ID是否等于头部记录的线程ID
偏向锁
偏向锁竞争才会释放,偏向锁撤销需等待全局安全点,暂停偏向锁线程若此时持锁线程还活着,则升级为轻量级锁
栈帧生成锁记录LockRecord,存储一份MarkWord的拷贝尝试通过CAS操作将Mark Word指向LockRecord并将Lock Record里的owner指针指向Mark Word更新失败则判断Mark Word是否已经指向栈帧若指向,则代表已经获取了轻量级锁
加锁
若Mark Word仍指向栈帧,通过CAS替换Mark Word,此时可能替换失败未指向和替换失败代表此时锁已经升级为重量级锁需要唤醒锁阻塞队列的下一个等待线程
解锁
轻量级锁
当多次获取轻量级锁失败时,升级为重量级锁进入Monitor的锁阻塞队列,等待持锁节点唤醒
重量级锁
锁升级
实现原理
锁消除
锁粗化
早期依赖于操作系统实现线程间切换需要由用户态转为核心态
synchronized
TIDYING:所有任务都执行完(包含阻塞队列里面任务)当前线程池活动线程为 0,将要调用 terminated 方法;
TERMINATED:终止状态,terminated方法调用完成以后的状态。
状态转换
ctl用来标记线程池状态(高3位),线程个数(低29位)默认是RUNNING状态,线程个数为0
参数
https://www.cnblogs.com/huangjuncong/p/10031525.html
添加任务执行
execute
添加工作线程
addWorker
执行流程
源码解析
线程池使用ThreadLocal导致内存溢出不去调用remove,导致所有的线程一直都会持有对象
ThreadPoolExecutor
原理图
内存泄露问题
ThreadLocal
链表长度除去头结点大于8转为红黑树小于6退化转红黑树的另一个前提元素个数大于64当不满足该条件时只会发生扩容
延迟初始化:put -> resize(初始化/扩容)-> p = tab[i = (n - 1) & hash](算出元素下标)
流程
使元素分布更均匀:高低位异或(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
jdk8 -> 高低位尾插法,不会无限循环
扩容
JDK1.7,在扩容时由于顺序颠倒,可能导致环形链表的产生,导致无限循环
HashMap
sizeCtl:负数代表扩容或初始化-1 代表初始化-n 代表有n-1个线程进行扩容操作0代表还未被初始化初始化后这个值代表下一次初始化的大小
CAS + synchronized
区间划分,协助扩容
addCountCounterCell
1.7 分段锁,二次Hash
CHM
tryAcquire一般实现:CAS修改state状态设置当前持锁线程下次获取是重入acquire:当已被其他线程持有独占模式进入阻塞队列并等待前一个节点唤醒唤醒后设置当前节点为head
tryRelease一般实现:减少重入次数,当减少为0时,清空当前持锁线程release:尝试释放锁成功则唤醒下个节点
void acquire(int arg)void acquireInterruptibly(int arg)boolean release(int arg)
独占
tryAcquireShared一般实现:自选通过CAS减少State值acquireShared:获取失败共享模式进入锁阻塞队列并等待前一个节点唤醒唤醒后设置当前节点为head并进行传递,唤醒下个共享节点
tryReleaseShared一般实现:CAS增加state个数releaseShared:尝试成功唤醒下个节点
void acquireShared(int arg)void acquireSharedInterruptibly(int arg)boolean releaseShared(int arg)
共享
单向链表存储条件队列await 释放锁进入条件队列signal 将条件队列的头部节点转移至AQS队列并唤醒让其尝试获取锁
ConditionObject
AQS
ReentrantLock
高16位:共享锁状态低16位:互斥锁状态
ReentrantReadWriteLock
写时拷贝,读快照
CopyOnWriteArrayList
Lock
CountDownLatch 闭锁
CyclicBarrier 栅栏
Semaphore 信号量
Exchanger 交换器
实现
BlockingQueue
其他
并发编程/异步编程
// TODO
设计模式
集群模式启动https://www.processon.com/view/link/60603dfb1e0853028ab273a5
IO通信模型https://www.processon.com/view/link/6061913c7d9c08555e684073
Leader选举和客户端请求处理https://www.processon.com/view/link/6062e663079129148ce4a71f
Watch机制的存储与转发https://www.processon.com/view/link/607d37597d9c08283dd3b312
Zookeeper
Kafka消息传递https://www.processon.com/view/link/607d37a4079129368891ca82
Cloud Stream的启动与坑https://www.processon.com/view/link/607077a25653bb4d47999d0d
Kafka/Cloud stream
注册Bean定义和注册单例Bean时的顺序问题
若先注册单例,会被保存至手工单例容器中此后注册Bean定义,会将手工单例容器中的Bean移至单例Bean容器中最后会重置Bena定义,将单例Bean容器中的Bean Remove掉此时相当于由容器管理我们Bean的创建,而手动注入的方式是为了自己管理Bean的创建
beanFactory.registerBeanDefinitionbeanFactory.registerSingleton注册顺序问题
SpringCloudStreamBinder启动时消息队列尚未启动会导致启动线程阻塞,即Tomcat无法监听端口
@Async无法解决循环依赖
对于prototype类型的Bean,其销毁方法并不会由Spring管理
出现循环依赖时,不允许在Bean初始化时替换对象,否则各对象之间状态不一致只能在三级缓存放入二级缓存的那一刻替换代理对象
问题记录
学习总结
0 条评论
回复 删除
下一页