java线程模型
关于线程的理解
关于多线程并发:
并发不一定依赖多线程(如php中很常见的多进程并发),但是在java中谈并发大多数和线程拖不了关系
线程和进程:
线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的分配资源和执行调度分开,各个线程既可以共享资源(内存地址,文件io等),又可以独立调度(线程是cpu调度的基本单位)
线程与操作系统:
主流的操作系统都提供了线程实现,Java语言则提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经执行start()且还未结束的java.lang.Thread类的实例就代表了一个线程。
关于线程的实现
线程的实现有三种方式:
- 使用内核线程实现
- 使用用户线程实现
- 使用用户线程和轻量级进程混合实现
内核线程模型
内核线程模型:
完全依赖操作系统内核提供的内核线程,来实现多线程。在这个模型下,线程的切换和调度由系统的内核完成,系统内核负责将多个线程执行的任务映射到各个CPU中去执行。
轻量级进程:
程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型。
轻量级进程是由内核线程支持的一种高级接口。
用户进程使用系统内核提供的接口———轻量级进程(Light Weight Process,LWP)来使用系统内核线程。在此种线程模型下,由于一个用户线程对应一个LWP,因此某个LWP在调用过程中阻塞了不会影响整个进程的执行
轻量级线程的局限性:
由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作,但是轻量级进程具有它的局限性:
首先,由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。
其次,每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的
用户型线程模型
轻量级线程属于?
轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,效率会受到限制。
用户线程指的是完全建立在用户空间的线程库上,
系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。
如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型。
使用用户线程的优势和劣势:
优势:在于不需要系统内核支援
劣势:没有系统内核的支援
没有系统内核的支援导致:
所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至不可能完成。因而使用用户线程实现的程序一般都比较复杂,此处所讲的“复杂”与“程序自己完成线程操作”,并不限制程序中必须编写了复杂的实现用户线程的代码,使用用户线程的程序,很多都依赖特定的线程库来完成基本的线程操作,这些复杂性都封装在线程库之中,除了以前在不支持多线程的操作系统中(如DOS)的多线程程序与少数有特殊需求的程序外,现在使用用户线程的程序越来越少了,Java、Ruby等语言都曾经使用过用户线程,最终又都放弃使用它。
混合线程模型
关于混合线程模型理解:
线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式。在这种混合实现下,既存在用户线程,也存在轻量级进程。
用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。
而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。
在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为N:M的关系。许多UNIX系列的操作系统,如Solaris、HP-UX等都提供了N:M的线程模型实现。
java线程调度
关于线程调度的理解
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度(Cooperative Threads-Scheduling)和抢占式线程调度(Preemptive Threads-Scheduling)。
协同式调度:
如果使用协同式调度的多线程系统,
- 线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上。
- 协同式多线程的最大好处是实现简单,而且由于线程要把自己的事情干完后才会进行线程切换,
- 切换操作对线程自己是可知的,所以没有什么线程同步的问题。
缺点:
Lua语言中的“协同例程”就是这类实现。它的坏处也很明显:线程执行时间不可控制,甚至如果一个线程编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。很久以前的Windows 3.x系统就是使用协同式来实现多进程多任务的,
相当不稳定,一个进程坚持不让出CPU执行时间就可能会导致整个系统崩溃。
抢占式调度
如果使用抢占式调度的多线程系统,那么,线程的切换不由线程本身来决定(在Java中,Thread.yield()可以让出执行时间,但是要获取执行时间的话,线程本身是没有什么办法的)。在这种实现线程调度的方式下,线程的执行时间是系统可控的,也不会有一个线程导致整个进程阻塞的问题,Java使用的线程调度方式就是抢占式调度。在JDK后续版本中有可能会提供协程(Coroutines)方式来进行多任务处理。与前面所说的Windows 3.x的例子相对,在Windows 9x/NT内核中就是使用抢占式来实现多进程的,当一个进程出了问题,我们还可以使用任务管理器把这个进程“杀掉”,而不至于导致系统崩溃。
线程的优先级
虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。
不过,线程优先级并不是太靠谱,原因是Java的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统,虽然现在很多操作系统都提供线程优先级的概念,但是并不见得能与Java线程的优先级一一对应,如Solaris中有2147483648(232)种优先级,但Windows中就只有7种,比Java线程优先级多的系统还好说,中间留下一点空位就可以了,但比Java线程优先级少的系统,就不得不出现几个优先级相同的情况了,表12-1显示了Java线程优先级与Windows线程优先级之间的对应关系,Windows平台的JDK中使用了除THREAD_PRIORITY_IDLE之外的其余6种线程优先级。
volatile语义
什么是volatile?
- volatile在Java内存模型(JMM)中,保证共享变量对所有线程可见,但不保证原子性。
- volatile语义是同步,通过共享变量的方式,完成线程间的通信。
为什么需要volatile?
1.关于线程工作内存和主内存了解:
- Java内存模型中抽象、简化了计算机物理设备,分成工作内存和主内存,线程有各自的工作内存,却共享主内存。如果要把Java内存模型与物理设备映射起来的话,L1,L2 Cache可以视为工作内存,而L3 Cache视为主内存。
2.线程执行指令时,如何选择使用内存?
- 线程执行指令时,会优先选择距离 CPU 较近的位置的工作内存中使用,而不会从读写速度较慢的主内存中,我称之为“就近原则”。
3.volatile解决了什么问题?
- 当线程指令执行完后,赋值给工作内存,如果不回写到主内存,或者通知其他线程,其他线程是无法知晓变量已经修改,仍然会使用曾经缓存在工作内存中的变量,这就造成了缓存不一致的问题,Java使用volatile解决这种问题。
- volatile保证指令赋值完后的变量立即同步回主内存中,声明并通知其他线程当前赋值的变量已经失效,其他线程在下次使用时会放弃工作内存中变量,使用主内存中的变量。这样就完成了线程间对于volatile修饰的变量的通信。
可见性
- 执行引擎只与工作内存交互,再有工作内存与主内存交互。
- 站在执行引擎的角度,与工作内存操作完成即表示指令执行完,但是什么时候工作内存会将结果刷新回主内存却不可预测。
Java线程间的通信是通过共享内存的方式,线程A如果想通知其他所有线程(线程B,线程C)对于变量f的变化情况,需要满足两点:
- 将变量回写到主内存中
- 执行引擎读取时强制从主内存中加载
在增加了增加了L1、L2 Cache之后,CPU何时将变量从独享缓存刷新会共享内存,独享缓存是否从共享内存加载变量,时间上都是不可确定的,这就造成了缓存不一致的问题。
可见性的语义是线程对变量更新操作后,其他线程是可以获知变量的变更情况
用处和使用场景
volatile是java中较轻量的同步原语,用volatile声明的变量保证了该变量的可见性, 使用volatile声明的变量被其他线程更改时,该变量会立即被写入内存,同时 CPU会锁住总线,将缓存同步指令传递到其他处理器,其他处理器会从主内存中获取该变量的最新值更新到自己的缓存中。
https://blog.csdn.net/y874961524/article/details/82934831
详细解析
https://www.cnblogs.com/geyifan/p/6132242.html
线程同步的七种方式
1.同步方法
使用synchronized
2.同步代码块
使用synchronized代价高,使用时尽量缩小需要同步的代码块的范围
3.使用volatile
4.使用重入锁ReentrantLock
在读写操作前后,进行获得锁和释放锁
5.使用ThrowLocal类来管理变量
如果使用ThreadLocal管理变量,则每一个使用变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
6.使用阻塞队列实现
(例子:商家和卖家的买卖)
LinkedBlocking来实现线程同步LinkedBlockingQueue是一个基于已连接节点的,范围任意的blocking queue 队列是先进先出的顺序。
7.使用原子变量实现
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类
线程不同步的问题,主要出现在变量的读写操作时时,不是原子操作,导致部分线程可能中途插入修改值,不能同步数据
详细学习地址
https://blog.csdn.net/yuqing2015/article/details/82788041
数据库的索引(详细)
什么是索引?
索引是对数据库表中的一列或者多列的值进行排序的一种数据结构,如果把数据库中的表比作一本书,索引就是这本书的目录,通过目录可以快速查找到书中指定内容的位置
索引也是一张表,该表中存储着索引的值和这个值的数据所在行的物理地址,使用索引后可以不用扫描全表来定位某行的数据,而是通过索引表来找到该行数据对应的物理地址
原文链接:https://blog.csdn.net/ys_230014/article/details/88773918
数据库索引的引出(为什么需要数据库索引)?
数据库的索引问题 - > 查找问题
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询,更新数据库中表的数据.
索引的实现通常使用B树和变种的B+树(mysql常用的索引就是B+树)
除了数据之外,数据库系统还维护为满足特定查找算法的数据结构,这些数据结构以某种方式引用数据.这种数据结构就是索引
创建索引的好处
①通过创建索引,可以在查询的过程中,提高系统的性能
②通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性
③在使用分组和排序子句进行数据检索时,可以减少查询中分组和排序的时间
创建索引的坏处
①创建索引和维护索引要耗费时间,而且时间随着数据量的增加而增大
②索引需要占用物理空间,如果要建立聚簇索引,所需要的空间会更大
③在对表中的数据进行增加删除和修改时需要耗费较多的时间,因为索引也要动态地维护
应该在哪些列上创建索引呢
①经常需要搜索的列上
②作为主键的列上
③经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度
④经常需要根据范围进行搜索的列上
⑤经常需要排序的列上
⑥经常使用在where子句上面的列上
不应该在哪些列上创建索引
①查询中很少用到的列
②对于那些具有很少数据值的列.比如人事表的性别列,bit数据类型的列
③对于那些定义为text,image的列.因为这些列的数据量相当大
④当对修改性能的要求远远大于 搜索性能时.因为当增加索引时,会提高搜索性能,但是会降低修改性能
索引的分类和使用
按物理存储角度分
聚集索引
表记录的排列顺序和索引的排列顺序一致,所以查询效率快,
只要找到第一个索引值记录,其余连续性的记录在物理上一样连续存放.
聚集索引的缺点就是修改慢,因为为了使表记录和索引的排列顺序一致,在插入记录的时候,会对数据页重新排序
非聚集索引
表记录和索引的排列顺序不一定一致,两种索引都采用B+树的结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表记录的指针.非聚集索引层次多,不会造成数据重排
聚集索引查询快,插入数据慢
非聚集索引查询相对慢,当时插入快
两者关系有点像ArrayList和LinkList,数组和链表
按逻辑角度分
1)主键索引:是一种特殊的索引,不允许有空值,一般在建表的时候同时创建主键索引
2)基本索引:最基本的索引,没有任何限制
3)唯一索引:和普通索引相似,索引对应的列必须唯一,允许空值
4)复合索引(多列索引,联合索引):在多个字段上建立索引,用来提高复合查询的效率
创建联合索引:create index idx_name_age on student(name,age);
查看索引:show index from student;
使用!=,is null,or相关会导致索引失效
使用like配合通配符也会失效(解决办法:使用覆盖索引,查询的字段不为*即可)
不要在查询时,在索引上做任何的操作
数据库索引在什么情况下失效
(1)条件中用or(这就是为什么少用or的原因)
(2)
对于多列(复合、联合)索引,不是使用的第一部分,则不会使用索引。(最左匹配原则或者叫做最左前缀原则)
比如:Index_SoftWareDetail索引包含(a,b,c) 三列,但是查询条件里面,没有a,b 列,只有c 列,那么 Index_SoftWareDetail索引也不起作用。
例如:bc c acb bac 都是不行的
(3)like的模糊查询以%开头,索引失效
(4)如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不会使用索引
(5)如果MySQL预计使用全表扫描要比使用索引快,则不使用索引
(6)判断索引列是否不等于某个值时。‘!=’操作符。比如:select * from SoftWareDetailInfo where SoftUseLine != 0
(7)
对索引列进行运算。这里运算包括±*/等运算。也包括使用函数。比如:
select * from SoftWareDetailInfo where SoftUseLine +0= 0
此时索引不起作用。
select * from SoftWareDetailInfo where count(SoftUseLine) = 0
此时索引也不起作用。
也就是说如果不是直接判断索引字段列,而是判断运算或其它函数处理后的索引列索引均不起作用。
(8)索引字段进行判空查询时。也就是对索引字段判断是否为NULL时。语句为is null 或is not null。
比如:select * from SoftWareDetailInfo where CreateTime is null 此时就不检索time字段上的索引表了。也就是索引在这条语句执行时失效了。
接着再执行select * from SoftWareDetailInfo where CreateTime = ‘2015-04-11 00:00:00’ 此时就会检索索引表了。索引又起作用了。
(9)范围列可以用到索引(联合索引必须是最左前缀),但是范围列后面的列无法用到索引
索引的优化
①尽量不要使用左模糊和全模糊,如果需要可以使用搜索引擎来解决
②union,in和or都可以命中索引,建议使用in
③负向条件查询不能使用索引,可以优化为in查询
负向条件查询有:!= <> not in not like等等
例如:select * from user where status!=1 and status!=2
优化为:select * from user where status in (0,3,4);
④合理使用联合索引的最左前缀原则
如果在(a,b,c)三个字段上建立联合索引,那么它能够加快 a | (a,b) | (a,b,c) 三组查询速度。
比如说把(username,password)建立了联合索引,因为业务上几乎没有password的单条件查询,而有很多username的单条件查询需求,所以应该建立(username,password)的联合索引,而不要建立(password,username)的联合索引
注意:(1)建立联合索引的时候,要把查询频率较高的列放在最左边
(2)如果建立了(a,b)索引,就不必再独立建立a索引。同理如果建立了(a,b,c)联合索引,就不必再独立建立a,(a,b)索引
(3)存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如 where a>? and b=?,那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。
(4)最左前缀原则,并不是要求where后的顺序和联合索引的一致。下面的 SQL 语句也可以命中 (login_name, passwd) 这个联合索引。
selectuid, login_time from user where passwd=? andlogin_name=?
但还是建议 where 后的顺序和联合索引一致,养成好习惯。
⑤把计算放到业务层而不是数据库层。(因为对索引列进行运算,不能命中索引)
⑥表数据比较少、更新十分频繁、数据区分度不高的字段上不宜建立索引。
一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算。
⑦强制类型转换会全表扫描
例如:如果phone字段是varchar类型,则下面的sql不能命中索引
select * from user where phone = 18838003017
可以优化为:select * from user where phone = ‘18838003017’
⑧利用覆盖索引进行查询操作,避免回表
select uid,login_time from user where username=? and password=?
如果建立了(username,password,login_time)的联合索引,由于login_time已经建立在索引中了,被查询的username和password就不用去row上获取数据了,从而加速查询
⑨在order by和group by中要注意索引的有序性
如果order by是组合索引的一部分,应该将该字段放在组合索引的最后
例如:where a=? and b=? order by c ->可以建立联合索引(a,b,c)
如果索引中有范围查找,则索引的有序性无法利用
例如:where a>10 order by b ->索引(a,b)无法排序
⑩建立索引的列,不许为null
单列索引不存 null 值,复合索引不存全为 null 的值,如果列允许为 null,可能会得到“不符合预期”的结果集,所以,请使用 not null 约束以及默认值。
sql语句的优化
①能用到索引尽量用到索引.对索引的优化实际上就是sql语句的调优
②任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
③尽量使用where,而不要使用having
④尽量使用多表查询,不要使用子查询
⑤where后的and.or左右执行顺序是从右至左
运算符为and时–尽量把为假的放在右边
运算符为or时–尽量把为真的放在右边
学习地址
https://blog.csdn.net/qq_36071795/article/details/83956068
JVM系列
JVM加载class文件的原理
执行java程序的步骤
1)源文件:编写Java源文件(我们也称之为源代码文件),它的扩展名为.java;
2)编译:然后通过编译器把源文件编译成字节码文件,字节码文件扩展名为.class;
3)运行:最后使用解释器来进行将字节码文件翻译成机器可识别的二进制机器码,最后运行。
类加载的主要步骤:
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
通过类加载器将class文件读入内存,并为之创建一个Class对象。
连接
检查 检查待加载文件的正确性
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用(这一步是可选的)
初始化
对静态变量和静态代码块执行初始化操作
类加载器的组成
Bootstrap ClassLoader 系统类加载器
负责Java核心类的加载比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。在JDK中JRE的lib目录下ext目录
AppClassLoader负责加载应用类
classpath指定的目录或jar包中的类
Java的内存模型(JVM的内存划分)
线程共享区域
线程共享区:堆和方法区
线程独占区:栈,本地方法区和程序计数器
堆
存放内容:
- 存放的是new出来的东西(对象实例)
- 被final修饰的局部变量.
划分区域:
java堆是垃圾收集器管理的主要区域,因此很多时候被称为”GC堆”,
java堆还可以细分为新生代和老年代,默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2,
1)新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。默认的,Eden : from : to = 8 : 1 : 1,JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。
2)因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。如果堆中没有内存来完成实例分配,并且堆无法再扩展时,会抛出OutOfMemoryError
方法区
用于存储已被虚拟机加载的类信息(类的版本,字段(成员变量),方法,接口),常量,静态变量和编译器编译后生成的.class文件.
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
栈
存放的是局部变量
如何判断一个对象是否是垃圾对象
引用计数算法
算法原理:
- 给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;
- 当引用失效时,计数器值就减1;
- 计数器为0,说明对象没有被使用
缺点:
但是主流的java虚拟机里面没有选用引用计数法来管理内存,
其中最重要的原因:很难解决对象之间互相循环引用的问题
(objA和objB都有字段instance,赋值令objA.instance=ObjB和objB.instance=objA,除此之外,两者再无任何引用,但是因为它们互相引用着对方,导致它们的引用计数都不为0,于是引用计数算法无法通知GC收集器回收它们)
可达性分析算法
通过一系列的称为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,
有点像广度搜索,回溯法
当一个对象到GC Roots没有任何引用链相连,则证明此对象是不可用的
垃圾回收算法
标记-清除算法(Mark-Sweep)
1、标记出所有需要回收的对象
2、在标记完成后统一回收所有被标记的对象
缺点:一个是效率问题,标记和清除两个过程的效率都不高;
另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中
需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
复制算法(Copying)
1、将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。
2、当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
优点:这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
缺点:这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低
标记-整理算法(Mark-Compact)
1、标记过程仍然和”标记-清除算法一样”
2、让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
分代收集算法(Generational Collection)
1、根据对象存活周期的不同将内存划分为几块。
2、一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
3、在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法
4、老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清除”或者“标记—整理”算法来进行回收。
JVM内存模型1.7和1.8的区别
方法区是 JVM 的规范,而永久代是这种规范的一种实现
1.8:元数据区
1.7:永久代
1.8版本用元数据区取代了1.7版本及以前的永久代。
元数据区和永久代本质上都是方法区的实现。
元空间与永久代之间最大的区别在于:
元空间并不在虚拟机中,而是使用本地内存(也就是说jvm可以使用外边的内存)。
因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小
什么是GC
GC就是垃圾回收,它的主要作用就是回收程序中不再使用的内存.
是否可以主动通知jvm进行垃圾回收?
1.不能实时调用垃圾回收器对某个对象或者所有对象进行垃圾回收.
2.但是可以通过System.gc()方法来通知垃圾回收器运行,当然,jvm也并不保证垃圾回收器马上就会运行.由于System.gc()方法的执行会停止所有的响应,去检查内存是否有可回收的对象,对程序的正常运行和性能造成了威胁,所以该方法不能频繁使用
自定义类加载器(没学)
双亲委派模型(没学)
Minor GC和Full GC(没学)
1、大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次MinorGC。
2、执行GC后,将存活的对象分配到Survivor空间
3、无法放到Survivor空间的对象,分配到老年代
4、分配到Survivor的对象,经过多次Minor GC后,进入老年代
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GCMajor GC的速度一般会比Minor GC慢10倍以上。
Eden区空间不足,触发MinorGC;老年代空间不足,触发Full GC
学习地址
https://blog.csdn.net/qq_36071795/category_8297314.html
数据库的索引结构
数据库中索引使用的结构为BTree索引结构和哈希结构。而哈希结构底层使用哈希表,当我们是查询单挑记录时,使用哈希结构,查询速度快,其他场景使用BTree索引结构。
Hash索引:
- 根据name来创建索引,每个name对应的一个hash值,相当于一个键值对,存在一个哈希表里面,然后,通过哈希值,在对比哈希表可以找到需要的name。
- 当用户发起查询用户名称时,根据name去对应的哈希表里进行查找,找到对应的哈希值,在拿哈希值去对应的数据表获取用户想要的数据。
使用BTree索引结构实现了两种搜索引擎,innoDB和MYISAM
MYISAM:
- 使用B树里的B+树作为索引结构,其索引用的是非主键索引,辅助索引,二级索引
- 1.根据辅助索引找到对应的主键索引,在根据主键索引找到对应的数据行
- 2.其叶子节点的data域存储了数据存储地址,首先根据BTree的搜索索引的算法,进行搜索,如果对应的key存在,表示找到了数据存在,则获取当前叶子节点的data域里的地址值,从数据文件中读取相应的记录。
- 在MYISAM这种搜索引擎的存储结构中,数据文件和索引文件是相互分离的,而索引文件(B+树索引结构)存储了一个指向数据的指针。我们称这种索引为非聚集索引,类似LinkList,链表结构
innoDB:
根据图片,我们可以发现innoDB的索引结构也是使用的B+树,但是实现方式却完全不一样。
在innoDB结构中,索引文件和数据文件不分离的,这棵树的叶子节点data域包含了所有的数据信息,
我们只需要根据B+树的索引搜索方法进行搜索即可,找到对应的数据
索引的key也就是这个数据表的主键。我们称这种索引为聚集索引,类似为ArrayList,数组结构。
参考地址:https://blog.csdn.net/qq_27607965/article/details/79925288
https://www.cnblogs.com/liqiangchn/p/12432236.html (非常详细!!!!!!!!!!)
对象的创建和销毁
https://blog.csdn.net/qq_36071795/article/details/83956068
学习网址
https://blog.csdn.net/u014297473/article/details/45825663
https://www.cnblogs.com/mq0036/p/4155136.html