数据与内存模型 (mysql索引、JVM内存、java线程内存)
1、mysql索引
当mysql数据量比较大的时候,查询特别慢,
我们第一个想到的解决方式应该是为数据添加索引。
索引能够高效获取数据排好序的数据结构。
1.1、索引的数据结构
5种数据结构:
二叉树、红黑树、Hash表、B-Tree、B+Tree
mysql的索引内置:Hash表、B+Tree
1.1.1、二叉树
二叉树是以单边增长的形式存储索引
查找5,要获取5次磁盘io
1.1.2、红黑树
红黑数是一种自平衡二叉查找树
例如:只有1、2
例如:只有1、2、3
例如:有1-7
查找5,要获取4次磁盘io
缺点:红黑树在数据量较大的时候高度很高,读取io的次数比较多
1.1.3、hash表
每增加一个索引,都会被hash运算一次,算出来的值就对应内存中一个具体的地址。
用运算减少io读取
hash的缺点:
1、数量较大时,哈希运算次数回变得很多,暂用cpu资源
2、做范围查找时,其他数据结构能够很快查出来,而hash要一次一次运算
1.1.4、B-Tree
B-Tree是在红黑树的基础上,增加首节点个数(首节点存在内存里)
缺点:没有解决范围查找
1.1.5、B+Tree
mysql官方对B+Tree进行了节点限制,将一个节点的大小设置成了16384个字节,也就是大约16KB。
大致的算一下,16KB/14B = 1170个索引元素。
这里高度为3,那么能存的数据量为1170x1170x16 大概为2千多万条数据
数据结构展示网址:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
1.2、数据表与对应的文件
1.2.1、MyISM 非聚集索引
frm 文件:用来存储表的结构
MYD 文件:存储所有数据行
MYI 文件:存储主键索引
1.2.2、InnoDB 聚集索引
frm 文件:用来存储表的结构
ibd 文件:用来存储索引+数据
-
InnoDB 是按照B+Tree组织的一个索引结构文件
-
InnoDB必须有主键,并且推荐使用整型自增
-
如果你在建InnoDB表的时候,没指定主键,它会找一个能够唯一识别的字段,作为你的主键,找不到会帮你生成一个默认的索引。
-
如果不用整型自增,用uuid做主键,会让B+Tree进行不断的分离,从而性能降低,如下
例:
使用uuid的索引,每个值大小的不确定性就会造成如上的分裂。
自增的整型会按照顺序在后面增加而不分裂
2、JVM内存模型
2.1、javap 指令查看字节码文件
第一步:打开class位置
第二步:输入指令javap -c 文件名.class > 1.txt
第三步:查看calss字节码文件
对比 jvm指令手册:https://www.cnblogs.com/qizhelongdeyang/p/12125121.html
就能知道什么意思
2.2、栈(线程)
- 栈:程序在执行过程中存储局部变量。每个线程独享一个栈。
- 栈帧:当程序运行过程中,每执行一个新方法,都会生成一个栈帧,这个栈帧里面存局部变量。
一个栈帧包含的内容:局部变量表、操作数栈、动态链接、方法出口
2.2.1、操作数栈
- 可以用来操作局部变量,操作数每增加一个,就完成一个局部变量的入栈
- 一些零时的数据操作会在操作数栈里进行
2.2.2、动态链接
动态链接,里面存的是方法名+一个方法在方法区里的内存地址,
当一个方法被执行的时候,会去方法区寻找这个方法的地址。
2.2、程序计数器
每个线程都应该有一个程序计数器
它用来存储程序下一个执行的行
相当于它里面记录着程序现在执行到哪个位置了(class字节码中的位置)
2.3、堆
堆与栈的关系:
当栈中有一个引用类型的变量被创建时,new的内容会被放进堆,栈的这个变量存的就是对应堆里的空间地址。
2.4、方法区(元空间)
方法区里面的内容:常量+静态变量+类元信息
以前叫方法区或者持久代,jdk1.8之后叫元空间
类元信息里储存着一个类的基本信息,每一次在堆里面new的时候,都会从方法区调取类的元信息。
下面我们输出class字节码的附加信息看看
-v输出来的就多了很多常量池
元空间的数据是直接存在内存里面的
2.5、本地方法栈
本地方法栈里面存着和其他语言交换的接口
通过本地方法栈调用其他语言,这种方法目前已经不流行了,现在更多的是通过一些框架去调用
2.6、jvisualvm 查看jvm内存空间
如果配置了JDK环境变量,可以在cmd直接输入jvisualvm打开
没有配置,可以在jdk/bin目录下找到
运行一个java代码就能找到它的相关信息
3、java线程内存模型
多核CPU缓存架构
java的线程内存模型
线程的原子操作(线程从主内存中读取到工作内存中的一些操作)
3.1、工作内存和主内存
1、线程间变量互相独立
2、如果想让线程都能知道一个变量的变化,就用volatile关键字。
读取volatile类型的变量时总会返回最新写入的值。
3.2、volatile实现两个线程的工作内存变量一致
如下示例,线程1循环获取变量的值,线程2改变变量值
在没有加volatile关键字之前的线程原子操作流程图
我们发现线程2,更新变量值后会改变主内存中变量的值,但是
加上volatile关键字之后
加上volatile关键字能够实现两个线程的工作内存变量一致。
有两种实现方式
1、总线加锁(性能低)
2、MESI缓存一致性协议
cpu嗅探能够感知主内存里的变化,一旦主内存变化,就向cpu里的变量重新赋值
3.3、hsdis插件 : 将java反编译成汇编语言
首先下载hsdis插件
http://vorboss.dl.sourceforge.net/project/fcml/fcml-1.1.1/hsdis-1.1.1-win32-amd64.zip
下载完成将里面的两个文件放入java的jre/bin目录下
然后在idea中加入这个参数
-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssemblu -XX:Command=compileonly,*VolatileVisibilityTest.prepareData
注意上面的jre要选择hsdis-amd64.dll文件所在的那个jre。
输入的那长串参数最后的VolatileVisibilityTest指的类名,prepareData是方法名。
接下来开始运行
3.4、并发编程的三大特性
可见性、原子性、有序性
volatile保证可见性与有序性
synchronized保证原子性
volatile只能用来修饰变量
- 可见性:当一个线程改变变量的值时,volatile保证其他线程能够感知这个改变、可见这个改变。
- 有序性:被volatile修饰的变量的操作,会严格按照代码顺序执行(写库,查询,删除)。
- 原子性:synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A
synchronized使用场景:
本文地址:https://blog.csdn.net/a__int__/article/details/110851052
推荐阅读
-
《深入理解Java虚拟机》-----第12章 Java内存模型与线程
-
深入理解Java虚拟机(第三版)-13.Java内存模型与线程
-
数据与内存模型 (mysql索引、JVM内存、java线程内存)
-
《深入理解Java虚拟机》-----第12章 Java内存模型与线程
-
深入理解Java虚拟机(第三版)-13.Java内存模型与线程
-
Java内存模型与线程
-
[二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义
-
java内存模型与线程
-
JVM内存模型与运行时数据区域的详解(图文)
-
荐 【探究JVM四】Java方法执行的线程内存模型——虚拟机栈 字节码指令追踪,万字长文深入探究内部结构