题目答案总结并不是标准,仅供参考,若是有错误或者更好的看法,欢迎留言讨论!java
一、什么是事务?事务的特性(ACID)mysql
什么是事务:事务是程序中一系列严密的操做,全部操做执行必须成功完成,不然在每一个操做所作的更改将会被撤销,这也是事务的原子性(要么成功,要么失败)。程序员
事务特性分为四个:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持续性(Durability)简称ACID。面试
一、原子性:事务是数据库的逻辑工做单位,事务中包含的各操做要么都作,要么都不作。算法
二、一致性:事务执行的结果必须是使数据库从一个一致性状态变到另外一个一致性状态。所以当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。若是数据库系统运行中发生故障,有些事务还没有完成就被迫中断,这些未完成事务对数据库所作的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。sql
三、隔离性:一个事务的执行不能其它事务干扰。即一个事务内部的操做及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。数据库
四、持久性:也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操做或故障不该该对其执行结果有任何影响。json
二、事务的隔离级别有几种,最经常使用的隔离级别是哪两种?数组
并发过程当中会出现的问题:缓存
事务的隔离级别有4种:
一、未提交读(Read uncommitted)
二、提交读(Read committed)
三、可重复读(Repeatable read)
四、串行化(Serializable)
三、分布式缓存的典型应用场景?
四、MongoDB与Mysql的区别?
两种数据库的区别:
Mongodb的鲜明特征:
Mongodb的优点:
Mongodb的缺陷:
Mongodb的应用场景:
不适用的场景:
关系型数据库和非关系型数据库的应用场景对比:
关系型数据库适合存储结构化数据,如用户的账号、地址:
NoSQL适合存储非结构化数据,如文章、评论:
五、Mysql索引相关问题。
1)什么是索引?
2)索引具体采用的哪一种数据结构呢?
3)InnoDb内存使用机制?
Innodb体系结构如图所示:
Innodb关于查询效率有影响的两个比较重要的参数分别是innodb_buffer_pool_size,innodb_read_ahead_threshold:
能够看出来,Mysql的缓冲池机制是能充分利用内存且有预加载机制,在某些条件下目标数据彻底在内存中,也可以具有很是好的查询性能。
4)B+ Tree索引和Hash索引区别?
5)B+ Tree的叶子节点均可以存哪些东西吗?
6)这二者有什么区别吗?
7)聚簇索引和非聚簇索引,在查询数据的时候有区别吗?
8)主键索引查询只会查一次,而非主键索引须要回表查询屡次(这个过程叫作回表)。是全部状况都是这样的吗?非主键索引必定会查询屡次吗?
覆盖索引(covering index)指一个查询语句的执行只用从索引中就可以取得,没必要从数据表中读取。也能够称之为实现了索引覆盖。当一条查询语句符合覆盖索引条件时,MySQL只须要经过索引就能够返回查询所须要的数据,这样避免了查到索引后再返回表操做,减小I/O提升效率。
如,表covering_index_sample中有一个普通索引 idx_key1_key2(key1,key2)。当咱们经过SQL语句:select key2 from covering_index_sample where key1 = 'keytest';的时候,就能够经过覆盖索引查询,无需回表。
9)在建立索引的时候都会考虑哪些因素呢?
通常对于查询几率比较高,常常做为where条件的字段设置索引。
10)在建立联合索引的时候,须要作联合索引多个字段之间顺序,这是如何选择的呢?
在建立多列索引时,咱们根据业务需求,where子句中使用最频繁的一列放在最左边,由于MySQL索引查询会遵循最左前缀匹配的原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。
因此当咱们建立一个联合索引的时候,如(key1,key2,key3),至关于建立了(key1)、(key1,key2)和(key1,key2,key3)三个索引,这就是最左匹配原则。
11)你知道在MySQL 5.6中,对索引作了哪些优化吗?
12)如何知道索引是否生效?
explain显示了MySQL如何使用索引来处理select语句以及链接表。能够帮助选择更好的索引和写出更优化的查询语句。使用方法,在select语句前加上explain就能够了。
13)那什么状况下会发生明明建立了索引,可是执行的时候并无经过索引呢?
在一条单表查询语句真正执行以前,MySQL的查询优化器会找出执行该语句全部可能使用的方案,对比以后找出成本最低的方案。这个成本最低的方案就是所谓的执行计划。优化过程大体以下:
14)为何索引结构默认使用B+Tree,而不是Hash,二叉树,红黑树?
六、如何优化MySQL?
MySQL优化大体能够分为三部分:索引的优化、SQL语句优化和表的优化
索引优化能够遵循如下几个原则:
SQL语句优化,能够经过explain查看SQL的执行计划,优化语句原则能够有:
数据库表优化
七、为何任何查询都不要使用SELECT *?
Java实现多线程有几种方式?
有三种方式:
线程的生命周期
一、新建状态(New):新建立了一个线程对象。
二、就绪状态(Runnable):线程对象建立后,其余线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
三、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
四、阻塞状态(Blocked):阻塞状态是线程由于某种缘由放弃CPU使用权,暂时中止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的状况分三种:
五、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
start()方法和run()方法的区别?
Runnable接口和Callable接口的区别?
volatile关键字
volatile基本介绍:volatile能够当作是synchronized的一种轻量级的实现,但volatile并不能彻底代替synchronized,volatile有synchronized可见性的特性,但没有synchronized原子性的特性。可见性即用volatile关键字修饰的成员变量代表该变量不存在工做线程的副本,线程每次直接都从主内存中读取,每次读取的都是最新的值,这也就保证了变量对其余线程的可见性。另外,使用volatile还能确保变量不能被重排序,保证了有序性。
当一个变量定义为volatile以后,它将具有两种特性:
volatile boolean isOK = false;
//假设如下代码在线程A执行
A.init();
isOK=true;
//假设如下代码在线程B执行
while(!isOK){
sleep();
}
B.init();
复制代码
A线程在初始化的时候,B线程处于睡眠状态,等待A线程完成初始化的时候才可以进行本身的初始化。这里的前后关系依赖于isOK这个变量。若是没有volatile修饰isOK这个变量,那么isOK的赋值就可能出如今A.init()以前(指令重排序,Java虚拟机的一种优化措施),此时A没有初始化,而B的初始化就破坏了它们以前造成的那种依赖关系,可能就会出错。
volatile使用场景:
若是正确使用volatile的话,必须依赖下如下种条件:
在如下两种状况下都必须使用volatile:
什么是线程安全?
若是你的代码在多线程下执行和在单线程下执行永远都能得到同样的结果,那么你的代码就是线程安全的。
线程安全的级别:
sleep方法和wait方法有什么区别?
写一个会致使死锁的程序。
public class MyThread{
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (lock1){
System.out.println("thread1 get lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println("thread1 get lock2");
}
System.out.println("thread1 end");
}
}).start();
new Thread(()->{
synchronized (lock2){
System.out.println("thread2 get lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println("thread2 get lock1");
}
System.out.println("thread2 end");
}
}).start();
}
}
复制代码
类加载过程
一、类加载过程:加载->连接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载
具体过程以下:
1)加载:首先经过一个类的全限定名来获取此类的二进制字节流;其次将这个字节流所表明的静态存储结构转化为方法区的运行时数据结构;最后在java堆中生成一个表明这个类的Class对象,做为方法区这些数据的访问入口。总的来讲就是查找并加载类的二进制数据。
2)连接:
验证:确保被加载类的正确性。
准备:为类的静态变量分配内存,并将其初始化为默认值。
解析:把类中的符号引用转换为直接引用。
直接引用能够是:
为何要使用符号引用?
符号引用要转换成直接引用才有效,这也说明直接引用的效率要比符号引用高。那为何要用符号引用呢?这是由于类加载以前,javac会将源代码编译成.class文件,这个时候javac是不知道被编译的类中所引用的类、方法或者变量他们的引用地址在哪里,因此只能用符号引用来表示,固然,符号引用是要遵循java虚拟机规范的。
还有一种状况须要用符号引用,就例如前文举得变量的符号引用的例子,是为了逻辑清晰和代码的可读性。
3)为类的静态变量赋予正确的初始值。
二、类的初始化
1)类何时才被初始化:
2)类的初始化顺序
三、类的加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,而后在堆区建立一个这个类的java.lang.Class对象,用来封装类在方法区类的对象。如:
类的加载的最终产品是位于堆区中的Class对象。Class对象封装了类在方法区内的数据结构,而且向Java程序员提供了访问方法区内的数据结构的接口。加载类的方式有如下几种:
四、加载器
JVM的类加载是经过ClassLoader及其子类来完成的,类的层次关系和加载顺序能够由下图来描述:
加载器介绍:
1)BootstrapClassLoader(启动类加载器):
负责加载JAVA_HOME中jre/lib/rt.jar里全部的class,加载System.getProperty(“sun.boot.class.path”)所指定的路径或jar。
2)ExtensionClassLoader(标准扩展类加载器):
负责加载java平台中扩展功能的一些jar包,包括JAVAHOME中jre/lib/rt.jar里全部的class,加载System.getProperty(“sun.boot.class.path”)所指定的路径或jar。2)ExtensionClassLoader(标准扩展类加载器):负责加载java平台中扩展功能的一些jar包,包括JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包。载System.getProperty(“java.ext.dirs”)所指定的路径或jar。
3)AppClassLoader(系统类加载器):
负责加载classpath中指定的jar包及目录中class。
4)CustomClassLoader(自定义加载器):
属于应用程序根据自身须要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现。
类加载器的顺序
五、类加载器之双亲委派模型
垃圾回收机制
Java内存区域划分
咱们先来看看Java的内存区域划分状况,以下图所示:
私有内存区的区域名和相应的特性以下表所示:
虚拟机栈中的局部变量表里面存放了三个信息:
这个returnAddress和程序计数器有什么区别?前者是指示JVM的指令执行到了哪一行,后者是指你的代码执行到哪一行。
共享内存区(接下来主要讲jdk1.7)的区域名和相应的特性以下表所示:
哪些内存须要回收?
私有内存区伴随着线程的产生而产生,一旦线程停止,私有内存区也会自动消除,所以咱们在本文中讨论的内存回收主要是针对共享内存区。
Java堆
新生代GC(Minor GC):指发生在新生代的垃圾收集动做,由于Java对象大都具有朝生夕灭的特性,因此Minor GC很是频繁,通常回收速度也比较快。
老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,常常会伴随至少一次的Minor GC (但非绝对,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度通常会比Minor GC慢10倍以上。
新生代:刚刚新建的对象在Eden中,经历一次Minor GC, Eden中的存活对象就被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC, Eden和S0中的存活对象会被复制送入第二块survivor space S1。S0和Eden被清空,而后下一轮S0与S1交换角色,如此循环往复。若是对象的复制次数达到16次,该对象就被送到老年代中。
为何新生代内存须要有两个Sruvivor区:
先不去想为何有两个Survivor区,第一个问题是,设置Survivor区的意义在哪里?
若是没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC(由于Major GC通常伴随着Minor GC,也能够看作触发了Full GC)。老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多。你也许会问,执行时间长有什么坏处?频发的Full GC消耗的时间是很是可观的,这一点会影响大型程序的执行和响应速度,更不要说某些链接会由于超时发生链接错误了。那咱们来想一想在没有Survivor的状况下,有没有什么解决办法,能够避免上述状况:
显而易见,没有Survivor的话,上述两种解决方案都不能从根本上解决问题。咱们能够获得第一条结论:Survivor的存在乎义,就是减小被送到老年代的对象,进而减小Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,下面咱们来分析一下。为何一个Survivor区不行?
第一部分中,咱们知道了必须设置Survivor区。假设如今只有一个survivor区,咱们来模拟一下流程:
刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,若是此时把Eden区的存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就致使了内存碎片化。
那么,瓜熟蒂落的,应该创建两块Survivor区,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程很是重要,由于这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。S0和Eden被清空,而后下一轮S0与S1交换角色,如此循环往复。若是对象的复制次数达到16次,该对象就会被送到老年代中。
老年代:若是某个对象经历了几回垃圾回收以后还存活,就会被存放到老年代中。老年代的空间通常比新生代大。
这个流程以下图所示:
何时回收?
Java并无给咱们提供明确的代码来标注一块内存并将其回收。或许你会说,咱们能够将相关对象设为null或者用System.gc()。然而,后者将会严重影响代码的性能,由于每一次显示调用system.gc()都会中止全部响应,去检查内存中是否有可回收的对象,这会对程序的正常运行形成极大威胁。
另外,调用该方法并不能保障JVM当即进行垃圾回收,仅仅是通知JVM要进行垃圾回收了,具体回收与否彻底由JVM决定。
生存仍是死亡
可达性算法:这个算法的基本思路是经过一系列的称为“GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的。
二次标记:在可达性分析算法中被判断是对象不可达时不必定会被垃圾回收机制回收,由于要真正宣告一个对象的死亡,必须经历两次标记的过程。
若是发现对象不可达时,将会进行第一次标记,此时若是该对象调用了finalize()方法,那么这个对象会被放置在一个叫F-Queue的队列之中,若是在此队列中该对象没有成功拯救本身(拯救本身的方法是该对象有没有被从新引用),
那么GC就会对F-Queue队列中的对象进行小规模的第二次标记,一旦被第二次标记的对象,将会被移除队列并等待被GC回收,因此finalize()方法是对象逃脱死亡命运的最后一次机会。
在Java语言中,可做为GC Roots的对象包括下面几种:
GC的算法
引用计数法(Reference Counting):
给对象添加一个引用计数器,每过一个引用计数器值就+1,少一个引用就-1。当它的引用变为0时,该对象就不能再被使用。它的实现简单,可是不能解决互相循环引用的问题。
优势:
缺点:
标记-清除(Mark-Sweep)算法:
分为两个阶段:首先标记出全部须要回收的对象,在标记完成后统一回收全部被标记的对象(后续的垃圾回收算法都是基于此算法进行改进的)。
缺点:效率问题,标记和清除两个过程的效率都不高;空间问题,会产生不少碎片。
复制算法:
将可用内存按容量划分为大小相等的两块,每次只用其中一块。当这一块用完了,就将还存活的对象复制到另一块上面,而后把原始空间所有回收。高效、简单。
缺点:将内存缩小为原来的一半。
标记-整理(Mark-Compat)算法
标记过程与标记-清除算法过程同样,但后面不是简单的清除,而是让全部存活的对象都向一端移动,而后直接清除掉端边界之外的内存。
分代收集(Generational Collection)算法
新生代中,每次垃圾收集时都有大批对象死去,只有少许存活,就选用复制算法,只须要付出少许存活对象的复制成本就能够完成收集。
老年代中,其存活率较高、没有额外空间对它进行分配担保,就应该使用“标记-整理”或“标记-清除”算法进行回收。
增量回收GC和并行回收GC这里就不作具体介绍了,有兴趣的朋友能够自行了解一下。
垃圾收集器
Serial收集器:单线程收集器,表示在它进行垃圾收集时,必须暂停其余全部的工做线程,直到它收集结束。"Stop The World"。
ParNew收集器:实际就是Serial收集器的多线程版本。
Parallel Scavenge收集器:该收集器比较关注吞吐量(Throughout)(CPU用于用户代码的时间与CPU总消耗时间的比值),保证吞吐量在一个可控的范围内。
CMS(Concurrent Mark Sweep)收集器:CMS收集器是一种以获取最短回收停顿时间为目标的垃圾收集器,是基于“标记——清除”算法实现的。
其回收过程主要分为四个步骤:
因为初始标记和从新标记速度比较快,其它工做线程停顿的时间几乎能够忽略不计,因此CMS的内存回收过程是与用户线程一块儿并发执行的。初始标记和从新标记两个步骤须要Stop the world;并发标记和并发清除两个步骤可与用户线程并发执行。“Stop the world”意思是垃圾收集器在进行垃圾回收时,会暂停其它全部工做线程,直到垃圾收集结束为止。
CMS的缺点:
G1(Garbage First)收集器:G1收集器是一款成熟的商用的垃圾收集器,是基于“标记——整理”算法实现的。
其回收过程主要分为四个步骤:
G1收集器的特色:
CMS收集器与G1收集器的区别:
JDK 1.8 JVM的变化
一、为何取消方法区
二、JDK 1.8里Perm区中的全部内容中字符串常量移至堆内存,其余内容如类元信息、字段、静态属性、方法、常量等都移动到元空间内。
三、元空间
元空间(MetaSpace)不在堆内存上,而是直接占用的本地内存。所以元空间的大小仅受本地内存限制
也可经过参数来设定元空间的大小:
除了上面两个指定大小的选项之外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC以后,最小的Metaspace剩余空间容量的百分比,减小为分配空间所致使的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC以后,最大的Metaspace剩余空间容量的百分比,减小为释放空间所致使的垃圾收集。
元空间的特色:
性能优化:
java自动装箱拆箱总结
当基本类型包装类与基本类型值进行==运算时,包装类会自动拆箱。即比较的是基本类型值。
具体实现上,是调用了Integer.intValue()方法实现拆箱。
int a = 1;
Integer b = 1;
Integer c = new Integer(1);
System.out.println(a == b); //true
System.out.println(a == c); //true
System.out.println(c == b); //false
Integer a = 1;
会调用这个 Integer a = Integer.valueOf(1);
Integer已经默认建立了数值【-128到127】的Integer常量池
Integer a = -128;
Integer b = -128;
System.out.println(a == b); //true
Integer a = 128;
Integer b = 128;
System.out.println(a == b); //false
Java的数学计算是在内存栈里操做的
c1 + c2 会进行拆箱,比较仍是基本类型
int a = 0;
Integer b1 = 1000;
Integer c1 = new Integer(1000);
Integer b2 = 0;
Integer c2 = new Integer(0);
System.out.println(b1 == b1 + b2); //true
System.out.println(c1 == c1 + c2); //true
System.out.println(b1 == b1 + a); //true
System.out.println(c1 == c1 + a); //true
复制代码