文章目录
1. hashmap
1、put(key, value)中直接调用了内部的putVal方法,并且先对key进行了hash操作;
2、putVal方法中,先检查HashMap数据结构中的索引数组表是否位空,如果是的话则进行一次resize操作;
3、以HashMap索引数组表的长度减一与key的hash值进行与运算,得出在数组中的索引,如果索引指定的位置值为空,则新建一个k-v的新节点;
4、如果不满足的3的条件,则说明索引指定的数组位置的已经存在内容,这个时候称之碰撞出现;
5、在上面判断流程走完之后,计算HashMap全局的modCount值,以便对外部并发的迭代操作提供修改的Fail-fast判断提供依据,于此同时增加map中的记录数,并判断记录数是否触及容量扩充的阈值,触及则进行一轮resize操作;
6、在步骤4中出现碰撞情况时,从步骤7开始展开新一轮逻辑判断和处理;
7、判断key索引到的节点(暂且称作被碰撞节点)的hash、key是否和当前待插入节点(新节点)的一致,如果是一致的话,则先保存记录下该节点;如果新旧节点的内容不一致时,则再看被碰撞节点是否是树(TreeNode)类型,如果是树类型的话,则按照树的操作去追加新节点内容;如果被碰撞节点不是树类型,则说明当前发生的碰撞在链表中(此时链表尚未转为红黑树),此时进入一轮循环处理逻辑中;
8、循环中,先判断被碰撞节点的后继节点是否为空,为空则将新节点作为后继节点,作为后继节点之后并判断当前链表长度是否超过最大允许链表长度8,如果大于的话,需要进行一轮是否转树的操作;如果在一开始后继节点不为空,则先判断后继节点是否与新节点相同,相同的话就记录并跳出循环;如果两个条件判断都满足则继续循环,直至进入某一个条件判断然后跳出循环;
9、步骤8中转树的操作treeifyBin,如果map的索引表为空或者当前索引表长度还小于64(最大转红黑树的索引数组表长度),那么进行resize操作就行了;否则,如果被碰撞节点不为空,那么就顺着被碰撞节点这条树往后新增该新节点;
10、最后,回到那个被记住的被碰撞节点,如果它不为空,默认情况下,新节点的值将会替换被碰撞节点的值,同时返回被碰撞节点的值(V)。
2.JVM
2.1详细jvm内存模型
程序计数器:指向当前正在执行线程的字节码地址的指令或行号。属于线程私有的。
虚拟机栈:存储当前线程运行方法所需的数据指令,访问地址(java独有的)
Java虚拟机栈划分为四个部分:局部变量表,操作数栈,动态链接,方法出口
本地方法栈:存储本地方法
方法区:方法区包含静态变量+类信息+字面量常量+运行时常量池
2.2 讲讲什么情况下回出现内存溢出,内存泄漏?
内存泄漏的原因很简单:
对象是可达的(一直被引用)
但是对象不会被使用
解决:
查看程序是否存在内存泄漏的问题
设置参数加大空间
代码中是否存在死循环或循环产生过多重复的对象实体、
查看是否使用了nio直接操作内存。
2.3 JVM 年轻代到年老代的晋升过程的判断条件是什么呢?
- 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存2活,就存入到老年代。
- 如果对象的大小大于Eden的二分之一会直接分配在old,如果old也分配不下,会做一次majorGC,如果小于eden的一半但是没有足够的空间,就进行minorgc也就是新生代GC。
- minor gc后,survivor仍然放不下,则放到老年代
- 动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代
2.4 类的实例化顺序
1. 父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
2. 子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
3. 父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
4. 父类构造方法
5. 子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
6. 子类构造方法
2.5 JVM垃圾回收机制,何时触发MinorGC等操作
当young gen中的eden区分配满的时候触发MinorGC(新生代的空间不够放的时候).
2.6 各种回收算法
GC最基础的算法有三种:
标记 -清除算法
复制算法
标记-压缩算法
我们常用的垃圾回收器一般都采用分代收集算法(其实就是组合上面的算法,不同的区域使用不同的算法)。
具体:
标记-清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
3.spring的什么周期
4.分布式事务
4.1 X/OPEN DTP
AP application, 应用程序
RM resource manager ,资源管理,一般表示数据库,必须实现XA定义的接口
TM transaction manager 事务管理器,负责协调和事务管理
2pc提交协议
a)提交事务请求(投票)
b)执行事务请求(提交或中断)
2pc的数据一致性问题
a)数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
b)同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态
c)二阶段无法解决的问题:协调者在发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交
d)单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去
3pc提交协议
a)canCommit
b)preCommit
c)doCommit
改进点
a)增加了超时机制
b)第二阶段,如果协调者超时没有接受到参与者的反馈,则自动认为失败,发送abort命令
c)第三阶段,如果参与者超时没有接受到协调者的反馈,则自动认为成功开始提交事务(基于概率)
3pc的问题
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
XA/JTA
XA 就是 X/Open DTP 定义的事务管理器与资源管理器的接口规范(即接口函数),XA 接口函数由数据库厂商提供。
JTA是基于X/Open DTP模型开发的java transaction APi规范
概述
通过2pc的方式去完成分布式事务,虽然通过这种方式能够达到预期的效果,但是我们在现实中很少会用到2pc方式的提交的XA事务,有几个原因
1.互联网电商应用的快速发展,对事务和数据的绝对一致性要求并没有传统企业应用那么高
2.XA事务的介入增加了TM中间件,使得系统复杂化
3.XA事务的性能不高,因为TM要等待RM回应,所以为了确保事务尽量成功提交,等待超时的时间通常比较长,比如30s到几分钟,如果RM出现故障或者响应比较慢,则整个事务的性能严重下降
4.2 互联网的分布式事务解决方案
目前互联网领域里有几种流行的分布式解决方案,但都没有像之前所说的XA事务一样形成X/OpenDTP那样的工业规范,而是仅仅在具体的行业里获得较多的认可
业务接口整合,避免分布式事务
这个方案就是把一个业务流程中需要在一个事务里执行的多个相关业务接口包装整合到一个事务中,比如我们可以讲A/B/C整合为一个服务D来实现单一事务的业务流程服务
最终一致性方案之ebay模式
eBay在2008年公布了一个关于BASE准则提到一个分布式事务解决方案。eBay的方案其实是一个最终一致性方案,它主要采用消息队列来辅助实现事务控制流程,方案的核心是将需要分布式处理的任务通过消息队列的方式来异步执行,如果事务失败,则可以发起人工重试的纠正流程。人工重试被更多的应用于支付场景,通过对账系统对事后问题进行处理
比如一个很常见的场景:某个用户产生了一笔交易,那么需要在交易表中增加记录,同时需要修改用户表的金额(余额),由于这两个表属于不同的远程服务,所以就会涉及到分布式事务与数据一致性的问题
user(id, name, amt_sold, amt_bought)
transaction(xid, seller_id, buyer_id, amount) begin;
INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount);
UPDATE user SET amt_sold = amt_sold + $amount WHERE id = $seller_id;
UPDATE user SET amt_bought = amt_bought + $amount WHERE id = $buyer_id;
commit;
那么在这里可以使用消息队列(MQ)来做
先启动一个事务,更新交易表(transaction)后,并不直接更新user表,而是将要对user表进行的更新插入到消息队列中。
目标系统收到该消息以后,启动本地事务去对用户表的余额做调整
伪代码
bool result=dao.update();
if(result){
mq.send();
}
根据上面的伪代码的实现方案,可能出现几种情况
1.数据库操作成功,向MQ中投递消息也成功
2.操作数据库失败,不会向MQ中投递消息
3.操作数据库成功,但是向MQ中投递消息时失败,向外抛出异常。数据库操作回滚
对于上面几种情况,问题都不大。那么我们分析小消费端的问题
1.消息出队列以后,消费者对应的业务操作要执行成功。如果执行失败,消息不能失效或者丢失。需要保证消息和业务操作一致
2.尽量避免消息重复消费,如果重复消费,也不能影响业务的执行结果
对于第一个问题,如何保证消息不丢失
现在用的比较普遍的MQ都具有持久化消息的功能,如果消费者宕机或者消费失败,都可以执行重试机制
对于如何避免消息的重复消费
1.保证消费者的幂等性;也就是说如果队列中的消息因为网络异常导致发送多次的情况下,仍然需要保证消息被应用多次与应用一次产生的效果是一样的
2.通过消费日志表来记录消费状态;增加一个message_applied(msg_id)表,用来记录已经被成功应用的消息。在目标系统执行更新操作之前,先检测该消息是否已经被消费过,消费完成后通过本地事务控制来更新这个“消费表状态”,用来避免消息重复消费问题
上面这种方式是非常经典的实现,基本避免了分布式事务,实现了“最终一致性”。
各大知名的电商平台和互联网公司,几乎都是采用类似的设计思路来实现“最终一致性”的。这种方式适合的业务场景广泛,而且比较可靠。不过这种方式技术实现的难度比较大
保证最终一致性的模式
5. mysql性能优化
5.1 OLTP & OLAP
5.2 提高性能
6. 如何控制多线程的执行顺序
6.1 join
通过join方法去保证多线程顺序执行的特性
join:让主线程等待,子线程完后才继续往下执行
6.2 ExecutorService
ExecutorService executorService = Executors.newSingleThreadExecutor(); FIFO 先进先出队列的方式
7. volatile & syncronized
7.1 volatile
对于声明了volatile的变量进行写操作时,JVM会向处理器发送一条lock前缀的指令。会把这个变量所在缓存行的数据写回到系统内存;
7.1 syncronized
8. 数据结构
8.1 数组
数组是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始。例如下面这段代码就是将数组的第一个元素赋值为 1。
优点:
1、按照索引查询元素速度快
2、按照索引遍历数组方便
缺点:
1、数组的大小固定后就无法扩容了
2、数组只能存储一种类型的数据
3、添加,删除的操作慢,因为要移动其他的元素。
适用场景:
频繁查询,对存储空间要求不大,很少增加和删除的情况。
8.2 栈
栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。
8.3 队列
队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。从一端放入元素的操作称为入队,取出元素为出队
8.4 链表
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
链表的优点:
链表是很常用的一种数据结构,不需要初始化容量,可以任意加减元素;
添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快;
缺点:
因为含有大量的指针域,占用空间较大;
查找元素需要遍历链表来查找,非常耗时。
适用场景:
数据量较小,需要频繁增加,删除操作的场景
8.5 树
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个节点有零个或多个子节点;
没有父节点的节点称为根节点;
每一个非根节点有且只有一个父节点;
除了根节点外,每个子节点可以分为多个不相交的子树;
在日常的应用中,我们讨论和用的更多的是树的其中一种结构,就是二叉树。
二叉树是树的特殊一种,具有如下特点:
1、每个结点最多有两颗子树,结点的度最大为2。
2、左子树和右子树是有顺序的,次序不能颠倒。
3、即使某结点只有一个子树,也要区分左右子树。