1.JVM 方法区里有什么
1) CLass文件信息
a)类型信息
类的全限定名称
类的直接父类的全限定名称
类型标志(该类是类类型还是接口类型)
类的访问描述符(public、private、default、abstract、final、static)
b)类型的常量池
存放该类型所用到的常量的有序集合
c)字段信息
字段修饰符(public、protect、private、default)
字段的类型
字段名称
d)方法信息
方法名
方法的返回类型
方法参数的类型、数目以及顺序
方法修饰符(public、private、protected、static、final、synchronized、native、abstract)
e)类变量(静态变量)
静态变量
f)指向类加载器的引用
g)指向 Class 实例的引用
h)方法表
JVM 对每个加载的非虚拟类的类型信息中都添加了一个方法表
2)运行时常量池
jdk1.8以前在永久代内实现方法区,1.8开始 元空间实现,方法区是一个逻辑概念
2.JDK1.8新特性
速度更快 – 红黑树
代码更少 – Lambda
强大的Stream API – Stream,对容器对象操作的增强,专注于聚合操作
便于并行 – Parallel
最大化减少空指针异常 – Optional
3.常量池技术
4.对象、引用和指针
Person p = new Person();
通过关键字new 调用Person 的构造器,返回person实例,并直接将该Person实例赋给p变量
p是变量,也是引用也是指针,存储在栈中
p指向堆中的某个地址,堆存储真正的对象
5.JVM的几种常量池
1)Class文件常量池;
a)字面量
文本字符串 (public String s = "abc";)
用final修饰的成员变量(public final static int f = 343;)
b)符号引用
类和接口的全限定
字段名称和描述符
方法的名称和描述符
2)运行时常量池;
运行时常量池是方法区的一部分,所以也是全局共享的
类加载时会将类的全限定名获取的二进制流转化为方法区的运行时数据结构,这其中就包括了class文件常量池转化为运行时常量池的过程。
运行时常量池中存储这class类的的符号引用,在类解析的过程中会将【符号引用】转为【直接引用】
3)全局字符串常量池;
a)字符串常量池的特殊性:String类型是一个final对象,他的字面量存在于class文件常量池中,但是运行期行为却与普通常量不同;
JDK 1.7中,字符串常量池和类引用被移动到了Java堆中(与运行时常量池分离),因此不同版本的String行为也有所差异
4)基本类型包装类对象常量池
略
6.栈帧结构
局部变量
操作数栈,栈最典型的一个应用就是用来对表达式求值
动态链接,指向方法区中运行时常量池的引用;方法执行可能需要类的常量
返回地址
6.1栈帧的出栈和入栈
1.栈,线程运行时,需要内存空间;一个栈存多个栈帧,先入后出的顺序。
2.活动栈帧,在栈顶的栈帧;
每次方法调用,栈帧入栈;调用完毕,栈帧出栈
3.栈内存溢出;
1)递归;即栈帧过多
2)栈多大
4.栈属于线程私有,无需回收;内存固定,栈越大则可分配线程越少;
5.出栈/入栈图片可参考
https://blog.csdn.net/qq1104380920/article/details/121199089
7.JDK1.7和1.8的区别
1)移除了永久代(PermGen),替换为元空间(Metaspace)
2)永久代中的 class metadata 转移到了 native memory(本地内存,而不是虚拟机)
3)方法区中的字符串常量池转移到堆中
1.JVM在“new”一个对象时做了什么
类装载方式:隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象
显式装载, 通过class.forname()等方法,显式加载需要的类
1)方法区的常量池中寻找类的符号引用,该类是否加载到内存;没加载,先加载,
2)分配内存,设置对象头,hash码,gc分代年龄,锁的状态
3)init方法字段赋值
2.虚拟机的类加载过程
类加载过程分为 加载、验证、准备、解析、初始化、使用、卸载 7个阶段
验证、准备、解析这三步统一命名为链接
3.类加载器的作用
类加载的过程中,通过全限定名获取Class文件的二进制字节流。
如果同一个类由两个不同的类加载器加载,得到两个实例,对于虚拟机而言,不是同一种类型
4.类加载器有哪些
启动类加载器BootStrap ClassLoader,负责加载<JAVA_HOME>/lib
拓展类加载器ExtendClass Loader,负责加载<JAVA_HOME>/lib/ext下的类
系统类加载器Application ClassLoader,我们写的代码默认就是由它来加载
自定义类加载器
5.双亲委派模型
过程:
加载Hello.class文件,首先AppclassLoader检查是否加载过。如果没有,那么会拿到父加载器,调用父加载器loadClass方法。
父加载器会重复以上的过程,如果没有任何加载器能加载,就会抛出ClassNotFoundException
优势:
1)防止用户重复定义基础类至classpath,导致程序混乱
2)防止内存中存在多份相同的字节码
JVM中的类需要由【加载它的类加载器】和【类本身】来确定它的唯一性。
Object类,无论哪个类加载器来加载它,最后都要委托启动类加载器来加载Object
这就保证了Object在程序的各个类加载器环境中都是同一个类
6.能不能自己写个类叫java.lang.System?
通常不可以,但可以采取另类方法达到这个需求
1.不可以,System类由BootStap加载,就算自己重写,也总是使用Java系统提供的System
7.为什么需要双亲委派模式
问题5中的优势:
8.为何要打破双亲委派模式以及如何打破
1)父类加载器需要委托子类加载器去加载class文件
Driver可以由数据库的服务商提供;
加载DriverManager时,要加载各个实现了Driver接口的实现类,
而DriverManager由bootstrap加载器加载,我们
2)重写 loadClass或者
findClass,父类无法加载时,通过子类重写的findClass方法
9.类加载器,加载顺序
1)findLoadedClass
判断该类型是否已经被加载
2)没有,给父类加载;父类加载器,启动类加载器加载
3)findClass
如果父类加载器和启动类加载器都不能完成加载任务
10.findclass和loadclass
1)自定义一个class文件重写findclass;使用自己的类加载器加载
2)自定义loadclass,需要打破双亲委派机制重写loadclass
1.JVM的垃圾回收哪部分
JVM内存分:堆,方法区,虚拟机栈,本地方法栈,程序计数器
1)程序计数器,栈属于线程私有,会随线程的回收而回收
2)Java堆区内存的分配和回收是动态的,是垃圾收集器需要关注的部分
2.jvm判断对象是否存活的算法
1)引用计数算法
2)可达性分析算法
3.Java中的引用分为哪几类
强引用、软引用、弱引用、虚引用
引用强度依次逐渐减弱
1)Object obj = new Object() 强引用,垃圾收集器永远不会回收
2)软引用场景caffeine,本地缓存
3)弱引用 threadlocal 内部threadlocalMap中的key
4)虚引用,形同虚设,很少使用
4.堆内存划分
1)JDK1.8之前 新生代+老年代+永久代 (1:2)
新生代 = Eden + from + to (8:1:1)
2)JDK1.8开始 新生代+老年代+MetaDataSpace
3)永久代和元空间都是是方法区的一种实现;
类信息几乎是静态的(1.7中字符串常量纳入方法区,导致一系列的性能问题),永久代存在垃圾回收但是很少发生,一旦超过设定值立即抛出异常;
元空间属于本地内存,Native memory,只要本地内存足够就不会出现“java.lang.OutOfMemoryError: PermGen space”
4)为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代
5.GC堆
1)Minor GC ,Major GC,Full GC
Minor GC(Young GC) 是发生在新生代中的垃圾收集动作,所采用的是复制算法。年轻代存活的对象很少,适合复制算法。
Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法
2)GC机制:程序调用System.gc时可以触发;系统自身来决定GC触发的时机
Eden空间占满了, 会触发 minor GC;
当进行Minor GC时,会预测老年代能否承受得住新生代晋升的空间;【当前老年代所剩空间大小和以往Minor GC平均晋升空间大小】;
实际情况还可能出现预测大于,实际小于的情况,这种情况下就会出现【担保失败】的情况,还会引发一次Full GC
5.1.youngGc触发条件-复制算法
1)Eden 区没有足够空间后(不一定是eden区满),触发youngGc
2)youngGc 存活对象超过一定年龄会晋升到老年代
5.2.Full GC触发条件-(标记-清除算法/标记-整理)
1)老年代空间不足,方法去空间不足(1.7)
2)young gc的担保机制
a)老年代给新生代担保
b)复制算法需要担保
c)根据以往young gc存活下对象的平均数据计算
够,直接young gc;不够,full gc腾出空间作担保
3)标记清除算法会出现较多碎片,大对象分配空间不足会引发full gc
4) System.gc()——full gc
6.Young GC
1)何时触发 -Eden区年轻代分配对象失败,即内存不足触发YoungGC
2)finalize()方法,GC过程中,垃圾回收器准备释放对象占用的内存,将首先调用该对象的finalize()方法,并且下一次垃圾回收动作发生时,才真正回收对象占用的内存空间
7.finalize()方法
1)对象在进行可达性分析后发现没有与GC Roots 相连接的引用链,同时子类覆盖了finalize方法,并且该finalize方法并没有被JVM调用过时,finalize方法才会被调用
子类覆盖finalize方法来处理系统资源、执行其他清理或者对象自救
2)GC过程中,垃圾回收器准备释放对象占用的内存,将首先调用该对象的finalize()方法,并且下一次垃圾回收动作发生时,才真正回收对象占用的内存空间
8.可作为GC Roots的对象包括哪几种
(1)[虚拟机]栈(栈帧中的本地变量表)中引用的对象。
(2)方法区中的类[静态属性]引用的对象。
(3)方法区中常量引用的对象。
(4)本地方法栈中JNI(即一般说的Native方法)引用的对象。
(5)正在运行的线程。
9.回收算法
1)标记-清除(标记清除算法标记的是存活对象);老年代CMS
2)复制 ;年轻代 s0->s1
3)标记-整理;清除碎片
4)分代
10.CMS回收的全过程
CMS是一款并发、使用标记-清除算法的gc,牺牲吞吐量换取最短回收时间
1)初始标记
STW ,耗时短,仅标记GC Roots能直接关联到的对象
2)并发标记
不会STW ,耗时长,从初始标记的对象遍历整个对象图,标记可以关联到的对象
3)重新标记
STW,耗时短, 修正并发标记中用户线程导致的标记变动的记录
4)并发清理
不会STW,产生浮动垃圾,并发失败,启用Serial old ,停顿时间更长
11.默认垃圾收集器
JDK1.6/1.7 parallel scanvege 和 parallel old
JDK1.8 Parallel Scavenge Parallel Old
11.1 JVM的垃圾回收器
1.两个收集器间有连线,表明它们可以搭配使用
1) Serial/Serial old
2) Serial/CMS (JDK9废弃)
3) ParNew/Serial Old (JDK9废弃)
4) ParNew/CMS
5) Parallel Scavenge/Serial Old (预计废弃)
6) Parallel Scavenge/Parallel Old
7) G1
2.Serial Old作为CMS出现”Concurrent Mode Failure”失败的后备预案
2.为什么要有很多收集器,一个不够吗?因为Java的使用场景很多,移动端,服务器等。所以就需要针对不同的场景,提供不同的垃圾收集器,提高垃圾收集的性能
12.什么时候触发CMS
1)触发(不同的垃圾收集器的触发gc条件不同)
-XX:CMSInitiatingOccupancyFraction
比如80%,老年代内存使用80%,触发 预留20%装载浮动垃圾
数值太低,触发频繁
数值太高,CMS并发清理时工作线程不足,会报错(Concurrent Mode Failure),临时启用Serial Old
2)Concurrent Mode Failure
CMS 过程中工作线程将对象放入老年代
3)CMS产生的碎片问题
X:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5
CMS在进行5次Full GC(标记清除)之后进行一次标记整理算法
13.标记三次分别标记什么
小课堂:
并发的多个任务之间是互相抢占资源的。
并行的多个任务之间是不互相抢占资源的
1)第一次初始标记,标记出GC Roots能直接关联到的对象
2)第二次并发标记,标记整个对象树,标记可以关联到的对象
3)第三次重新标记,并发标标记期间产生用户线程变化的对象 。此时不需要重新标记所有对象,第二次标记会记录下哪些是新建,哪些是变动的
14.三色标记(标记->被GC Roots扫描到)
1)定义
黑 灰 白
黑 自己和所有属性都标记完成
灰 自己标记完成以及至少一个属性没被标记
2)步骤
第一步GC Roots直接关联的对象置为灰色
第二步遍历灰色对象的所有引用,灰色对象本身置为黑色,引用置为灰色;
重复2,直到没有灰色对象为止
3)在并发标记的过程中,由于工作线程的存在有两类问题
a.漏标 -浮动垃圾
A(黑色) A.B(灰色)
GCRoots 开始遍历B,工作线程开始A.B=null,此时B已经不可达,应该被清除,所以遗漏
b.错标
A(灰色) A.B(白色)
GCRoots 开始遍历A,,工作线程开始C.B建立链接,C(黑色),浮动垃圾下次回收
4)CMS 的解决
漏标的情况都要解决。
发生条件
a)灰色取消了对白色的引用
b)黑色建立了对白色的引用
【原始快照】 打破第一个:当灰色对象指向白色对象的引用被断开时,就将这条引用关系记录下来。当扫描结束后,再以这些灰色对象为根,重新扫描一次。相当于无论引用关系是否删除,都会按照刚开始扫描时那一瞬间的对象图快照来扫描
【增量更新】打破第二个:当黑色指向白色的引用被建立时,就将这个新的引用关系记录下来,等扫描结束后,再以这些记录中的黑色对象为根,重新扫描一次。相当于黑色对象一旦建立了指向白色对象的引用,就会变为灰色对象
CMS采用的方案就是:写屏障+增量更新来实现(这里的写屏障相当于Aop 前置与后置处理)
14.CMS的优点和缺点
开启了「GC并发收集」的先河
优点:
缺点:
1)并发标记,CPU资源占用(Concurrent Mode Failures),Stop-the-World的Full GC就会被触发
2)标记清除算法,容易产生内存碎片。参数配置,标记整理
3)浮动垃圾
4)回收时间长,吞吐量不如Parallel
15.G1回收器原理(简述)
1)JDK9默认
2)内存分区;取消物理分代,逻辑分代;没有完全独立的s区做复制
3)没有大量的内存碎片,增加预期机制(最大停顿时间)
4)新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活的对象拷贝到老年代或者Survivor空间;
老年代,G1收集器通过将对象从一个区域复制到另一个区域,完成了清理工作
5)回收过程和cms类似
a)初始标记
b)并发标记
C)最终标记
d)根据时间来进项价值最大化的回收
mysql基本架构
1.B+树的特点
树的概念:阶(每个节点至多拥有的子节点的数量),深度(层数),
1)B+树是一种数据结构,一种n叉树。每个节点通常有多个孩子。一颗B+树包含根节点,内部节点和叶子节点。
2)B+ 树通通常用于数据库和操作系统的文件系统中,B+ 树元素自底向上插入。
3)父节点的元素都出现在子节点中,且是子节点的最大(最小)元素。
4)叶子节点都有【卫星数据】,即索引元素所指向的数据记录。
5)聚集索引中的叶子节点,叶子节点直接包含卫星数据;在非聚集索引中,叶子节点带有指向卫星数据的指针。
2.磁盘读写原理
硬盘基本知识(磁头、磁道、扇区、柱面),有兴趣可以补充
1)磁盘上的数据文件以数据页的物理结构存储,数据页通过指针组成一个双向列表(有图为证)
2)主存和磁盘之间的数据交换不是以字节为单位的,而是n个扇区为单位,通常是4kb(8个扇区),一个扇区有512字节;扇区(sector)是磁盘的最小存储单位
3)一个扇区的大小是 512 字节,文件系统(例如XFS/EXT4)他的最小单元是块,一个块的大小是 4k,InnoDB 存储引擎也有自己的最小储存单元——页,一个页的大小是 16K
4)每一次IO操作,需要查找该行文件所在柱面号,磁盘号,扇区号,页号,很耗费时间。减少IO,提高性能
5)MySQL要求一个行的定义长度不能超过65535字节
2.1磁盘基本概念
1)扇区(512byte),硬盘的读写以扇区为基本单位
2)磁盘块(4kb),文件系统读写数据的最小单位
3)磁盘页,内存操作的基本单位,
4)InnoDB 页(16kb),页是innodb存储引擎管理数据的最小磁盘单位
磁盘控制器即操作系统(块,n个连续的扇区,4kb)——>磁盘(扇区,512byte)
操作系统(页,最小单位,页的大小通常为磁盘块大小的 2^n 倍)——>内存
3.B+树的优点
1)IO次数少, 磁盘中的数据传输到内存是性能损耗的中重点。
a)所有的数据记录都在叶子节点,中间节点不需要像B+那样存储记录,可以容纳更多的关键字
b) 一个磁盘块是16byte,一个关键字4bytes,记录(或指针)4bytes,5阶B-tree(最多4个关键字),需要两个磁盘块
2)B+树的查询效率更加稳定,每次都需要遍历叶子节点。
3)增删文件(节点)时,效率更高,叶子节点是有序的链表结构存储。
4)有序,范围查询;链表,插入和删除
3.1数据结构
时间复杂度:执行当前算法所消耗的时间
空间复杂度:*空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度*
链表:
插入删除速度快(新增或删除,相关联的两个元素指针变化)
不能随机查找,必须从第一个开始遍历,查找效率低
内存利用率高,不会浪费内存
数组:
随机访问性强,查找速度快
插入和删除效率低(新增或删除时,每个数据都要挪位子)
内存空间要求高,必须有足够的连续内存空间,可能浪费内存

4.相比较B树的缺点
5.一个B+树可以存储多少数据
1)磁盘最小存储单元是扇区,512个字节
文件系统,最小单元是块4k
存储引擎的存储单元是页16k
2)存储引擎的数据页可以存放数据,也可以存放关键字+指针
一条记录1k;主键long,8个字节+指针 6个字节
每个页指针数量=16*1024/14=1170
每个页的记录数量=16/1=16
三层数据量 1170*1170*16=21902400
6.一个B+树如何查询的
1.什么时候开始innodb
1)5.4之前,包括5.4默认引擎MyISAM
InnoDB是Mysql的默认存储引擎
2)区别
myisam是默认表类型不是事物安全的;innodb支持事物
myisam不支持外键;Innodb支持外键
myisam使用非聚集索引;innodb使用聚集索引,索引和数据存在一个文件
3)支持三种索引
B+树索引
全文索引
哈希索引
4)
2.常见名词解释
1)回表
2)索引覆盖
3)最左匹配
a)当遇到范围查询(>、<、between、like)就会停止匹配
select * from t where a=1 and b>1 and c =1; #这样a,b可以用到(a,b,c),c不可以
4)索引下推-针对组合索引
a)
举例:
建立联合索引(name,age)
SELECT * from user where name like '陈%' and age=20,此时只会走索引name,
Mysql5.7之前会忽略age字段,查出多个符合条件的记录,多次回表,age条件由server层过滤
Mysql5.7开始匹配记录时,会算上age字段,减少回表。直接在引擎层做好过滤
b)Mysql5.7开始存在这个概念
c)explain解析结果可以看出Extra的值为Using index condition,说明使用了索引下推
3.非聚合索引的查询——字符串索引
比如字符串索引
建表语句中会选择字符集,比如CHARSET=utf8,字符集会决定字符串间的比较规则
1)字符串索引需要消耗CPU计算,一般比数值索引慢;
2)字符串索引,key关键词比整型大,需要更多的存储空间,索引树层级高,需要更多的IO读取数据
4.哪些场景不走索引
1) 数据类型发生隐式转换
select * from student where last_name = 1;——>select * from student where last_name = '1';
2) where 条件表达式等号左侧使用函数
select * from task_table where DATE_ADD(update_time,INTERVAL 2 DAY) < NOW();
改进——>把函数放到右边
3) like前缀匹配走索引
4)OR 其中一个条件有索引,另一个没有索引
select * from order where customer_id = 123 or status = 'Y';
改进 ——>改进方法使用union
5)where语句中索引列使用了负向查询,可能会导致索引失效
负向查询包括:NOT、!=、<>、!<、!>、NOT IN、NOT LIKE等。
5.mysql中的优化器
RBO,基于规则的优化器,比如有索引,走索引
CBO,基于成本的优化器,计算IO,CPU等消耗
mysql是CBO,基于成本的优化器
1)SHOW TABLE STATUS LIKE 't_fm_metadata_modify_worker';
rows 记录数量;
data_length(possiblekey) 16384;
Data_length = 聚簇索引的页面数量 * 每个页面的大小
2)执行计划中的possiblekey,计算possiblekey和全表扫描的CUP和IO成本
6.如何优化SQL(慢sql优化思路)
1) 将name字符串索引长度改为100
2)对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
3)SELECT子句中避免使用*号,尽量全部大写SQL
4) not in,不等于慎用
6.SQL的执行顺序
- from
- join
- on
- where
- group by `(从此处开始可以使用select中指定的别名)`
- avg、sum、max
- having
- select
- distinct
- order by
7.SQL的执行计划的参数意义
explain查看执行计划。
重要参数:
type(非常重要,可以看到有没有走索引) 访问类型
ALL 扫描全表数据 速度非常慢
index 遍历索引 索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫
range 索引范围查找
index_subquery 在子查询中使用 ref
unique_subquery 在子查询中使用 eq_ref
ref_or_null 对Null进行索引的优化的 ref
fulltext 使用全文索引
ref 使用非唯一索引查找数据 指的是使用普通的索引
eq_ref 在join查询中使用PRIMARY KEYorUNIQUE NOT NULL索引关联。
const 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
possible_keys
可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。
key
显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。
extra 常见的有:
Using index 使用覆盖索引
Using where 使用了用where子句来过滤结果集
Using filesort 使用文件排序,使用非索引列进行排序时出现,非常消耗性能,尽量优化。
Using temporary 使用了临时表 sql优化的目标可以参考阿里开发手册
8.uuid作为主键
1)16个字节,占用空间增加树的层级
2)无序,插入会导致子节点位置变动,甚至上层节点位置变动。随之产生磁盘碎片
1.事务的ACID
原子性automic
一致性consistency(每个事务都要)
隔离性isolation
持久性durability
2.隔离级别
1)
读未提交 (READ_UNCOMMITTED)
读已提交 (READ_COMMITTED)
可重复读 (REPEATABLE_READ)
可串行化 (SERLALIZABLE)
2) mysql默认可重复读
oracle默认读已提交
3)
脏读:一个事务读取了另一个事务尚未提交的数据
幻读:一个事务(同一个read view)在前后两次查询同一范围的时候,后一次查询看到了前一次查询没有看到的行。强调范围查询
重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的.(不管其他事务是否提交都不影响当前事务)
3.MVCC--多版本并发控制
维护数据历史版本,解决并发访问读一致性的问题
- 读和写
1)快照读和读最新
1)行记录中两个隐藏字段:事务id和回滚指针(undo log日志地址)
- trx_id 和roll_pointer
4.锁机制
- 事务并发访问同一数据资源的情况主要就分为读-读、写-写和读-写三种
1)读-读 ,两个事务同时读,不会造成任何影响
2)写-写 加锁,防止脏写
3)读-写,两个事务,一个读一个写,可能造成,脏读,幻读,不可重复读
注:
快照读 普通的select
当前读
select * from table where ? lock in share mode; (加共享锁)
select * from table where ? for update; (加排它锁)
- 锁的粒度
行锁和表锁
- 锁的分类
读锁和写锁也即共享锁和排他锁(还有一种意向锁,加锁前,通过意向锁判断是否可以加锁,避免直接加锁失败)
1)共享锁 (shared Locks) ,s锁可以被多个事务同时持有, select ...... lock in share mode
2)排他锁(Exclusive Locks),x锁在同一时刻只能被一个事务持有
自动 update语句
手动 for update
注:当一个事务已经持有对某行记录的s锁时,另一个事务是无法加x锁的;反之亦然
- InnoDB中的表级锁
AUTO_INCREMENT的实现
1)AUTO-INC锁:insert语句前,先加表级锁--自增锁(AUTO-INC锁)
2)轻量级锁:在插入语句生成AUTO_INCREMENT值时先才获取这个轻量级锁,然后在AUTO_INCREMENT值生成之后就释放轻量级锁
插入语句在执行前可以确定插入多少条记录,则采用轻量级锁的方式对AUTO_INCREMENT修饰的列进行赋值,减少表级别的锁
5.传播特性
定义:当一个事务方法被另一个方法调用时,这个事务方法该如何运行
1)REQUERED(默认)
当前事务存在,方法将会在该事务中运行;否则会启动一个新事务。
2)SUPPORTS
支持当前事务,没有事务,以非事务方式执行
##6.Innodb解决幻读和实现可重复读写
- 幻读——间隙锁和临键锁
幻读MVCC无法解决
读:读快照和读最新
1)读最新
update,insert,delete
select * from table where ? lock in share mode;
select * from table where ? for update;
2)读快照
举例:
A事务读,B事务insert;
A事务update (无条件更新),此时会把B事务提交的数据更新掉,也就意味着读最新;
出现幻读
3)临键锁(Next-Key Locks)
mysql默认行锁类型就是临键锁(Next-Key Locks)。当使用唯一性索引,等值查询匹配到一条记录的时候,临键锁(Next-Key Locks)会退化成记录锁;没有匹配到任何记录的时候,退化成间隙锁;
间隙锁(Gap Locks)和临键锁(Next-Key Locks)都是用来解决幻读问题的,在已提交读(READ COMMITTED)隔离级别下,间隙锁(Gap Locks)和临键锁(Next-Key Locks)都会失效
- 可重复读——MVCC
undo日志记录下的版本链和ReadView
1. 哪些日志
错误日志
查询日志
慢查询日志
binlog日志,server生成的日志
事务日志(redo log和undo log,innodb生成的日志)
a) redo log和undo log,innodb生成的日志
b)redo log记录的是"物理级别"上的页修改操作;如比如页号xxx、偏移量yyy写入了'zzz'数据
undo log 记录的是逻辑操作日志;比如对某一行数据进行了INSERT语句操作,那么 undo log就记录一条与之相反的DELETE操作
2.WAL 预写式日志——write-ahead logging
write-ahead logging,先写日志,再写磁盘。mysql 通过 redo、undo日志实现WAL
1)
2.redo log(重做日志)
1)redo log保证事务的持久性
a)保证事物的持久性需要落盘
b)保证速度引入内存的"缓冲池"
c)保证宕机会导致缓冲池中的数据不丢失
d)引入了redo log,只记录事务对数据页做了哪些修改(Elasticsearch中,叫translog)
2)redo log 分为两部分
a)内存中的日志换种 ,redo log buffer
b)磁盘上的日志文件,redo log file
3)redo log记录过程
redo log,一组4个文件,每个文件的大小是1GB。0 1(write pos)----(checkpoint) 2 3
a)固定大小,循环写入(环状结构)
b)write pos 当前记录的位置,一边写一边后移
c)checkpoint是当前擦除的位置,擦除前数据落盘
d)write pos 和check point之间用来写新记录。
e)write pos 追上check point 需要停下来
3.update语句的内部流程
update table xx set name='xxx' where id=2;
1)执行器先找 ID=2的记录,内存中有直接返回,没有从磁盘中获取.
注:旧值写入到 Undo log 日志中,用于回滚数据!!
2) 执行器将 原值设定为新值
3)新行更新到内存中,写入redolog 处于prepare阶段(数据记入内存中,查询就是最新的值,不必从磁盘中取)
4)写入binlog ,提交事务处于commit状态
5)prepare 和 commit,这就是"两阶段提交",保证redo log和bin log的一致性。crash后用redo log恢复数据(当前的库)和bin log恢复数据需要一样(备库)。
4.binlog
1)定义
binlog 记录数据库写入性操作,以二进制的形式 保存在磁盘中。
binlog 是逻辑日志,sql语句。追加的方式进行写入的,可以设置maxsize,到达最大值后,然后生成新的文件。
2)使用场景:主从复制和数据恢复
3)刷盘时机:
0 系统自行判断如何写入磁盘
1 每次commit时,写入磁盘(默认)
N 每N个事务,写入磁盘
4)日志格式
a)STATMENT,SQL语句复制 不用记录每一行的记录,减少io和日志量。主从之间sysdate存在差异
b)ROW,记录哪些记录被修改。alter table 会导致日志暴涨。
c)MIXED 混合模式
关于redis的缓存穿透&击穿&雪崩&热key
- redis的缓存穿透
定义:大量请求,redis没有命中,压力堆积到持久层-数据库
解决:
1)过滤器,合法性校验,字段长度
2)缓存空值,数据库查不到直接缓存一个空值,默认值,失效时间。
简单直接,但是占用缓存空间
3)使用布隆过滤器
1.布隆过滤器
布隆算法,判断一个元素是否存在于一个集合当中
1)定义
a)位数组+hash函数,假设位数组的长度是10000,hash函数的个数是3
b)位数组初始化状态,每个位都是0。将集合中的数据通过hash函数映射到数组上,然后数组的该位置变为1.
c)判断某个元素是否在集合中,通过hash函数映射到数组上,如果3个位置有一个不为1,则不存在。3个都为1,则可能存在。(hash冲突)
d)不支持删除,一个位置可能被多个元素占用
2)降低误判率
a)预计元素数量m,数组长度n,错误率f,hash函数个数k。前两个可以计算出后两个(存在已被证明的公式)
https://krisives.github.io/bloom-calculator/
b)多个hash函数和位数组的长度
3)空间占用
4)实战
guava包中有对Bloom Filter,加上redis组合使用可作为分布式布隆过滤器
- redis的缓存击穿
定义:缓存过期的一瞬间,大量用户访问同一个key,压力堆积到持久层
解决:
1)热key,不失效
2)使用互斥锁(mutex key)---其中一个请求获取数据
手写伪代码
public String getUser(String key){
String value = getRedis(key)
if(value==null){
if("OK".equals(jedisTemplate.set(key+":NX",1,"NX","px",60*1000))){
String value = getDB()
redis.set()
rern vale;
}else{
sleep(60)
return getRedis(key);
}
}
return value
}
- redis的缓存雪崩
定义:缓存服务器宕机或者大量热key同一时间过期,所有请求,压力堆积到持久层
解决:
1)分散失效时间
2)互斥锁
3)二级缓存
互斥锁缺陷:redis本身出现故障无法恢复,使用二级缓存
-
缓存守卫
-
缓存与数据库双写一致
1)最终一致性,定时刷新
2)强一致性
a) 删除,不是更新
写多次,读一次;Lazy 计算,读的时候计算一次
b)先删除缓存,在更新数据库
若删除缓存后,数据库更新失败,查询缓存时 可以会回源
- 大key和热key
1.大key
问题:大key超时,阻塞服务
假设一个 key 为 1MB,每秒访问量为 1000,那么每秒产生 1000MB 的流量
解决:
1).拆分
a)每次都是整存整取
b)每次只存取部分数据
hash结构属性的拆分,歌词,详情,标签,物料
2)不可拆分----管道,布隆过滤器
2.热key
1).二级缓存
2).代理层面——>统计热key,加上随机数
3).提前得知热点key
- redis一个实例能存多少个key?
官方说单例能处理key:2.5亿个
一个key或是value大小最大是512M
9000w,5G
1.可重入锁的使用场景
1)提升了加锁行为的封装性,简化并发代码的开发
2)如果设置自旋,可能会产生死锁;递归调用,产生死锁
2.redission的分布式锁
1)自带心跳机制,自动续时
2)lua脚本原子性
3)hash结构
a)key redisson
b)filed 业务key+线程id
c) value 加锁次数 hincrby 1
D)不存在塞值,存在+1
4)脚本语言解释
if(exists keys[1]==0 1.不存在key锁) then
hset keys[1] argv[2] 1 赋值 key field value-->1.1尝试获取锁
pexpire keys[1] argv[1] 过期 expire key time-->1.2设置锁过期时间
return 空;
end
if(hexists keys[1] argv[2]==1 存在Key name 的锁--》2.当前线程已获取锁) then
hincrby keys[1] argv[2] 1 -->2.1原子计数器+1 锁重入!!!
pexpire keys[1] argv[1] 过期-->2.2重置锁过期时间
return 空;
end
return pttl keys[1]-->3.返回剩余过期时间
1.redis的集群模式
1)主从,哨兵,集群
2)集群中的节点,分为主节点和从节点
2.主从复制的原理
1)过程
主从刚连接时,进行全量同步
a)从服务器向主服务器发送SYN命令,
b)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令。
c)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令
d)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照
e)!!!主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令
全同步结束后,进行增量同步
增量同步——每执行一个写命令就会向从服务器发送相同的写命令
2)从库的数据不会自动过期
3.哨兵-2.8版本开始
1)哨兵的作用是监控redis主,从库是否正常运行。某个节点作为哨兵,监控主从节点
2)心跳机制
心跳-每10秒发送PING PONG,超过一定时间认为下线
一个实例认为Master下线,主观下线;多个实例(具体数据看配置)认为下线则客观下线。
3)master客观下线后投票裁决-Sentinel领导者选举
sentinel确认主节点主观下线,会向其他sentinel发送消息,让自己成为领导者。
其他sentinel未同意过其他节点,则同意,否则是拒绝。(单选)
该Sentinel节点发现票数过半,则成为领导者。
领导者进行故障恢复
4)自动故障迁移
a)优先级最高
b)复制偏移量最大
c)ip最小
setinel从节点执行slaveof no one,该节点成为主节点
5)每个哨兵都会向主,从,其他哨兵发送
4.集群
基于p2p结构的HashSlot
1)16384个hash槽位,CRC16算法产生的hash值有16个bit,刚好2的6次方等于16384
2)每个节点均匀分配这个16384个槽位,每次写入key,通过crc16算法算出属于哪个槽位
3)扩容,每个节点迁移一部分槽位到新的节点,相应的数据也一起迁移
去中心化访问
4)映射表和内部转发,通过**Gossip协议**来实现
5)redis cluster 重定向机制
a)MOVED 重定向——槽位迁移完成的报错
客户端请求某节点数据时,计算出的hash槽不在该节点上。返回moved 槽位 ip
客户端会一直重复请求指定节点
b)ASK 重定向——槽位迁移未完成的报错
当槽位slot1 正从实例2,迁移至实例3,且key1和key2,已经迁移,key3和key4未迁移
5.一致性hash
解决分布式系统扩容或者缩容后数据映射失效的问题;
nginx和memcached都采用了一致性Hash来作为集群负载均衡的方案;
1)环状hash空间,替代线性hash空间
2)映射两次到同一个环上
ip映射;key映射,沿着顺时针方向找到环上的第一个节点
3)增加和删除节点只有少部分key失效
4)数据倾斜,某个节点失效。所有数据由下一个节点接管,可能发生雪崩
引入虚拟节点,一个节点对应3个虚拟节点,数据分布更加均匀
5)历史Hash环保留一段时间,保证节点扩容过程中的数据可查找
6)缩容时各节点压力增大,需要熔断机制,防止连锁反应。
弊端:
1)分布式缓存需要路由层做负载均衡,存在单点故障的风险
2)hash环上的节点过多,更新频繁可能会出现性能问题
6.redis集群模式下如何使用管道pipline
1)redis 3.0开始支持集群模式,但是集群模式下不能使用管道。所有的操作只能在同一个节点上进行
比如mget会出现直接报错的问题。key不在指定的节点上
注:减少IO;非原子性;mget是
2)如何解决
redission;
客户端手动实现;
3)
已存在:
JedisClusterInfoCache中
Map<String, JedisPool> nodes
ip->redis连接
Map<Integer, JedisPool> slots
slot->redis连接
手写:
JedisClusterCRC16.getSlot(key)->slot
相同槽位的key,使用同一个jedis.pipeline
合并pipeline所有的response
1.redis的基本架构
网络请求模块+数据操作+索引模块+存储模块+高可用集群支撑模块
1)redis的网络IO和键值对的读取是单线程的。即网络请求+数据操作模块是单线程的,其他模块是多线程的。
2.为什么不用多线程
1)redis 操作基于内存, CPU不是性能瓶颈
2)多线程可以提升IO利用率,但是存在安全问题,且上下文切换存在性能损耗
3)单线程使用多路复用 I/O技术也能提升Redis的I/O利用率
还是要记住:Redis并不是完全单线程的,只是有关键的网络IO和键值对读写是由一个线程完成的。
4)6.0之后网络io模块采用多线程。利用多个cpu,并行将网卡buffer中的io数据搬运到redis进程中。内核态向用户态切换,降低网卡被打满的可能性
3.IO多路复用
1)多个进程的io可以放到一个管道中处理,管道统一和内核交互。减少用户态与内核态的交互
2)一个线程管理多个Socket
4.为什么redis这么快
1)基于内存
2)数据结构简单。哈希表,跳表性能很高
3)单线程,IO多路复用
5.Redis 6.0 引入多线程
1)网络请求过程采用了多线程,而数据的读写操作,仍然是单线程处理的
2)提升QPS,集群模式成本较高
3)不存在线程安全问题
6.1亿个key中有10w个key以固定前缀开头,如何找出来。
1)keys * 不行,阻塞
2)scan 不阻塞,存在一定概率的重复。客户端去重
5.Redis做异步队列
1)list结构作为队列
2)rpush生产消息,lpop消费无消息时需要sleep重试
3)blpop,无需等待,阻塞直到有消息
4)pub/sub主题订阅者模式,一个生产者,多个消费者。缺点是在消费者下线的情况下,生产的消息会丢失
5)sortedset(有序集合)实现延迟队列,时间戳作为分数。zadd生产,zrangebyscore获取N秒前的数据
6.redis的事务
1)类似于数据库的命令
2)没有回滚。命令错误,所有命令都不执行;运行失败,其他命令正常执行
3)原子性可以通过lua脚本实现
7.IO模型
1.redis的持久化机制
把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失
Redis 提供两种持久化机制 RDB(默认) 和 AOF 机制
1)RDB-定时快照 将内存快照保存到dump.rdb文件中
触发机制:
a)手动触发
save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞
bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子 进程负责,完成后自动结束
b)自动触发
使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改 时,自动触发bgsave;
默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则 自动执行bgsave;
当子进程写入完成所有的数据后会用该临时文件替换旧的RDB文件;
优点:RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据 快照,适合全量复制,或拷贝至远程服务器中,用于灾难恢复;
加载RDB恢复数据远远快于AOF的方式;
缺点: RDB方式数据没办法做到实时持久化/秒级持久化,如果宕机,会有部分数据丢失
2)AOF -将每一条写日志记如独立日志(appendonly.aof)
AOF的主要作用 是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式
触发机制(开启):appendonly yes,默认不开启
a)命令写入 (append):命令写入缓冲区,单线程,保证性能
b)文件同步(sync):Redis有多种缓冲区同步硬盘的策略
c)默认刷盘机制,每秒写入一次;(最多丢1s数据)
d)文件重写(rewrite):AOF文件越来越大,需要定期对AOF文件进行重写
手动触发:直接调用bgrewriteaof命令
自动触发:auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默
认 为64MB;
auto-aof-rewrite-percentage:代表当前AOF文件空间 (aof_current_size)和上一次重写后AOF文件空间的比值
e)重启加载 (load)
3)redis4.0后,支持两种持久化方式同时开启。
a)默认开启,可以在redis.conf中通过通过配置`aof-use-rdb-preamble` 开启
b)**RDB文件和AOF日志文件存在一起,aof文件内容格式是[RDB file][AOF tail]**
c)aof重写时,内存中的数据转成rdb格式,然后aof则是增量数据;数据恢复先rdb,然后aof;效率和数据的完整性都得到保证
2.主从复制
1)主从刚连接时,进行全量同步
a)从服务器向主服务器发送SYN命令,
b)主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令。
c)主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令
d)从服务器收到快照文件后丢弃所有旧数据,载入收到的快照
e)!!!主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令
全同步结束后,进行增量同步
增量同步——每执行一个写命令就会向从服务器发送相同的写命令
3.RDB是全量复制一份内存吗?
不是。
copywrite(写时复制的)fork出来的子进程和父进程共享一套内存,如果此时写如新的数据,会将写的那部分数据拷贝出新的
而RDB的数据不变
1.淘汰算法
LRU,最近最少使用。按使用时间排序,淘汰最旧的数据
LFU,最近不经常使用。按使用频次排序,淘汰使用最少的数据。
FIFO,先进先出。(LIFO ,后进先出。)
W-TinyLFU----caffenie
1)Count–Min Sketch,降低频率字段带来的内存消耗
相当于一个4维数组,每个key需要hash四次,取最小的数字。。
2)pk机制让新上的热点数据缓存
访问队列分为三段,WindowDeque ,ProtectedDeque ,ProbationDeque
a)新访问数据进入WindowDeque,满了之后进入ProbationDeque
b)ProbationDeque中的key再次访问,直接晋升到ProtectedDeque
c)当ProtectedDeque满了,会降级到ProbationDeque
d)ProbationDeque队列满了,首尾对比,大胜小汰。
2.softValues和weakValues
软引用和弱引用
软引用,GC时内存不足时回收softValues
弱引用,GC时必然回收weakValues,不管是否内存不足
1.淘汰算法
FIFO,先进先出。(LIFO ,后进先出。)
LRU,最近最少使用。按使用时间排序,淘汰最旧的数据
缺点:大批量冷门数据进来冲掉历史热门数据
LFU,最近不经常使用。按使用频次排序,淘汰使用最少的数据。
缺点:a)记录使用次数,增加空间成本
b)历史数据次数一直累加,无法淘汰
统计频率算法
1)hashMap ,内存消耗 ;hash冲突会导致查询性能的下降
2)不精确计数,count-min sketch
a)不存储key,只计数
b)降低hash冲突,使用多维(二维)数组和多个hash
c)caffenie 使用的是一维数组
long 分成16份,每份4bit。
2的4次方是16,每份最大是15。
超过15,reset,除以2
W-TinyLFU----caffenie
1)Count–Min Sketch,降低频率字段带来的内存消耗
a)long分成16份,每份4bit
b)相当于一个4维数组,每个key需要hash四次,取最小的数字
多个hash函数取最小,缓解了hah冲突的计数过大的问题
c)如果超过15,reset,除以2
2)pk机制让新上的热点数据缓存
LRU+分段LRU
访问队列分为三段,WindowDeque ,ProtectedDeque ,ProbationDeque
a)新访问数据进入WindowDeque(每个key积累计数),满了之后进入ProbationDeque
b)ProbationDeque中的key再次访问,直接晋升到ProtectedDeque
c)当ProtectedDeque满了,会降级到ProbationDeque
d)ProbationDeque队列满了,首尾对比,大胜小汰。
2.softValues和weakValues
软引用和弱引用
软引用,GC时内存不足时回收softValues
弱引用,GC时必然回收weakValues,不管是否内存不足
3.方法参数
1)失效
expireAfterWrite 写入后多久失效
expireAfterAccess 最后一次访问多久失效
expireAfter 自定义失效策略
2)
refreshAfterWrite
4.guava回源
1)expire
同一个key单线程回源,其他线程返回阻塞
refreshAfterWrite,其他线程返回旧数据
2)多个key同时失效,最多4个(类似concurrentHashmap的分段锁),并发度是4。可配置
1.什么是SPI
Services provider interface
将服务接口与服务实现分离,通过本地的注册发现获取到具体的实现类,轻松可插拔的设计方式
2.为什么说SPI打破了双亲委派机制
1)举例
Class.forName("com.mysql.jdbc.Driver")
Driver 类中有静态方法区,从jdbc4.0之后无需这个操作,spi机制会自动找到相关的驱动实现,DriverManager静态方法区执行加载
2)归纳
SPI 的调方和接口定义方很可能都在 Java 的核心类库之中(java.sql.Driver接口,需要BootStrap),实现类交由开发者实现(com.mysql.jdbc.Driver)
然而实现类并不会被启动类加载器(bootstrapclassloader)所加载,基于双亲委派的可见性原则,SPI 调用方无法拿到实现类.
DriverManager由启动类加载器(bootstrapclassloader)加载,而.Driver接口的实现系统类加载器(appclassloader)加载
****DriverManager.java
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取当前线程中的上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
***AppClassLoader.java
public Launcher() {
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
// 上下文类加载器
Thread.currentThread().setContextClassLoader(this.loader);
...
}
1.全局字符串常量
1)String str=new String("abc")
首先在堆中(不是常量池)创建一个指定的对象,并让str引用指向该对象
在字符串常量池中查看,是否存在内容为"abc"字符串对象
若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来
若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来
可能创建一个对象,也可能创建两个对象
2)jdk1.7以前【字符串常量池】在方法区中 ;1.7开始在存放在堆中
2.String.intern()
1)String str=new String("abc")
jdk1.6 将字符串放入字符串常量池中
如果串池中有,不会放入。返回已有串池中的对象地址
如果没有,会把此对象复制一份放入串池,返回对象地址;
jdk1.7 将字符串放入字符串常量池中
如果串池中有,不会放入。返回已有串池中的对象地址
如果没有,会把此对象的引用地址复制一份放入串池,返回引用地址;
2) String str1 = "abc"; String str2 = "ab" + "c"; str1==str2是true吗?
是。因为String str2 = "ab" + "c"会查找常量池中时候存在内容为"abc"字符串对象,如存在则直接让str2引用该对象,显然String str1 = "abc"的时候,上面说了,会在常量池中创建"abc"对象,所以str1引用该对象,str2也引用该对象,所以str1==str2
String str1 = "abc"; String str2 = "ab"; String str3 = str2 + "c"; str1==str3是false吗?
是。因为String str3 = str2 + "c"涉及到变量(不全是常量)的相加,所以会生成新的对象,其内部实现是先new一个StringBuilder,然后 append(str2),append("c");然后让str3引用toString()返回的对象
3) String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2);
String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
jdk1.6 false,false
jdk1.7 false,true
1.hashMap的数据结构
1)jdk1.7数组+链表 ;数组默认16,加载因子0.75。
加载因子太大容易产生hash冲突,太小扩容太频繁
jdk1.8数组+链表+红黑树;数组长度到达64,链表长度8,转红黑树;链表长度6时,红黑树退化
2)entry结构,key,value,hash,next
3)链表的时间复杂度为 O(N),红黑树O(lgN),红黑树占空间
4)static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
运算快;减少碰撞
5)2的幂次方,
resize时原本的位置不变;散列均匀
2.hashMap的线程安全问题
3.hashMap中转红黑树的条件为什么是数组长度到达64,链表长度8
1)红黑树的特性
2)1.7HashMap链表会形成死循环;采用的头插法,链表形成环状
3)1.8采用尾部插入法
4)
//添加元素方法 -> 添加新节点方法 -> 扩容方法 -> 把原数组元素重新分配到新数组中
put() --> addEntry() --> resize() --> transfer()
5)这样当获取一个不存在的 key 时,计算出的 index 正好是环形链表的下标就会出现死循环
4.jdk8中对HashMap做了哪些改变?
1)在java 1.8中,如果链表的长度超过了8,那么链表将转换为红黑树。(桶的数量必须大于64,小于64的时候只会扩容)
2)发生hash碰撞时,java 1.7 会在链表的头部插入,而java 1.8会在链表的尾部插入
3)在java 1.8中,Entry被Node替代(换了一个马甲。
5.HashMap 和 HashTable 有什么区别
1)HashMap 是线程不安全的,HashTable 是线程安全的;
2)由于线程安全,所以 HashTable 的效率比不上 HashMap;
3)HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而 HashTable不允许;
4)HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1;
5)HashMap 需要重新计算 hash 值,而 HashTable 直接使用对象的 hashCode
6.ConcurrentHashMap
1)1.7;数组+segment+分段式锁
2)1.8;数组+链表+红黑树,CAS
3)get方法是弱一致的;
迭代器已经遍历过的数据发生变化,不会抛出异常;未遍历过的数据发生变化,抛出异常;
7.排序
1)Collections.sort();对实现Comparable接口的对象进行排序
2)Comparator 和 Comparable
8.hashcode和equals
1)继承于Object,equals是==;hashcode是对象的地址
2)重写equals一般也同时重写hashcode
a)保证equals方法和hashcode结果一直
b)保证效率。先对比hashCode
c)内容与地址结果一致性
1.全文检索场景
1)正向索引
select * from xx where a like "%你在哪%";全表扫描
结构化数据,用数据库存储,sql查询
id->记录
2)倒排索引
记录->id
2.倒排索引结构
1)Term (单词):文本分析后得出的单词
2)Term Dictionary(单词字典):Term 的集合
3)Term Index(单词索引):快速定位到单词字典
4)Posting List(倒排列表):单词出现的文档,在文档中的位置,出现的次数等
单词
3.联合索引查询
1)每个字段进行倒排索引查询
2)查询后结果取交集
a)跳表合并策略,Posting List 倒排表取交集
b)Bitset 合并策略(Roaring Bitmap)
将一部分结果集缓存下来,以便重复利用
3)mysql不支持结果合并,而是只能通过一个条件查询,而后在内存过滤
3.跳表
1)支持二分发查找的链表就是跳表
2)链表建立多级索引
4.顺带下redis数据结构
1)hash
ziplist(压缩列表)和hashtable
a)所有key和value小于64个字节
b)hash对象的整个键值对的个数小于512个
a,b满足一个使用ziplist
2)sorted set
ziplist和skiplist
a)有序集合保存的元素数量小于128个
b)有序集合保存的所有元素的长度小于64字节
两个同时满足ziplist
5.ES的三种缓存
1)query cache: 也叫filter cache查询中包含的过滤器执行结果进行缓存,term, range
2)request cache:结果集
3)field data:doc_values作用一样,都是让我们在inverted index倒排索引的基础上做aggregation统计、sort排序
5.ES逆向索引的特点
1)逆向索引不可修改,无需加锁
2)加载之内存中,几乎没有磁盘io操作
6.ES逆向索引的新增/修改/删除
1)lucence将大的索引,拆分成若干个小段(segment),每个segment也是一个逆向索引
2)commit point文件,记录当前可用segment,每个可以用的segment返回搜索记录,汇总后返回给用户
3)新增文档会被放入内存,然后到一定数量或者到时间
4)每个commit point文件会维护一个.del文件,文件内部记录已被删除的文档记录;这些删除的记录会被检索出来,最后返回时会被过滤;
更新和删除采用类似的方式,首先文档在原segment的.del文件中显示被删除,新的文档会被放入新的segment
7.ES的refresh
1)ES的记录需要落盘持久化,避免频繁io操作,先落入文件缓存;
2)数据落入内存buffer,同时记录translog;
refresh,然后默认一秒同步到os cache,也就是计入segment,此时可以检索;
一定时间,或者translog数量足够大,segment落盘之后,清空对应translog。
3)每个segment占据独立的内存/cpu/文件句柄,性能耗费严重
ES定期合并segment,真正删除文件的过程
1.架构组成
2.集群说明
1)所有节点具有相同的cluster.name,分担数据和负载压力
2)master,第一个启动的节点是master。负责集群管理,不涉及文档级别的增,删,改
3)请求可以发送到任意节点,每个节点知道所有数据存储的位置,可以将数据聚合后返回客户端
3.ES节点类型
1)master;node.master = true,默认node.master = true node.data = true;既是数据节点,又是候选节点
2)数据节点;node.data=true
3)协调节点(客户端节点);每一个节点都隐式的是一个协调节点
建议:主节点+数据节点两属性为一身;专用协调节点(客户端节点)
4.ES路由
1)没有指定路由,查询时则遍历所有的shard
2)写时,默认根据id路由
3)shard = hash(routing) % number_of_primary_shards
5.ES集群分片策略
1)通常情况下,分片个数=节点数,副本=1;3台服务器,正常3000w
2)数据搜索——默认5个主分片,1个副本;
a)一次搜索通过5个分片共同完成,可能是主分片可能是副本
b)接收到客户端请求的节点叫做协调节点;指定或者某种随即策略
c)广播所有的shard节点,所有结果在协调节点中合并排序返回客户端
d)搜索分类:query then fetch 查询两次分片;query and fetch 查询一次分片
3)索引创建后,运行中的集群(需要停机),需要无法修改分片。重新创建索引,reindex(无需停机)
4)副本可以随时修改
5)number_of_shards 针对具体的索引而言,每个索引单独配置
6)优化策略
a)读大于写,多副本;
b)写大于读,一个节点一个shard,副本一个
c)写时,默认半数节点同步即返回
5.1分片
1)通常主分片=数据节点数量;主分片数量决定数据量
2)单个分片数据大小20G-50G。按照2kb算1000-2500w条左右
3)路由;shard = hash(routing) % number_of_primary_shards
routing,默认id,可自定义;
主分片数量变化,路由失效;除非重建索引
4)副本,防止节点宕机,数据丢失;
5)当ES集群扩容或者缩容时,分片会自动在节点中迁移(rebalacne)
6.高可用和容错
1)主节点,3台以上的节点成为主节点。只负责维护集群的整体状态
2)高可用,任何一个节点宕机,仍然保留全部数据
a)两台主机会把挂掉主机的主分片的副本提升为主分片,集群状态为yellow
b)一段时间后,挂掉的主机未重新启动,集群会生成缺失的副本,集群状态变成green
c)如果挂掉的重启,数据取完整度更高的那个,重新rebalance
3)脑裂容错
a)网络问题,无响应;discovery.zen.ping_timeout
b)主节点负载过高,无响应;控制选举行为的最小数量,master候选节点数量/2+1,过半数
c)gc频繁,可能是即做master,又当数据节点
7.Lucene和ES
1)Lucene全文检索引擎单机版,ES分布式
2)搜索过程中的shard搜索属于Lucene,聚合排序在ES中
8.ES的准实时
1)es写入shard ,然后同步replica shard
2)es先写入内存buffer,然后默认一秒同步到os cache
1.基本架构
1)10层
服务接口层(Service):该层是与实际业务逻辑相关的,根据服务提供方和服务消费方的业务设计对应的接口和实现。
配置层(Config):对外配置接口,以ServiceConfig和ReferenceConfig为中心,可以直接new配置类,也可以通过spring解析配置生成配置类。
服务代理层(Proxy):服务接口透明代理,生成服务的客户端Stub和服务器端Skeleton,以ServiceProxy为中心,扩展接口为ProxyFactory。
服务注册层(Registry):封装服务地址的注册与发现,以服务URL为中心,扩展接口为RegistryFactory、Registry和RegistryService。可能没有服务注册中心,此时服务提供方直接暴露服务。
集群层(Cluster):封装多个提供者的路由及负载均衡,并桥接注册中心,以Invoker为中心,扩展接口为Cluster、Directory、Router和LoadBalance。将多个服务提供方组合为一个服务提供方,实现对服务消费方来透明,只需要与一个服务提供方进行交互。
监控层(Monitor):RPC调用次数和调用时间监控,以Statistics为中心,扩展接口为MonitorFactory、Monitor和MonitorService。
远程调用层(Protocol):封将RPC调用,以Invocation和Result为中心,扩展接口为Protocol、Invoker和Exporter。Protocol是服务域,它是Invoker暴露和引用的主功能入口,它负责Invoker的生命周期管理。Invoker是实体域,它是Dubbo的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起invoke调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。
信息交换层(Exchange):封装请求响应模式,同步转异步,以Request和Response为中心,扩展接口为Exchanger、ExchangeChannel、ExchangeClient和ExchangeServer。
网络传输层(Transport):抽象mina和netty为统一接口,以Message为中心,扩展接口为Channel、Transporter、Client、Server和Codec。
数据序列化层(Serialize):可复用的一些工具,扩展接口为Serialization、 ObjectInput、ObjectOutput和ThreadPool。
2)3层
业务逻辑层+RPC+网络协议和数据转换
业务逻辑之外,其他的几层都是 SPI 机制
3)节点角色
a)provider 服务提供方
b)consumer 服务消费方
c)register 注册中心
d)monitor 监控服务调用时间和次数
e)container 服务运行容器
2.路由&负载均衡
3.容错
1)失败默认重试
2)
4.网络协议
5. 网络通信
1)Reactor 模式实现IO
2)rpc框架底层使用的netty作为通信组件
3)
6.动态代理
1)
默认JavassistProxyFactory,直接操作字节码,替代反射;不能被修改;性能更好
JdkProxyFactory
2)封装远程调用的细节,跟调用本地接口一样
3)负载均衡策略
4)失败+降级+重试
7. 超时机制
1)针对消费端,超时直接返回超时异常,服务端继续执行
针对服务端,超时返回异常
2)方法级别>接口级别>全局
3)超时会重试,默认1
4)NIO模式,同步非阻塞,消费端ResponseFuture轮询返回结果;避免死循环引入超时
1.bean的生命周期
1)创建
2)调用
3)销毁
2.创建过程
1)BD(bean的原始信息),在beanfactory中
扩展点
a)beanFactoryPostProcessor,增强器;修改属性
b)BeanDefinitionRegistryPostProcessor;对整个BD进行替换
2)反射进行实例化(createBeanInstance)
a)在堆中分配空间,属性都是默认值
3)初始化
a)填充属性 populatedBean
b)调用ware接口,注入到容器内;applicationContext
c) before
beanpostprocessor,对bean进行扩展工作——>AOP(动态代理)
中间---- 初始化方法的调用;init-method
after
4)调用
3.bean的生命周期
1)按使用者给对象分类
a)容器对象
applicationContext;实现aware接口(ApplicationContextAware)
b)自定义对象
2)调用
3)销毁
4.扩展——spring MVC
1)与tomcat整合
tomcat——>spring——>spring mvc
a)tomcat 加载web.xml文件,监听器——>启动spring框架(加载spring配置文件)
b) spring mvc 容器,初始化servlet ,init(创建 springmvc的容器)-service-destory
2)ContextLoaderListener实现接口ServletContextListener
a)contextInitialized——spring容器
b)contextDestroyed
5.AOP 面向切面,IOC
1)容器获取初始化bean
xml方式(IO读取);注解方式;
最终是map,k-v格式的数据
2)生态和扩展性
3)将xml,properties,json等文件转换成BDR(beanDefinitionReader,一种接口规范)
4)beanFactory相当于容器
6.spring的作用域
1)单例
2)多例
3)request
4)session
5)全局session
7.factoryBean和beanFactory
1)beanFactory是一个factory,是IOC容器的顶层接口;
2)factoryBean,特殊的bean。复杂bean的创建,隐去其过程;
3)都是接口
1.给bean的属性赋值的方式
构造器
set
2.spring中bean的注入方式
构造器
set
实例工厂
静态工厂
3.三级缓存
1) 单例,完整bean,直接可用
/** 1级缓存 Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
2)提前暴露的bean
/** 2级缓存 Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
3)对应工厂对象
/** 3级缓存 Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
4)二级缓存——>区分完整和不完整的bean
三级缓存——>解决代理问题
4.循环依赖解决过程
A和B循环依赖(实例化+初始化+填充属性+代理对象)
方法调用 getBean doGetBean createBean doCreateBean createBeanInstance populateBean
1)二级缓存(区分成品与半成品)
A实例化后,填充属性B;A暂停填入二级缓存(半成品),缓存中的bean可提前暴露,供其他bean依赖;
B创建初始化中,需要依赖A,直接从二级缓存中获取使用
3)三级缓存(lambda表达式)
实例化A完成,放入三级缓存;
初始化A,属性赋值B,缓存中找不到B,创建B;
实例化B完成,放入三级缓存;
初始化B,属性赋值A,三级缓存中找到A,移除三级缓存,放入二级缓存(半成品);
B完成创建,放入一级缓存,把B从三级缓存中移除;
A完成创建,A从二级缓存删除,放入一级缓存;
//=======
A依赖B,B依赖A
a)、A创建过程中需要B,于是先将A放到三级缓存,去实例化B。
b)、B实例化的过程中发现需要A,于是B先查一级缓存寻找A,如果没有,再查二级缓存,如果还没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。
c)、B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中的状态)。然后回来接着创建A,此时B已经创建结束,可以直接从一级缓存里面拿到B,去完成A的创建,并将A放到一级缓存
4)补充
a)懒加载可以解决循环依赖,IOC容器启动,懒加载的bean不会初始化
b)单例才能解决循环依赖,非单例不会缓存
c)构造函数也不能解决
1.调用连路核心概念
1) traceID/spanID/parentId
2) 没有parentId就是调用入口
3)spanID和parentId可以看到调用关系
4)分布式系统调用关系复杂
5)问题难以定位
6)性能难以测算
2.RPC的调用链
1)filter,多个的话使用spi机制;rpccontext.attachment ,本质上也是threadLocal
2)invoke方法中,前置处理traceID
3.HTTP的调用链
1)filter中处理
2)MDC,slf4j/log4j中提供的线程变量的封装
4.本质上都是使用threadLocal
1)异步场景无法使用;分布式跨机器无法使用
2)分布式----->rpc链路
3)mq可以使用
6.线程池如何传递线程上线文
思路:线程池提交任务时,记录当前上下文;执行完之后清除
实现方式:
1)在线程池提交处,手动编码
2)自定义线程池
3)阿里TTL
7.hystrix的线程池模式下如何传递
1)hystrix的的插件机制,HystrixConcurrencyStrategy
2)
8.javaagent—1.5引入的新特性
1)java类加载器—>agent——>加载jar包(可以修改class文件)——>类加载器
2)在修改的字节码时插入监控指令
3)写一个运行时jar包
1.tcp/ip协议
1)三次握手建立连接,四次挥手断开连接
2)tcp,面向连接的可靠的基于字节流的协议。连接并非物理性的,可靠的
udp
3)三次握手
a)A(syn) B(ack)
握手内容是通信双方数据的序列号;
每次数据发送都需要确认;
而单纯的ack无需确认;
数据以数据包的形式发送,而序列号确保数据包的顺序。
b)三次握手后,双方开辟资源。new 出线程,fork出进程
c)socket ip+port——>ip+port
4)四次挥手
A 发送FIN——> B
B(ack)——>A
{B(数据未发送完毕,继续发送)——>A}
B发送FIN——>A
A(ack)——>B
5)流量控制和拥塞控制
a)大小可变的滑动窗口进行流量控制
b)根据网络丢包情况,调整发送速率
2.http和https
1)https
a)加密分对称加密和非对称加密;对称加密的加密和解密是同一个秘钥,而非对称加密分公钥和私钥(其中一个加密,另一个可以解密);
b)https=http+ssl
c)兼顾效率和安全性。数据进行对称加密,而加密的秘钥通过非对称加密的方式传输
2)http
a)http不对请求双方进行身份验证
b)
3)https通信的整个过程
a)两次交互即http请求,三个秘钥
b)第一次请求,客户端(443端口)带上支持的加密算法——>服务端,服务端返回证书(公钥)——>客户端验证证书的合法性,用公钥加密客户端的秘钥
c)第二次请求,客户端带着加密后的秘钥和密文发送给服务端——>私钥解密秘钥,然后用得到的秘钥解密报文;返回时同样加密
3.网络分层
应用层
传输层
网络层
链路层
物理层
4.线程通信
1)线程通信的方式有两种:内存共享和消息传递
volatile和wait/notify
2)两个线程使用同一个锁对象Object lock = new Lock(),然后使用lock.wait和lock.notify进行通信
3.线程的状态
1)新建
2)就绪
3)运行
4)阻塞
5)消亡
6)WAITING
a)wait/thread.join/locksupport.park
b)TIMED_WAITING,以上方法加入时限+Thread.sleep
4.线程的创建方式
1)runable
2)thread
3)callable
5.线程不安全的原因
1)runable
2)thread
3)callable
6.线程变量源码
1)内部类ThreadLocalMap,key/value形式,key是ThreadLocal实例的弱引用;每个线程内部维护了一个ThreadLocalMap
2)!!ThreadLocalMap跟thread的生命周期一致,如果用完theadlocal没有手动删除就会导致内存泄漏
3)key为弱引用是多了一层保障,key置为null后,threalocalmap的set/get/remote会自动删除对应的value
7.线程池
1)生产者和消费者。设计模式,
2)线程池的关闭
a)shutdown 和 shutdownnow
线程池状态 shutdown 和stop
中断空闲的workers和中断所有的workers
无返回和返回所有的任务列表
1.sychronized 和 reentrantlock
1)不同点
2)相同点(相近点)
2.synchronized的加锁方式
锁的状态保存在对象的头(mark word)文件中
是否为偏向锁+锁的标志位
1)无锁
2)偏向锁->轻量级锁->重量级锁
a)偏向锁适用于只有一个线程访问同步代码块
b)轻量级锁竞争线程不会阻塞,自旋;失败后自动升级
c)锁可以升级,没有降级;锁升级的过程也是一种性能消耗
3)重量级锁,线程的阻塞和唤醒需要cpu通过操作系统指令完成,频繁的用户态和内核态产生性能损耗
3.synchronized的加锁方式
1)方法加锁
锁住了本类的实例对象
2)synchronized(this/对象)
同步代码块,锁住的是当前实例对象
3)synchronized(xxx.class)
全局锁
4)静态方法前synchronized
全局锁
5)notify通知等待队列中最前面一个,并非优先级最高的
4.AQS
1)AQS的设计
a)内部维护一个先进先出的双向队列;线程获取锁失败后会被加入同步队列,线程阻塞;锁(同步状态)释放后,被唤醒;
b)公平与非公平;公平是获取锁之前先判断线程是否在等待队列的队首
c)读锁和写锁;判断抢占的线程是否为当前线程
读写互斥;写写互斥;读和读不互斥
readlock和writelock使用同一个同步器,也就是一个state
2)reentrantlock/countdownlatch/Semaphore 的内部类sycn继承于aqs
1.为什么需要AOP,IOC(解决了什么问题)
IOC 依赖注入和控制反转;单列模式资源重复利用;修改代码无需重复new A()
面向切面编程,是面向对象的一种补充
2.spring的Aop是如何实现的-代理(JDK或者cglib)
AopProxy有两个实现JdkDynamicAopProxy和ObjenesisCglibAopProxy
3.spring如何决定走JDK还是CGlib代理
DefaultAopProxyFactory createAopProxy
如果目标类是接口就一定会使用jdk代理,如果目标类没有可以代理的接口就一定会使用Cglib代理
proxy-target-class 参数
4.代理具体判断
a)实现接口的类默认JDK代理(注解)
b)未实现接口的类走CGlib代理(注解)
c)无aop注解的不走代理
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
第一个参数强制走CGlib代理,第二个参数AOPContext获取代理对象,内部方法调用时需要强制走代理
5.JDK和CGlib代理的分别是什么,区别,优劣势
a)
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
JDK代理的目标类必须实现接口,否则只能走CGlib
b)
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的
c)性能
CGlib底层是依靠ASM(开源的java字节码编辑类库)操作字节码实现的,运行性能比JDK强。jdk6和jdk7性能比cglib稍弱,从jdk8开始性能比cglib强
主要区别在创建和运行时的效率:CGLib创建的代理对象在运行时性能要比JDK动态代理高不少。有研究表明,大概要高10倍。
但是CGLib创建对象的时所花费的时间却比JDK动态代理要多,有研究表明,大概有8倍的差距
!!!
CGLib 更适合代理不需要频繁实例化的类,而Spring 绝大多数 Bean 都是单例的,因此在 Spring AOP 中推荐使用 CGLib,它的功能更强大些
!!!