1. Java并发类:
1、ConcurrentHashMap
ConcurrentHashMap其实就是线程安全版本的hashMap。前面我们知道HashMap是以链表的形式存放hash冲突的数据,以数组形式存放HashEntry等hash出来不一致的数据。为了保证容器的数据一致性,需要加锁。HashMap的实现方式是,只有put和remove的时候会引发数据的不一致,那为了保证数据的一致性,我在put和remove的时候进行加锁操作。但是随之而来的是性能问题,因为key-value形式的数据,读写频繁是很正常的,也就意味着我有大量数据做读写操作时会引发长时间的等待。为了解决这个问题,Java并发包问我们提供了新的思路。在每一个HashEntry上加一把锁,对于hash冲突的数据,因为采用链表存储,公用一把锁。这样我才在做不同hash数值的数据时,则是在不同的锁环境下执行,基本上是互不干扰的。在最好情况下,可以保证16个线程同时进行无阻塞的操作(HashMap的默认HashEntry是16,亦即默认的数组大小是16)。
那ConcurrentHashMap是如何保证数据操作的一致性呢?对于数据元素的大小,ConcurrentHashMap将对应数组(HashEntry的长度)的变量为voliate类型的,也就是任何HashEntry发生变更,所有的地方都会知道数据的大小。对于元素,如何保证我取出的元素的next不发生变更呢?(HashEntry中的数据采用链表存储,当读取数据的时候可能又发生了变更),这一点,ConcurrentHashMap采取了最简单的做法,hash值、key和next取出后都为final类型的,其next等数据永远不会发生变更。
另外ConcurrentHashMap采用的锁结构是将读和写分开的,大大的提升了性能。
2.CopyOnWriteArrayList
CopyOnWriteArrayList是线程安全版本的ArrayList。和ArrayList不同的是,CopyOnWriteArrayList默认是创建了一个大小为0的容器。通过ReentrantLock来保证线程安全。CopyOnWriteArrayList其实每次增加的时候,需要新创建一个比原来容量+1大小的数组,然后拷贝原来的元素到新的数组中,同时将新插入的元素放在最末端。然后切换引用。
针对CopyOnWriteArrayList,因为每次做插入和删除操作,都需要重新开辟空间和复制数组元素,因此对于插入和删除元素,CopyOnWriteArrayList的性能远远不如ArrayList,但是每次读取的时候,CopyOnWriteArrayList在不加锁的情况下直接锁定数据,会快很多(但是可能会引发脏读:脏读又称无效数据的读出,是指在数据库访问中,事务T1将某一值修改,然后事务T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取到的数据是无效的,值得注意的是,脏读一般是针对于update操作的。),对于迭代,CopyOnWriteArrayList会生成一个快照数组,因此当迭代过程中出现变化,快照数据没有变更,因此读到的数据也是不会变化的。在读多写少的环境下,CopyOnWriteArrayList的性能还是不错的。
2.Spring的IOC以及AOP.
IOC(DI):把对象的创建权限交给Spring容器,让spring帮我们实例化对象,我们只是从spring容器中取得实例。
AOP:把一些非核心业务的代码抽取到一个通知类(增强),再创建需要被增强的类的代理对象,在调用代理对象的方法时,织入增强代码,并调用目标方法的一种面向切面技术,一种对OOP进行补充的编程方式。
3.Spring的Bean的加载过程.
Spring通过资源加载器加载相应的XML文件,使用读取器读取资源加载器中的文件到读取器中,在读取过程中,解析相应的xml文件元素,转化为spring定义的数据结BeanDefinition,把相应的BeanDefinition注册到注册表中。注册表中包含的BeanDefinition的数据结构,没有经过加工处理过,无法得到我们想要的bean对象。
我们如何得到Bean对象,spring都做了那些工作?BeanFactory提供了多种方式得到bean对象,getBean()方法是最核心得到bean对象
getBean主要由AbstractBeanFactory、AbstractAutowireCapableBeanFactory、以及DefaultListableBeanFactory实现
AbstractBeanFactory 实现了依赖关系处理
AbstractAutowireCapableBeanFactory 实现了bean的create过程
DefaultListableBeanFactory 实现了BeanDefinition的管理
以下是getBean方法的实现流程。
getBean经过方法重载后,最终调用的是doGetBean方法,
需要的方法参数如下:
1.name 你要得到bean对象的名称 不能为空
2.requiredType 这个bean对象的Class类型,可以为null
3.args 可以为null,如果有参数,则代表在找到这个bean定义后,通过构造方法或工厂方法或其他方法传入args参数来改变这个bean实例。
spring 工厂开始自动化处理了.
4. Spring的循环依赖处理方式.
所谓Spring的循环依赖,指的是这样一种场景:
当我们注入一个对象A时,需要注入对象A中标记了某些注解的属性,这些属性也就是对象A的依赖,把对象A中的依赖都初始化完成,对象A才算是创建成功。那么,如果对象A中有个属性是对象B,而且对象B中有个属性是对象A,那么对象A和对象B就算是循环依赖,如果不加处理,就会出现:创建对象A–>处理A的依赖B–>创建对象B–>处理B的对象A–>创建对象A–>处理A的依赖B–>创建对象B…这样无限的循环下去。
这事显然不靠谱。
Spring处理循环依赖的基本思路是这样的:
虽说要初始化一个Bean,必须要注入Bean里的依赖,才算初始化成功,但并不要求此时依赖的依赖也都注入成功,只要依赖对象的构造方法执行完了,这个依赖对象就算存在了,注入就算成功了,至于依赖的依赖,以后再初始化也来得及(参考Java的内存模型)。
因此,我们初始化一个Bean时,先调用Bean的构造方法,这个对象就在内存中存在了(对象里面的依赖还没有被注入),然后把这个对象保存下来,当循环依赖产生时,直接拿到之前保存的对象,于是循环依赖就被终止了,依赖注入也就顺利完成了。
举个例子:
假设对象A中有属性是对象B,对象B中也有属性是对象A,即A和B循环依赖。
创建对象A,调用A的构造,并把A保存下来。
然后准备注入对象A中的依赖,发现对象A依赖对象B,那么开始创建对象B。
调用B的构造,并把B保存下来。
然后准备注入B的构造,发现B依赖对象A,对象A之前已经创建了,直接获取A并把A注入B(注意此时的对象A还没有完全注入成功,对象A中的对象B还没有注入),于是B创建成功。
把创建成功的B注入A,于是A也创建成功了。
于是循环依赖就被解决了。
5.缓存穿透与缓存击穿问题
5.1缓存穿透(数据查询不到—>假数据、set集合放ID布隆过滤器过滤)
解决方案(防止mysql宕机)
在Redis中放入
1.假数据
2.set集合,里面放入所有mysql中的id,再通过布隆过滤器过滤,没有这个id的请求就不在mysql中找了
5.2、缓存击穿(热点数据到期—>访问一次加一次访问时间、加锁)
解决方案
1.从Redis处理:一个请求,给这个热点数据加一点时间(避免热点数据过期)
2.分布式锁:Tomcat集群synchronized-Tomcat分布式锁-Redis(避免大量数据访问数据库)
6、MySql存储引擎
数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以获得特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引擎。
因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(Table Type,即存储和操作此表的类型)。
InnoDB存储引擎
InnoDB是事务型数据库的首选引擎,通过上图也看到了,InnoDB是目前MYSQL的默认事务型引擎,是目前最重要、使用最广泛的存储引擎。支持事务安全表(ACID),支持行锁定和外键。InnoDB主要特性有:
1、InnoDB给MySQL提供了具有提交、回滚和崩溃恢复能力的事物安全(ACID兼容)存储引擎。InnoDB锁定在行级并且也在SELECT语句中提供一个类似Oracle的非锁定读。这些功能增加了多用户部署和性能。在SQL查询中,可以自由地将InnoDB类型的表和其他MySQL的表类型混合起来,甚至在同一个查询中也可以混合
2、InnoDB是为处理巨大数据量的最大性能设计。它的CPU效率可能是任何其他基于磁盘的关系型数据库引擎锁不能匹敌的
3、InnoDB存储引擎完全与MySQL服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池。InnoDB将它的表和索引在一个逻辑表空间中,表空间可以包含数个文件(或原始磁盘文件)。这与MyISAM表不同,比如在MyISAM表中每个表被存放在分离的文件中。InnoDB表可以是任何尺寸,即使在文件尺寸被限制为2GB的操作系统上
4、InnoDB支持外键完整性约束,存储表中的数据时,每张表的存储都按主键顺序存放,如果没有显示在表定义时指定主键,InnoDB会为每一行生成一个6字节的ROWID,并以此作为主键
5、InnoDB被用在众多需要高性能的大型数据库站点上
InnoDB不创建目录,使用InnoDB时,MySQL将在MySQL数据目录下创建一个名为ibdata1的10MB大小的自动扩展数据文件,以及两个名为ib_logfile0和ib_logfile1的5MB大小的日志文件。
场景:由于其支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。如果需要频繁的更新、删除操作的数据库,也可以选择InnoDB,因为支持事务的提交(commit)和回滚(rollback)。
MyISAM存储引擎
MyISAM基于ISAM存储引擎,并对其进行扩展。它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM拥有较高的插入、查询速度,但不支持事物和外键。
MyISAM主要特性有:
1、大文件(达到63位文件长度)在支持大文件的文件系统和操作系统上被支持
2、当把删除和更新及插入操作混合使用的时候,动态尺寸的行产生更少碎片。这要通过合并相邻被删除的块,以及若下一个块被删除,就扩展到下一块自动完成
3、每个MyISAM表最大索引数是64,这可以通过重新编译来改变。每个索引最大的列数是16
4、最大的键长度是1000字节,这也可以通过编译来改变,对于键长度超过250字节的情况,一个超过1024字节的键将被用上
5、BLOB和TEXT列可以被索引,支持FULLTEXT类型的索引,而InnoDB不支持这种类型的索引
6、NULL被允许在索引的列中,这个值占每个键的0~1个字节
7、所有数字键值以高字节优先被存储以允许一个更高的索引压缩
8、每个MyISAM类型的表都有一个AUTO_INCREMENT的内部列,当INSERT和UPDATE操作的时候该列被更新,同时AUTO_INCREMENT列将被刷新。所以说,MyISAM类型表的AUTO_INCREMENT列更新比InnoDB类型的AUTO_INCREMENT更快
9、可以把数据文件和索引文件放在不同目录
10、每个字符列可以有不同的字符集
11、有VARCHAR的表可以固定或动态记录长度
12、VARCHAR和CHAR列可以多达64KB
存储格式:
1、静态表(默认):字段都是非变长的(每个记录都是固定长度的)。存储非常迅速、容易缓存,出现故障容易恢复;占用空间通常比动态表多。
2、动态表:占用的空间相对较少,但是频繁的更新删除记录会产生碎片,需要定期执行optimize table或myisamchk -r命令来改善性能,而且出现故障的时候恢复比较困难。
3、压缩表:使用myisampack工具创建,占用非常小的磁盘空间。因为每个记录是被单独压缩的,所以只有非常小的访问开支。
静态表的数据在存储的时候会按照列的宽度定义补足空格,在返回数据给应用之前去掉这些空格。如果需要保存的内容后面本来就有空格,在返回结果的时候也会被去掉。(其实是数据类型char的行为,动态表中若有这个数据类型也同样会有这个问题)
使用MyISAM引擎创建数据库,将产生3个文件。文件的名字以表名字开始,扩展名之处文件类型:frm文件存储表定义、数据文件的扩展名为.MYD(MYData)、索引文件的扩展名时.MYI(MYIndex)。
场景:如果表主要是用于插入新记录和读出记录,那么选择MyISAM能实现处理高效率。
MERGE存储引擎
MERGE存储引擎是一组MyISAM表的组合,这些MyISAM表结构必须完全相同,尽管其使用不如其它引擎突出,但是在某些情况下非常有用。
7.Mybatis的一级缓存和二级缓存的区别?
一级缓存(默认开启)
SqlSession级别的缓存,实现在同一个会话中数据的共享.
基于sqlSession默认开启,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据。不同的SqlSession之间的缓存数据区域是互相不影响的。
二级缓存
SqlSessionFactory级别的缓存,实现不同会话中数据的共享,是一个全局变量。
不同的sqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存,第二次查询会从缓存中获取数据,不再去底层数据库查询,从而提高效率。
8.HashMap
HashMap刚创建时,table是null,为了节省空间,当添加第一个元素时,table容量 调整为16,当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的2倍。目的是 减少调整元素的个数,jdk1.8 当每个链表长度大于8,并且数组元素个数大于等于64时,会调整为红黑树,目 的提高执行效率,jdk1.8 当链表长度小于等于6时,调整成链表,jdk1.8以前,链表是头插入,jdk1.8以后是尾插入
HashMap的安全问题
jdk1.7及之前的HashMap
在HashMap扩容的是时候会调用resize()方法中的transfer()方法,在这里由于是头插法所以在多线程情况下可能出现循环链表,所以后面的数据定位到这条链表的时候会造成数据丢失。和读取的可能导致死循环。
jdk1.8HashMap
1.8的HashMap对此做了优化,resize采用了尾插法,即不改变原来链表的顺序,所以不会出现1.7的循环链表的问题。但是它也不是线程线程安全的。不安全性如下:
在多线程情况下put时计算出的插入的数组下标可能是相同的,这时可能出现值的覆盖从而导致size也是不准确的。
9. final , finally , finalize区别
final修饰成员变量,成员方法,类
finally修饰代码块
finalize是Object类中的一个方法 -> 复活币 × 1
10.static关键字修饰什么
内部类
方法
变量
代码块
导包
11.volatile关键字
1.被volatile修饰的变量保证对所有线程可见。
2.禁止指令重排优化
12.CAS乐观锁(比较和交换)
CAS介绍(compare and swap比较和交换):
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS是一种非阻塞式的同步方式。
CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
CAS导致ABA问题:添加版本
线程1准备用CAS将变量的值由A替换为B,在此之前,线程2将变量的值由A替换为C,又由C替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功。但实际上这时的现场已经和最初不同了,尽管CAS成功,但可能存在潜藏的问题,如果CAS次数过多,会额外损耗CPU性能
解决ABA问题:增加版本号
13.GC的垃圾回收
1.JVM内存分代模型
年轻代和老年代
JVM将堆内存划分为了两个区域,即年轻代和老年代。年轻代主要存放的是创建和使用完即将被回收的对象,老年代存放的是一些长期被使用的对象。
2.确定是否回收
1、引用计数算法
判断对象的引用数量
通过判断对象的引用数量来决定对象是否可以被回收;
每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1;
任何引用计数为0的对象实例可以被当作垃圾收集。
优点:执行效率高,程序执行受影响较小。
缺点:无发检测出循环引用的情况,导致内存泄露。
2.可达性分析算法
有一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径被称
为“引用链”。
如果一个对象到GC Roots没有任何引用链相连接时,说明这个对象是不可用的。如果一个对象
到GC Roots有引用链相连接时,说明这个对象是可用的。
也就是说,给定一个集合的引用作为根节点出发,通过引用关系遍历对象图,能够遍历到的对象
就被判定为存活,不能够被遍历到的对象说明对象死亡。
3.常用垃圾算法
1.复制算法(新生代回收算法)
复制算法主要运用在新生代中,把新生代内存划分为两块内存区域,然后只使用其中一块,待
那块内存快满的时候,就把里面的存活对象一次性转移到另外一块内存区域,然后回收原来那
块的垃圾对象。
整个垃圾回收的流程就是,刚开始创建的对象都是在Eden区域的,如果Eden区域快满了,就会
触发垃圾回收。Eden区把存活的对象都转移到空着的S1区域,接着Eden区就会被请客。然后再
次分配对象到Eden区中直到满了进行下一次垃圾回收,这时会将S1中存活的对象和Eden区存活
的对象转移到空的Survivor区中(S2) 。这就是为什么新生代会被划分为8:1:1的结构了,
这样对内存的利用率会大大提升。
2.标记清除法
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,
再扫描整个空间中未被标记的对象,进行回收,如下图所示。标记-清除算法不需要进行对
象的移动,只需对不存活的对象进行处理,在存活对象比较多的情况下极为高效,但由于标
记-清除算法直接回收不存活的对象,因此会造成内存碎片。
3.标记整理算法(老年代回收算法)
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活
的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整
理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存
碎片的问题。
15.class文件是如何加载到JVM内存的
各个加载器的工作责任:
1)Bootstrap ClassLoader:负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
2)Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
3)App ClassLoader:负责记载classpath中指定的jar包及目录中class
工作过程:
1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载
请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加
载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class)
,会使用ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载
5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException
这就是所谓的双亲委派模型。简单来说:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
好处:防止内存中出现多份同样的字节码(安全性角度)
特别说明:类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。
类加载详细过程
加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象。
连接,连接又包含三块内容:验证、准备、解析。
1)验证,文件格式、元数据、字节码、符号引用验证;
2)准备,为类的静态变量分配内存,并将其初始化为默认值;
3)解析,把类中的符号引用转换为直接引用
初始化,为类的静态变量赋予正确的初始值。
16.JVM内存模型(待完善)
1.程序计数器(PC寄存器):
程序计数器是一块较小的内存空间,是当前线程正在执行的哪一条字节码指令的地址,若当前
线程正在执行的是一个本地方法
2.Java虚拟机栈(待完善)
描述Java方法运行过程的内存模型,保存局部变量、基本数据类型以及堆内存中对象的引用变量
随着线程创建而创建,随着线程的结束而销毁
3.本地方法栈
为JVM提供使用native方法的服务,本地方法栈描述本地方法运行过程的内存模型
也会抛出StackOverFlowError和OutOfMemoryError异常
4.堆
线程共享、垃圾回收的主要场地,在虚拟机启动的时候就被创建
堆这块区域是JVM中最大的,堆内存的大小是可以调节的
5.方法区
线程共享、 存储的是类信息+普通常量+静态常量+编译器编译后的代码等,
常量池(Constant Pool)是方法区的一部分
16.1虚拟机栈如何执行方法的
16.2方法区在JDK各个版本的更新
17.SQL语句的优化
sql本身优化,避免全盘扫描
18.聚簇索引和非聚簇索引
主键索引:
其他索引:
19.索引的常见结构
hash:数据量不大,效率远高于BTree
BTree,B+Tree:数据量大,更加稳定
树高问题,叶子节点指针
20.ES保存数据后,为什么不能立即查看到
我们经常有这样的需求,在对 Elasticsearch 数据进行操作的时候,要及时返回刚刚操作完毕的数据,或者数据列表。
比如加入存储一条数据后,我马上要返回数据的总条数,这个时候,会出问题,Elasticsearch会返回操作之前的数据,也就是假如开始有500条数据,我Insert了一条进去,按道理来说应该是501条,但是这个时候查询会发现,只有500条数据,再次请求又得到501条数据。
BulkRequestBuilder bulkRequest = ESTools.client.prepareBulk().setRefresh(true);
这里的setRefresh(true);
就是自动刷新的用处。所以在我们CRUD的时候,如果对数据增删改操作的时候,如果要及时返回最新数据,那么我们就需要加这个方法,及时刷新数据。