Java核心技术深度剖析:从JVM与垃圾回收到数据结构与MySQL事务

1、JVM

1.1 定义

JVM是双亲委派机制,自上向下加载,启动类加载器、扩展类加载器、应用类加载器,在JDK1.8开始新增了自定义类加载器

启动类加载器:主要加载java核心库

扩展类加载器:加载扩展卡,如classpath中的jre

应用类加载器:加载程序所在目录

自定义类加载器:加载用户自定义的文件

1.2 内存模型

一共分为两类:线程共享的和线程私有的

线程共享:静态方法区(在JDK1.8后被元空间替代)、堆

线程私有:虚拟方法栈、本地方法栈、程序计数器

虚拟方法栈:每个线程私有的区域,每个方法执行时,都会创建出一个栈帧,用于存储局部变量、动态链接、方法出口等信息;每个方法从调用到结束的过程,代表一个栈帧从入栈到出栈的过程。

静态方案区:常量、静态变量

1.3 垃圾回收

1、算法:

垃圾回收器主要有以下算法:标记清除、标记整理、复制算法、分代回收

2、垃圾回收器:

串行回收器:最原始的垃圾回收器,通过单线程执行垃圾回收,执行过程中,应用线程会停掉等待;包含:Serial、Serial Old垃圾回收器

并行回收器:多个线程通过执行垃圾回收器,但是在执行的过程中,应用线程也是需要停掉等待的;包含:PreNew、Paralle Old

并发回收器:CMS、G1、ZGC

CMS(JDK5):并发标记算法,主要目的是减少应用程序的停顿时间;该回收器采用的标记清除算法,主要分为四步操作:

1》初始标记:标记出所有可达的根对象,这步是STW(Stop-The-World)操作;

2》并发标记:标记出所有可达的对象,这步是并发执行的;

3》重新标记:标记出所有在并发阶段被修改的对象,确保在并发阶段结束时,没有新增对象或被标识的对象被修改;这步会STW;

4》并发清除:这步是在重新标记完成之后,会启动并发清除,垃圾回收器与应用程序并发执行;

优点:减少停顿时间、提高吞吐量

缺点:垃圾碎片太多、停顿时间不确定

G1(JDK9):分代清除算法,主要是将内存划分为年轻代和老年代;由于年轻代生命周期比较短,所以采用复制算法;老年代声明周期比较长,采用标记整理算法;该回收器还有一个更大的优势,是可预测模型算法,会根据之前的回收记录,评估平均需要多长时间,尽量满足设置的预期值;

优点:低停顿(因为分代)、可预测的停顿时间、内存碎片整理

缺点:性能波动(可能出现在应用程序刚启动或者堆内存分配太小)

ZGC(JDK11):是一个采用region(分区)布局,不分代的垃圾回收器,使用了读屏障、染色指针和内存多重映射来实现可并发的标记-整理算法,以低延迟为首要目标;在对吞吐量影响不大的情况下,实现任意堆内存大小下可以把垃圾回收停顿时间限制在10ms内

优点:极低停顿、对大堆内存的支持、高并发性能(采用了可中断的并发处理方式,可以在不影响应用程序响应的情况下完成回收)

缺点:性能成本比较高(会有一些额外开销,内存开销:因为他需要维护额外的数据结构来支持并发标记、整理、清理的过程;线程开销:因为他是可中断的处理方式,所以需要创建和管理一些额外的线程来操作垃圾回收)

3、四种引用:

强引用:即使内存不足时,也不会回收该对象;例如:new Object();

软引用:当系统内存不足时,会尝试回收该引用的对象;例如:使用java.lang.ref.SoftReference创建的对象

使用场景:缓存机制

弱引用:当垃圾回收器扫描到该引用时,不管内存是否充足,都会被回收;例如:使用java.lang.ref.WeakReference;

使用场景:对对象声明周期不敏感的缓存

虚引用:不会对对象生命周期产生影响,只是在对象被回收时被调用;

使用场景:当对象被回收后,如果需要做清理操作、记录日志或其他操作可以调用该引用;

2、数据结构

2.1 数组(Array)

定义:是一种线性数据结构,由一串连续的内存单元组成,用来存储相同类型的数据元素;

优点:1、查询快,可以根据索引快速访问任意元素,时间复杂度是O(1);

缺点:1、插入和删除慢;2、扩容代价比较大,如果确定大小了就无法扩容了;

底层使用的类:ArrayList、Vector;

2.2 队列(Queue)

定义:是一种线性数据结构,具有先进先出的特性,每次添加元素到队尾从队首开始消费

优点:1、先进先出,保证顺序;2、线程安全,在并发情况下可以使用ConcurrentLinkedQueue保证安全;

缺点:只能从队尾或者队首访问,无法随意访问;

底层使用的类:Linked List实现了Queue接口,因此,可以用作队列的实现;

2.3 链表(Linked List)

定义:是一种线性数据结构,由一系列节点组成,每个节点都有本节点元素及指向下一节点的指针;

优点:1、具有动态性,不需要提前分配内存;2、插入和删除效率高,只需要移动指针就可以;

缺点:1、查询慢,链表中的节点不是联系存储的,时间复杂度O(n);2、额外空间开销,每个节点都需要有一个指针指向下一个节点;

底层使用的类:Linked List

2.4 哈希表(Hash)

定义:是以键值对的形式存储,由数组和哈希函数组成,哈希函数将键映射到数组索引位置上,从而查询接近于常数;

优点:1、快速查找;2、快速增删;3、适用于大规模数据;

缺点:1、易出现哈希冲突;2、不适用有序操作;

底层使用的类:HashMap

2.5 栈(Stack)

定义:是一个先进后出的数据类型,一般有两种操作方式,压栈、出栈;

优点:操作简单,都是在栈顶操作

缺点:不灵活,只能在栈顶操作

底层使用的类:Stack

2.6 堆(Heap)

定义:堆是一个特殊的树形结构,通常由一个数组来表示;堆分为最大堆和最小堆,最大堆的父节点大于等于子节点,最小堆的父节点小于等于子节点;

优点:1、快速插入和删除;2、快速查找最值;3、不要求整体数据集有序,只要局部有序

缺点:1、不支持在常数范围内查看数据集,只支持查看最大值或最小值;

底层使用的类:java.util.PriorityQueue

2.7 图(Graph)

定义:是一个抽象的数据结构,由点和边组成;通常分为有向图和无向图;

优点:1、可以很好的表示关系

缺点:1、比较复杂,由于图中点和边的关系,让算法相对较复杂;

底层使用的类:Java 标准库中的 Graph 接口和相关类,如 ArrayList 来表示邻接表

3、数据库

3.1 索引

主键索引:逐渐索引,数据库帮我我们创建,不能有空值;

普通索引:基本索引类型,没有限制,允许在定义索引列中插入重复值和空值;

唯一索引:索引列中的值必须是唯一的;

组合索引:组合多个字段创建的索引,需要遵守最左原则;

全文索引:只有MYSUM引擎支持,并且只支持CHAR、VARCHAR、TEXT几种类型;

缺点:1、空间损耗,索引也需要一定的物理空间,尤其是联合索引占用会更大;2、性能影响,在插入修改和删除时,会更新索引;

3.2 存储引擎

MYSQL有两种存储引擎,Mysum和InnoDB,默认使用的是InnoDB,支持事务;数据结构有两种hash和Btree;并且使用Btree的话,它底层是属树结构,支持排序,像hash结构的话,不支持排序,只是查询快;

3.3 事务

1、四大特性

原子性(Atomicity):一次事务操作,要么全部成功,要么全部失败;

数据在写数据库时,会先写undo log文件中,当提交事务之后,会建undo log文件中的日志提交,如果要回滚,直接回滚该文件的日志即可;

一致性(Consistency):当一次完成的事务操作,事务前后状态保证一致

这个特性主要是靠约束或者业务逻辑保证;

隔离性(Isolation):事物与事物之间互不影响

主要是靠mvcc和锁保证事务隔离

锁:通过行级锁,可以防止下修改数据的过程中被其他事务修改

mvcc:通过本身事务默认的隔离级别“可重复读”,可以解决不可重复读和幻读的问题

持久性(Durability):将数据持久化到磁盘

主要通过预写日志的方式,将事务提交的内容先记入redo log文件中,然后redo log会定期刷盘,达到持久化的作用;

2、事务并发问题

脏读:A事务读取了B事务未提交的数据

不可重复读:在A事务内同一个查询,读到了两个不一样的结果,这是因为读取了B事务修改完并提交的数据

幻读:在同一个事务内,同样的两次查询,返回的结果不一样,这是因为有其他事务写入数据并提交了事务

3、事务隔离级别

读未提交:select语句不加锁,事务中修改的语句没有提交,对其他事务也都是可见的;

读已提交:可以读取到其他事务已经提交的数据,但是多次读取的数据可能不一致;这种级别可以避免脏读;

可重复读:保证了在同一事务中,多次读取同一个记录的结果是一致的;解决了不可重复读和脏读;

串行化:强制事务串行执行,在读取每一行数据时,加行锁;解决了脏读、不可重复读、幻读;

3.4 MVCC机制

多版本并发控制,是一种无锁的机制保证高并发;主要目的是提高数据库并发性能,降低死锁风险;

提高并发性能:读写操作之间互不影响;

降低死锁风险:无需使用显示锁进行并发控制;

应用事务:读已提交、重复读;

读未提交:直接写表

串行化:加表锁

组成:1、隐藏列 2、undo log 3、read view

三个隐藏列:1、当前数据操作事务ID 2、老数据列 3、事务ID列

3.5 锁

乐观锁:一般是用户自己实现的,例如使用版本号、时间戳等方式;
悲观锁:InnoDB支持表锁、行锁、页锁,并发能力强,但是容易死锁;Mysum支持表锁,不会出现死锁,但是并发能力差;

3.6 场景案例

场景一:

如果在同一个事务中,首先读取了表 T 的 A 字段为 1,然后在事务中修改了 A 字段为 2,继续在同一个事务中查询这张表,那么在查询时应该能够看到修改后的结果,即 A 字段的值为 2。

这是因为在数据库的隔离级别中,通常情况下,在同一个事务中所做的修改对该事务中的后续查询是可见的。即使在未提交的情况下,事务内部的修改对后续查询也是可见的。所以在这个场景下,事务内部先读取了 A 字段为 1,然后修改为 2,后续查询应该能够看到 A 字段被修改为 2 的值。

场景二:

如果在 AA 事务中首先读取了表 T 的 A 字段为 1,然后在另一个 BB 事务中修改了 A 字段为 2 并且 BB 事务提交了事务,之后在 AA 事务中继续查询这张表,那么在 AA 事务中的查询结果应该是 A 字段的值为 1。

这是因为 AA 事务在自己开始时读取了 A 字段为 1,之后 BB 事务修改了 A 字段为 2 并提交了事务。在数据库默认的隔离级别下,其他事务在提交后所做的修改对当前事务不可见。因此,尽管 BB 事务修改了 A 字段为 2 并提交了事务,但是在 AA 事务中查询时,AA 事务只能看到自己开始时的快照,即 A 字段的值为 1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乔公子搬砖

您的支持,是我分享的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值