给大家总结一份非常全面的Java面试题及答案,涵盖所有主流的119道 Java面试题及答案。@mikechen
Java集合面试题及答案
Java集合框架有哪些?
Java集合类主要由两个根接口Collection和Map派生出来的。
Collection代表一组对象,每一个对象都是它的子元素;
Set不包含重复元素的Collection,包含:HashSet、TreeSet、LinkedHashSet等。
List有顺序的collection,并且可以包含重复元素,常见包含:ArrayList、LinkedList等。
Map可以把键(key)映射到值(value)的对象,键不能重复,常见包含:HashMap、TreeMap、Hashtable等。
List集合框架有哪些?
List接口常用的实现类有:ArrayList、LinkedList、Vector。
ArrayList、Vector、LinkedList优劣势比较?
1)ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
2)Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低
3)LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
HashMap的数据结构
HashMap的数据结构:数组+链表+红黑树(JDK1.8增加了红黑树部分)的数据结构。
如下所示:
为什么HashMap1.8需要引入红黑树(重点)
1.7HashMap集合中,当我们发生了Hash冲突,则会存放在同一个链表中,当链表的查询长度过长,查询效率非常低,因为采用链表存放查询的时间复杂度是为O(n),从头查询到尾部、在JDK1.8开始优化当数组容量>=64且链表长度>8则会将链表转化为改为红黑树,红黑树的时间复杂度为O(logn),性能有所提升。
HashMap1.8链表在什么时候转化成红黑树
当数组的容量大于=64且链表长度大于8则会将链表转化成红黑树。
红黑树查询的时间复杂度是为O(logN)
当红黑树的节点个数<6则将红黑树转换成链表
HashMap如何解决Hash冲突问题
JDK1.7版本的HashMap
1.根据key的hash值,计算该key存放在数组的index位置
2.如果发生index冲突,则会使用单向链表存放
同一个链表中存放的都是hashCode值相同,但是内容值却不同
JDK1.8版本的HashMap
1.根据key的hash值,计算该key存放在数组的index位置
2.如果发生index冲突,则会使用单向链表存放,当数组的容量大于=64且链表长度大于8则会将链表转化成红黑树。
根据链表查询key的时间复杂度就是为O(n)—从头查询到尾部
Collection框架有哪些?
1)List
List代表了有序可重复集合,可直接根据元素的索引来访问
2)Set
Set代表无序不可重复集合,只能根据元素本身来访问
3)Queue
Queue是队列集合
Java集合常用Set?
Set接口常用的实现类有:HashSet、LinkedHashSet、TreeSet
Java集合常用的Map?
常见包含:HashMap、TreeMap、Hashtable等,重点要掌握好HashMap。
HashMap与HashTable之间的区别
1.HashMap线程不安全、HashTable线程安全,但是使用HashTable在多线程的情况下效率比较偏低,所以在多线程的情况下使用ConcurrentHashMap;
2.多线程的情况下使用HashTable能够保证数据安全性,是采用synchronized锁将整个HashTable中的数组锁住,在多个线程中只允许一个线程访问Put或者Get,效率非常低。
3.多线程的情况下使用HashMap线程不安全,没有上锁,可能会发生一些数据冲突问题,但是效率比较高的。
4.HashMap可以允许存放key值为null,存放在数组第0个位置、HashTable不允许存放的key为null
补充概念“线程安全问题” 多个线程同时访问一个全局共享变量 可能会发生线程安全问题。
Java多线程面试题及答案
进程、线程、多线的区别?
很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。
进程
在操作系统中运行的程序就是进程,比如你的QQ、播放器、游戏、IDE等等
线程
一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕,等等。
多线程
多线程:多个线程并发执行。
什么是同步?
同步
Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。
比如:synchronized关键字,在保证结果准确的同时,提高性能,线程安全的优先级高于性能。
线程的同步方式有哪些?
线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏,线程的同步是保证多线程安全访问竞争资源的一种手段。
1.普通同步方法
锁是当前实例对象 ,进入同步代码前要获得当前实例的锁。
/** * 用在普通方法 */ private synchronized void synchronizedMethod() { System.out.println("--synchronizedMethod start--"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("--synchronizedMethod end--"); }
2.静态同步方法
锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁。
/** * 用在静态方法 */ private synchronized static void synchronizedStaticMethod() { System.out.println("synchronizedStaticMethod start"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("synchronizedStaticMethod end"); }
3.同步方法块
锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
/** * 用在类 */ private void synchronizedClass() { synchronized (SynchronizedTest.class) { System.out.println("synchronizedClass start"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("synchronizedClass end"); } }
4.synchronized底层
什么是并行?
并行
多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
什么是并发?
并发
通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。
并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
线程状态的控制?
可以对照上面的线程状态流转图来看具体的方法,这样更清楚具体作用:
1.start()
启动当前线程, 调用当前线程的run()方法
2.run()
通常需要重写Thread类中的此方法, 将创建的线程要执行的操作声明在此方法中
3.yield()
释放当前CPU的执行权
4.join()
在线程a中调用线程b的join(), 此时线程a进入阻塞状态, 知道线程b完全执行完以后, 线程a才结束阻塞状态
5.sleep(long militime)
让线程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态
6.wait()
一旦执行此方法,当前线程就会进入阻塞,一旦执行wait()会释放同步监视器。
7.sleep()和wait()的异同
相同点:两个方法一旦执行,都可以让线程进入阻塞状态。
不同点:
1) 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2) 调用要求不同:sleep()可以在任何需要的场景下调用。wait()必须在同步代码块中调用。
2) 关于是否释放同步监视器:如果两个方法都使用在同步代码块呵呵同步方法中,sleep不会释放锁,wait会释放锁。
8.notify()
一旦执行此方法,将会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先度最高的。
9.notifyAll()
一旦执行此方法,就会唤醒所有被wait的线程 。
10.LockSupport
LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的。
线程的生命周期?
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态
- 新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值
- 就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
- 运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
- 阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态
- 死亡状态:线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。
synchronized的底层实现?
synchronized的底层实现是完全依赖JVM虚拟机的,所以谈synchronized的底层实现,就不得不谈数据在JVM内存的存储:Java对象头,以及Monitor对象监视器。
1.Java对象头
在JVM虚拟机中,对象在内存中的存储布局,可以分为三个区域:
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
Java对象头主要包括两部分数据:
1)类型指针(Klass Pointer)
是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;
2)标记字段(Mark Word)
用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键.
所以,很明显synchronized使用的锁对象是存储在Java对象头里的标记字段里。
2.Monitor
monitor描述为对象监视器,可以类比为一个特殊的房间,这个房间中有一些被保护的数据,monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据,进入房间即为持有monitor,退出房间即为释放monitor。
下图是synchronized同步代码块反编译后的截图,可以很清楚的看见monitor的调用。
使用syncrhoized加锁的同步代码块在字节码引擎中执行时,主要就是通过锁对象的monitor的取用(monitorenter)与释放(monitorexit)来实现的。
线程池的核心参数有哪些?
public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler ) { .... }
线程池任务执行流程?
从上图可以看出,提交任务之后,首先会尝试着交给核心线程池中的线程来执行,但是必定核心线程池中的线程数有限,所以必须要由任务队列来做一个缓存,先将任务放队列中缓存,然后等待线程去执行。
最后,由于任务太多,队列也满了,这个时候线程池中剩下的线程就会启动来帮助核心线程池执行任务。
如果还是没有办法正常处理新到的任务,则线程池只能将新提交的任务交给饱和策略来处理了。
多线程引入问题有哪些?
多线程的优点很明显,但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担,并且线程间的共享变量可能造成死锁的出现。
1.线程安全问题
1)原子性
在并发编程中很多的操作都不是原子操作,比如:
i++; // 操作2 i = j; // 操作3 i = i + 1; // 操作4
在单线程环境中这3个操作都不会出现问题,但是在多线程环境中,如果不通过加锁操作,往往很可能会出现意料之外的值。
在java中可以通过synchronized或者ReentrantLock来保证原子性。
2)可见性
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即得到这个修改的值。
如上图所示,每个线程都有自己的工作内存,工作内存和主存间要通过store和load进行交互。
为了解决多线程的可见性问题,java提供了volatile关键字,当一个共享变量被volatile修饰时,他会保证修改的值会立即更新到主存,当有其他线程需要读取时,他会去主存中读取新值,而普通共享变量不能保证其可见性,因为变量被修改后刷回到主存的时间是不确定的。
2.线程死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁,如图所示:
举一个例子:
public void add(int m) { synchronized(lockA) { // 获得lockA的锁 this.value += m; synchronized(lockB) { // 获得lockB的锁 this.another += m; } // 释放lockB的锁 } // 释放lockA的锁 } public void dec(int m) { synchronized(lockB) { // 获得lockB的锁 this.another -= m; synchronized(lockA) { // 获得lockA的锁 this.value -= m; } // 释放lockA的锁 } // 释放lockB的锁 }
两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁。
3.上下文切换
多线程并发一定会快吗?其实不一定,因为多线程有线程创建和线程上下文切换的开销。
CPU是很宝贵的资源,速度也非常快,为了保证均衡,通常会给不同的线程分配时间片,当CPU从一个线程切换到另外一个线程的时候,CPU需要保存当前线程的本地数据,程序指针等状态,并加载下一个要执行的线程的本地数据,程序指针等,这个切换称之为上下文切换。
一般减少上下文切换的方法有:无锁并发编程,CAS算法,使用协程等方式。
Java虚拟机面试题及答案
JVM内存模型有哪些组成?
JVM内存模型可以分为两个部分,如下图所示,堆和方法区是所有线程共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的。
1. 堆(Heap)
堆内存是所有线程共有的,可以分为两个部分:年轻代和老年代。下图中的Perm代表的是永久代,但是注意永久代并不属于堆内存中的一部分,同时jdk1.8之后永久代也将被移除。
堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。
其大小通过-Xms(最小值)和-Xmx(最大值)参数设置(最大最小值都要小于1G),前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小堆内存的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,当然为了避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。堆内存 = 新生代+老生代+持久代。
在我们垃圾回收的时候,我们往往将堆内存分成新生代和老生代(大小比例1:2),新生代中由Eden和Survivor0,Survivor1组成,三者的比例是8:1:1,新生代的回收机制采用复制算法,在Minor GC的时候,我们都留一个存活区用来存放存活的对象,真正进行的区域是Eden+其中一个存活区,当我们的对象时长超过一定年龄时(默认15,可以通过参数设置),将会把对象放入老生代,当然大的对象会直接进入老生代。老生代采用的回收算法是标记整理算法。
2. 方法区(Method Area)
方法区也称”永久代“,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB(64位JVM由于指针膨胀,默认是85M),可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。
它是一片连续的堆空间,永久代的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。不过,一个明显的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会报OOM的错误。参数是通过-XX:PermSize和-XX:MaxPermSize来设定的。
3.虚拟机栈(JVM Stack)
描述的是java方法执行的内存模型:每个方法被执行的时候都会创建一个”栈帧”,用于存储局部变量表(包括参数)、操作栈、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
声明周期与线程相同,是线程私有的。栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区被组织为以一个字长为单位、从0开始计数的数组,和局部变量区一样,操作数栈也被组织成一个以字长为单位的数组。但和前者不同的是,它不是通过索引来访问的,而是通过入栈和出栈来访问的,可以看作为临时数据的存储区域。
除了局部变量区和操作数栈外,java栈帧还需要一些数据来支持常量池解析、正常方法返回以及异常派发机制。这些数据都保存在java栈帧的帧数据区中。
局部变量表: 存放了编译器可知的各种基本数据类型、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。
4.本地方法栈(Native Stack)
与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。(栈的空间大小远远小于堆)
5.程序计数器(PC Register)
是最小的一块内存区域,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。
6.直接内存
直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。
垃圾回收器有哪些?
1.Serial收集器
Serial收集器是最古老的收集器,它的缺点是当Serial收集器想进行垃圾回收的时候,必须暂停用户的所有进程,即stop the world。到现在为止,它依然是虚拟机运行在client模式下的默认新生代收集器,与其他收集器相比,对于限定在单个CPU的运行环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾回收自然可以获得最高的单线程收集效率。
2.ParNew收集器
ParNew收集器是Serial收集器新生代的多线程实现,注意在进行垃圾回收的时候依然会stop the world,只是相比较Serial收集器而言它会运行多条进程进行垃圾回收。
ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百的保证能超越Serial收集器。当然,随着可以使用的CPU的数量增加,它对于GC时系统资源的利用还是很有好处的。它默认开启的收集线程数与CPU的数量相同,在CPU非常多(譬如32个,现在CPU动辄4核加超线程,服务器超过32个逻辑CPU的情况越来越多了)的环境下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
3.Parallel Scavenge收集器
Parallel是采用复制算法的多线程新生代垃圾回收器,似乎和ParNew收集器有很多的相似的地方。但是Parallel Scanvenge收集器的一个特点是它所关注的目标是吞吐量(Throughput)。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能够提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
4.CMS收集器
CMS(Concurrent Mark Swep)收集器是一个比较重要的回收器,现在应用非常广泛,我们重点来看一下,CMS一种获取最短回收停顿时间为目标的收集器,这使得它很适合用于和用户交互的业务。从名字(Mark Swep)就可以看出,CMS收集器是基于标记清除算法实现的。它的收集过程分为四个步骤:
- 初始标记(initial mark)
- 并发标记(concurrent mark)
- 重新标记(remark)
- 并发清除(concurrent sweep)
注意初始标记和重新标记还是会stop the world,但是在耗费时间更长的并发标记和并发清除两个阶段都可以和用户进程同时工作。
不过由于CMS收集器是基于标记清除算法实现的,会导致有大量的空间碎片产生,在为大对象分配内存的时候,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前开启一次Full GC。
为了解决这个问题,CMS收集器默认提供了一个-XX:+UseCMSCompactAtFullCollection收集开关参数(默认就是开启的),用于在CMS收集器进行FullGC完开启内存碎片的合并整理过程,内存整理的过程是无法并发的,这样内存碎片问题倒是没有了,不过停顿时间不得不变长。虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction参数用于设置执行多少次不压缩的FULL GC后跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)。
不幸的是,它作为老年代的收集器,却无法与jdk1.4中已经存在的新生代收集器Parallel Scavenge配合工作,所以在jdk1.5中使用cms来收集老年代的时候,新生代只能选择ParNew或Serial收集器中的一个。
ParNew收集器是使用-XX:+UseConcMarkSweepGC选项启用CMS收集器之后的默认新生代收集器,也可以使用-XX:+UseParNewGC选项来强制指定它。
5.G1收集器
G1收集器是一款面向服务端应用的垃圾收集器。HotSpot团队赋予它的使命是在未来替换掉JDK1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点:
- 并行与并发:G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间。
- 分代收集:和其他收集器一样,分代的概念在G1中依然存在,不过G1不需要其他的垃圾回收器的配合就可以独自管理整个GC堆。
- 空间整合:G1收集器有利于程序长时间运行,分配大对象时不会无法得到连续的空间而提前触发一次GC。
- 可预测的非停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
在使用G1收集器时,Java堆的内存布局和其他收集器有很大的差别,它将这个Java堆分为多个大小相等的独立区域,虽然还保留新生代和老年代的概念,但是新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
虽然G1看起来有很多优点,实际上CMS还是主流。
JVM垃圾回收算法有哪些?
1.标记清除
原理:
- 从根集合节点进行扫描,标记出所有的存活对象,最后扫描整个内存空间并清除没有标记的对象(即死亡对象)
适用场合:
- 存活对象较多的情况下比较高效
- 适用于年老代(即旧生代)
缺点:
- 标记清除算法带来的一个问题是会存在大量的空间碎片,因为回收后的空间是不连续的,这样给大对象分配内存的时候可能会提前触发full gc。
2.复制算法
原理:
- 从根集合节点进行扫描,标记出所有的存活对象,并将这些存活的对象复制到一块儿新的内存(图中下边的那一块儿内存)上去,之后将原来的那一块儿内存(图中上边的那一块儿内存)全部回收掉
适用场合:
- 存活对象较少的情况下比较高效
- 扫描了整个空间一次(标记存活对象并复制移动)
- 适用于年轻代(即新生代):基本上98%的对象是”朝生夕死”的,存活下来的会很少
缺点:
- 需要一块儿空的内存空间
- 需要复制移动对象
3.标记整理
原理:
- 从根集合节点进行扫描,标记出所有的存活对象,最后扫描整个内存空间并清除没有标记的对象(即死亡对象)(可以发现前边这些就是标记-清除算法的原理),清除完之后,将所有的存活对象左移到一起。
适用场合:
- 用于年老代(即旧生代)
缺点:
- 需要移动对象,若对象非常多而且标记回收后的内存非常不完整,可能移动这个动作也会耗费一定时间
- 扫描了整个空间两次(第一次:标记存活对象;第二次:清除没有标记的对象)
优点:
- 不会产生内存碎片
4.分代收集算法
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,这种算法并没有什么新的思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor[1]。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
JVM常用参数有哪些?
除了上面提及的一些参数,下面补充一些和GC相关的常用参数:
- -Xmx: 设置堆内存的最大值。
- -Xms: 设置堆内存的初始值。
- -Xmn: 设置新生代的大小。
- -Xss: 设置栈的大小。
- -PretenureSizeThreshold: 直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配。
- -MaxTenuringThrehold: 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就会加1,当超过这个参数值时就进入老年代。
- -UseAdaptiveSizePolicy: 在这种模式下,新生代的大小、eden 和 survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点。在手工调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量 (GCTimeRatio) 和停顿时间 (MaxGCPauseMills),让虚拟机自己完成调优工作。
- -SurvivorRattio: 新生代Eden区域与Survivor区域的容量比值,默认为8,代表Eden: Suvivor= 8: 1。
- -XX:ParallelGCThreads:设置用于垃圾回收的线程数。通常情况下可以和 CPU 数量相等。但在 CPU 数量比较多的情况下,设置相对较小的数值也是合理的。
- -XX:MaxGCPauseMills:设置最大垃圾收集停顿时间。它的值是一个大于 0 的整数。收集器在工作时,会调整 Java 堆大小或者其他一些参数,尽可能地把停顿时间控制在 MaxGCPauseMills 以内。
- -XX:GCTimeRatio:设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。
Java框架面试题及答案
Spring七大功能模块是什么?
Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。
Spring体系结构有哪些?
Spring框架至今已集成了20多个模块,这些模块分布在以下模块中:
- 核心容器(Core Container)
- 数据访问/集成(Data Access/Integration)层
- Web层
- AOP(Aspect Oriented Programming)模块
- 植入(Instrumentation)模块
- 消息传输(Messaging)
- 测试(Test)模块
Spring体系结构如下图:
Spring框架的好处是什么?
- 轻量:Spring是轻量的,基本的版本大约2MB。
- 控制反转:Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
- 面向切面的编程(AOP):Spring支持面向切面的编程,并且把应用业务逻辑和系统服务分开。
- 容器:Spring包含并管理应用中对象的生命周期和配置。
- MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
- 事务管理:Spring提供一个持续的事务管理接口,可以扩展到上至本地事务下至全局事务(JTA)。
- 异常处理:Spring提供方便的API把具体技术相关的异常(比如由JDBC,HibernateorJDO抛出的)转化为一致的unchecked异常。
Spring支持的事务管理类型有哪些?
Spring支持两种类型的事务管理:
- 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
- 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
Spring事务支持的隔离级别有哪些?
Spring 事务上提供以下的隔离级别:
- ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别
- ISOLATION_READ_UNCOMMITTED : 允许读取未提交的数据变更,可能会导致脏读,幻读或不可重复读
- ISOLATION_READ_COMMITTD : 允许读取为提交数据,可以阻止脏读,当时幻读或不可重复读仍可能发生
- ISOLATION_REPEATABLE_READ: 对统一字段多次读取结果是一致的,除非数据是被本事务自己修改.可以阻止脏读,不可重复读,但幻读可能发生
- ISOLATION_SERIALIZABLE : 完全服从ACID
SpringBean的生命周期?
Spring中每个Bean的生命周期如下,主要分为以下8大步骤:
- Spring容器从XML文件中读取bean的定义,并实例化bean。
- Spring根据bean的定义填充所有的属性。
- 如果bean实现了BeanNameAware接口,Spring传递bean的ID到setBeanName方法。
- 如果Bean实现了BeanFactoryAware接口,Spring传递beanfactory给setBeanFactory方法。
- 如果有任何与bean相关联的BeanPostProcessors,Spring会在postProcesserBeforeInitialization()方法内调用它们。
- 如果bean实现IntializingBean了,调用它的afterPropertySet方法,如果bean声明了初始化方法,调用此初始化方法。
- 如果有BeanPostProcessors和bean关联,这些bean的postProcessAfterInitialization()方法将被调用。
- 如果bean实现了DisposableBean,它将调用destroy()方法。
什么是AOP?
AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。
面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,如下图所示:
AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
Spring AOP的实现原理?
Spring的AOP实现原理其实很简单,就是通过动态代理实现的。
Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理。
- JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
- CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。
Spring AOP的应用场景有哪些?
比如典型的AOP的应用场景:
- 日志记录
- 事务管理
- 权限验证
- 性能监测
AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。
什么是Spring IOC?
IOC是Inversion of Control的缩写,多数书籍翻译成“控制反转”。
IOC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试,有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,如下图所示:
上图引入了IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器。
所以,IOC借助于“第三方”实现具有依赖关系的对象之间的解耦,使程序更优良。
Spring IOC的实现原理?
IOC容器其实就是一个大工厂,它用来管理我们所有的对象以及依赖关系。
- 原理就是通过 Java 的反射技术来实现的,通过反射我们可以获取类的所有信息(成员变量、类名等等等);
- 再通过配置文件(xml)或者注解来描述类与类之间的关系。
这样我们就可以通过这些配置信息和反射技术来构建出对应的对象和依赖关系了,如下图所示:
Spring MVC的工作原理?
MVC即Model-View-Controller的缩写,它是web应用当中的常用的设计模式。
具体如下图所示:
MVC 减弱了业务逻辑接口和数据接口之间的耦合,以及让视图层更富于变化,Struts是MVC的一种实现。
1)控制(Controller)
在Struts中ActionServlet起着一个控制器(Controller)的作用,这个控制组件提供了处理所有发送到Struts的HTTP请求的入口点,它截取和分发这些请求到相应的动作类,这些动作类都是Action类的子类。
另外控制组件也负责用相应的请求参数填充 Action Form,通常称之为FromBean,并传给动作类:通常称之为ActionBean。
动作类实现核心商业逻辑,所有这些控制逻辑利用Struts-config.xml文件来配置。
2)视图(JSP)
主要是由Jsp来控制页面输出的。它接收到Action Form中的数据,利用html,taglib,bean,logic等显示数据。
3)模型(JavaBean)
在Struts中,主要存在三种bean,分别是:Action,ActionForm,EJB或者Java Bean。ActionForm用来封装客户请求信息,Action取得ActionForm中的数据,再由EJB或者Java Bean进行处理。
Struts有哪些优缺点?
优点:
1) 实现了MVC模式,层次结构清晰,使程序员只需关注业务逻辑的实现;
2)struts提供了丰富的标签库,比如:jstl等,大大提高了开发的效率;
3) Struts2提供丰富的拦截器实现,Struts2 的拦截器是一个 Action 级别的 AOP, Struts2 中的 许多特性都是通过拦截器来实现的, 例如异常处理,文件上传,验证等。拦截器 是可配置与重用;
4)多种表现层技术. 如:JSP、FreeMarker、Velocity 等;
5)Struts能很好的与 hibernate、spring整合。
缺点:
1) 传参麻烦,要经过默认的拦截器,参数过多就会臃肿,而且参数校验繁琐;
2) 安全性有待提高,被爆漏洞多。
struts2框架的核心控制器是什么?作用?
1)Struts2框架的核心控制器是:StrutsPrepareAndExecuteFilter:
2)作用:负责拦截由
<url-pattern>/*</url-pattern>
指定的所有用户请求,当用户请求到达时,该Filter会过滤用户的请求。
Struts2 的工作流程?
1)客户端初始化一个指向Servlet容器的请求,比如:浏览器发送请求,该请求经过过滤器Filter后指向核心控制器FilterDispatcher;
2)核心控制器,根据请求询问ActionMapper,需要调用哪个Action;
3)ActionMapper把决定告诉核心控制器,核心控制器再把请求的处理交给ActionProxy;
4)ActionProxy,通过Configuration Manager询问框架的配置文件,找到需要调用的Action类,创建一个ActionInvocation的实例;
5)ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器Intercepter的调用;
6)一旦Action执行完毕ActionInvocation负责根据struts.xml中的配置找到对应的返回结果;
7)响应的返回是通过我们在web.xml中配置的过滤器;
8)如果ActionContextCleanUp是当前使用的,则FilterDispatecher将不会清理sreadlocal ActionContext,如果ActionContextCleanUp不使用则将会去清理sreadlocals。
Struts2 拦截器 和 过滤器 的区别?
①、过滤器依赖于 Servlet 容器,而拦截器不依赖于 Servlet 容器。
②、Struts2 拦截器只能对 Action 请求起作用,而过滤器则可以对几乎所 有请求起作用。
③、拦截器可以访问 Action 上下文(ActionContext)、值栈里的对象 (ValueStack),而过滤器不能.
④、在 Action 的生命周期中,拦截器可以多次调用,而过滤器只能在容器 初始化时被调用一次。
拦截器的生命周期与工作过程?
1)每个拦截器都是实现了Interceptor接口的 Java 类;
2)init(): 该方法将在拦截器被创建后立即被调用,它在拦截器的生命周期内只被调用一次,可以在该方法中对相关资源进行必要的初始化;
3)intercept(ActionInvocation invocation):每拦截一个动作请求,该方法就会被调用一次;
4)destroy:该方法将在拦截器被销毁之前被调用,它在拦截器的生命周期内也只被调用一次;
5)struts2中有内置了18个拦截器。
什么是 ORM 框架?
ORM(Object Relation Mapping),即对象关系映射,即通过类与数据库表的映射关系将对象持久化到数据库中。
ORM框架采用元数据来描述对象与关系映射的细节,元数据一般采用XML格式,并且存放在专门的对象一映射文件中。
当前ORM框架主要有五种:Hibernate(Nhibernate),iBATIS,mybatis,EclipseLink,JFinal。
Mybatis架构设计?
我们把Mybatis的功能架构分为三层:
- API接口层
- 数据处理层
- 基础支撑层
接口层的作用?
接口层:主要就是和数据库交互,提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库,接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
以使用Mapper接口为例,将配置文件中的每一个 节点抽象为一个 Mapper 接口,这个接口中声明的方法和跟Mapper.xml中的 节点项对应。
id值对应方法名称,parameterType 值对应方法的入参类型,而resultMap 值则对应返回值类型。
配置好后,MyBatis 会根据接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,当调用接口方法时,根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过 SqlSession.select/update( “statementId”, parameter) 等来实现对数据库的操作。
数据处理层的作用?
数据处理层:可以说是MyBatis 的核心,负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等,它主要的目的是根据调用的请求完成一次数据库操作。
从大的方面上讲,它要完成两个功能:
- 通过传入参数构建动态SQL语句
- SQL语句的执行以及封装查询结果集
Mybatis核心执行流程?
mybatis的总体执行流程,总结如下:
1.MyBatis配置文件
config.xml:配置了全局配置文件,配置了MyBatis的运行环境等信息。
mapper,xml:sql的映射文件,配置了操作数据库的sql语句,此文件需在config.xml中加载。
2.SqlSessionFactory
通过MyBatis环境等配置信息构造SqlSessionFactory(会话工厂)。
3.SqlSession
通过会话工厂创建SqlSession(会话),对数据库进行增删改查操作。
4.Exector执行器
MyBatis底层自定义了Exector执行器接口来具体操作数据库,Exector接口有两个实现,一个基本执行器(默认),一个是缓存执行器,SqlSession底层是通过Exector接口操作数据库。
5.MappedStatement
MyBatis的一个底层封装对象,它包装了MyBatis配置信息与sql映射信息等。mapper.xml中的insert/select/update/delete标签对应一个MappedStatement对象。标签的id就是MappedStatement的id。
MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo、Executor通过MappedStatement在执行sql前将输入的Java对象映射至sql中,输入参数映射就是JDBC编程对preparedStatement设置参数。
Mybatis的Mapper接口工作原理是什么?方法能重载吗?
Mapper接口全限定名称对应着Mapper.xml里的namespace。
namespace+id就是sqlSession语句的statement,也就是Map<String, MappedStatement>的key。
方法名和sql的id一致,所以Mapper接口的方法是不能重载的。
Mapper接口工作原理是使用JDK动态代理。SqlSession存在一个getMapper方法。该方法最终会使用Proxy.newProxyInstance方法去构建一个代理对象。代理对象拦截接口方法,从而执行MappedStatement所代表的sql,最终返回结果。
Mybatis动态SQL是做什么的?都有哪些动态SQL?能简述一下动态SQL的执行原理不?
动态SQL让我们在xml映射文件中,以标签的形式编写动态SQL,完成逻辑判断和SQL拼接。
mybatis提供了9中动态SQL标签if|where|choose|when|otherwise|foreach|set|trim|bind
动态SQL的原理是使用OGNL表达式从SQL参数对象中计算表达式的值,根据值动态拼接SQL
Mybatis如何将SQL执行结果封装为目标对象并返回的?都有哪些映射形式?
使用resultMap自定义映射返回,使用resultType+数据库列的别名返回与pojo一致的字段做自动参数绑定。
最终通过反射对返回对象赋值。
Hibernate工作原理?
1.读取并解析配置文件;
2.读取并解析映射信息,创建SessionFactory;
3.打开Sesssion;
4.创建事务Transation;
5.持久化操作;
6.提交事务;
7.关闭Session;
8.关闭SesstionFactory。
为什么要使用 Hibernate?
1)对JDBC做了轻量级的封装,简化了数据访问层编码;
2)Hibernate是一个ORM框架,开发者可以使用面向对象的思想操作数据库,使用更加方便;
3) hibernate映射很灵活,支持各种关系数据库,从一对一到多对多的各种复杂关系。
Hibernate 有几种查询方式?
Hibernate有4种查询方法:
- HQL 通过Hibernate提供的查询语言进行查询;
- EJBQL(JPQL 1.0) 是EJB提供的查询语言;
- QBC(query by cretira)通过Cretira接口进行查询;
- QBE(query by Example) 通过Example编程接口进行查询。
Hibernate 的缓存机制?
1)一级缓存(Session缓存)
它可以在session范围内减少数据库的访问次数! 只在session范围有效! Session关闭,一级缓存失效
只要是持久化对象状态的,都受Session管理,也就是说,都会在Session缓存中
Session的缓存由hibernate维护, 如果想操作缓存内容,可以通过close/clear/evict清空缓存
2) 二级缓存(SessionFactory缓存)
二级缓存所有的Session都可以使用,可以在hibernate的配置文件hibernate.cfg.xml自行配置是否开启
Java设计模式面试题及答案
什么是设计模式?
设计模式是对面向对象设计中反复出现的问题的解决方案,由Erich Gamma等人从建筑设计领域引入到计算机科学中来的。
设计模式可以提升代码重用性、可读性、可扩展性、可靠性,还能降低系统中类与类之间的耦合度。
常用的设计模式有哪些?
开发中常用到的模式如下:
singleton单例模式,用来减少重复创建对象;
factory工厂模式,用来解耦;
iterator迭代器模式,用来遍历对象;
observer观察者模式,用来收发消息;
templete模板模式,用来避免执行相同的操作;
strategy策略模式,用来定义算法等。
设计模式有哪些原则?
主要分为7大原则:
1.单一职责原则
对于类来说,一个类应该只负责一项职责
2.接口隔离原则
避免其实现类污染
3.依赖倒转(倒置)原则
1)高层模块不应该依赖底层模块,二者都应该依赖其抽象
2)抽象不应该依赖细节,细节应该依赖抽象
3)依赖关系传递的3中方式
- 接口传递
- 构造方法传递
- setter方法传递
4.里氏替换原则
子类尽量不要重写父类的方法
5.开闭原则
修改关闭,扩展开发
6.迪米特法则
一个对象应该对其他对象保持最少的了解
又叫最少知道原则,即一个类对自己依赖的类知道越少越好。
7.合成复用原则
尽量使用组合/聚合,不要用继承
设计模式有哪些种类?
主要分为三类:
1、创建型模式
用来描述 “如何创建对象”,它的主要特点是 “将对象的创建和使用分离”。
2、结构型模式
用来描述如何将类或对象按照某种布局组成更大的结构。
3、行为型模式
用来识别对象之间的常用交流模式以及如何分配职责。
创建型模式有哪些?
创建型模式,共五种:
1)工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,也就是将创建工作推迟到子类来完成。
2)抽象工厂模式:提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们(指的是这些相关或者相互依赖的对象)的具体的类。
3)生成器模式:将一个复杂对象的构建和它的表示相分离,使得同样的构建过程可以创建出不同的表示。
4)原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
5)单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。换句话来说就是,单例模式通过将单例类的构造器访问修饰符设置为private,禁止客户直接通过new的方式来实例化单例类;与此同时,单例类提供一个静态访问点来获取到通过内部创建出的唯一单例类对象。
结构型模式有哪些?
结构型模式共七种:
1.适配器模式:将一个类的接口转换成客户希望的另外一种接口,这样就能实现已有接口的复用。适配器主要有类适配器和对象适配器两种实现方式,通常情况下,推荐优先使用对象适配器方式。
2.桥接模式:将抽象部分与实现部分分离,使它们都可以独立地变化。它主要用于应对多维度变化点问题,通过对象组合的方式,可以极大地减少子类的数目,同时还能让不同维度独立扩展变化。
3.组合模式:将对象组合成树形结构以表示“整合-部分”的层次结构,从而使得用户对单个对象和组合对象的使用具有一致性,也就是客户端能够透明地无区别地操作两者。
4.装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更为灵活。
5.外观模式:为子系统中的一组接口提供一个一致的接口,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。这样原来需要客户直接与复杂的子系统打交道、交互,现在这一过程将完全将交由外观对象来完成,极大地方便了客户端的调用。
6.享元模式:运用共享技术有效地支持大量细粒度的对象。享元模式关键是将对象的内部状态和外部状态分离,尽可能地对“稳定”的内部状态进行共享,而将会随运用场景而改变的状态通过外部状态传入。
7.代理模式:为其他对象提供一种代理以控制对这个对象的访问。主要是在客户端和目标对象间增加一层间接层,通过这个间接层来完成对目标对象的种种控制操作,所以也就形成了不同功能类型的代理呢,比如远程代理、保护代理和虚代理等等。
行为型模式有哪些?
行为型模式共十一种:
- 策略模式
- 模板方法模式
- 观察者模式
- 迭代子模式
- 责任链模式
- 命令模式
- 备忘录模式
- 状态模式
- 访问者模式
- 中介者模式
- 解释器模式
数据库面试题及答案
事务四大特性?
事务应该具有4个属性:原子性、一致性、隔离性、持久性,这四个属性通常称为ACID特性。
- 原子性:事务作为一个整体被执行,包含在其中的对数据库的操作要么全部都执行,要么都不执行;
- 一致性:指在事务开始之前和事务结束以后,数据不会被破坏,假如A账户给B账户转10块钱,不管成功与否,A和B的总金额是不变的;
- 隔离性:多个事务并发访问时,事务之间是相互隔离的,一个事务不应该被其他事务干扰,多个并发事务之间要相互隔离;
- 持久性:表示事务完成提交后,该事务对数据库所作的操作更改,将持久地保存在数据库之中。
MySQL事务隔离级别有哪些?
MySQL 事务隔离级别总共有以下 4 种:
- READ UNCOMMITTED:读未提交;
- READ COMMITTED:读已提交;
- REPEATABLE READ:可重复读;
- SERIALIZABLE:序列化。
MySQL 语句执行的步骤?
主要包含如下执行步骤:
- 客户端请求: 连接器(验证用户身份,给予权限);
- 查询缓存:存在缓存则直接返回,不存在则执行后续操作;
- 分析器:对 SQL 进行词法分析和语法分析操作;
- 优化器:主要对执行的 SQL 优化选择最优的执行方案方法;
- 执行器:执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口-> 去引擎层获取数据返回(如果开启查询缓存则会缓存查询结果)。
数据库三范式?
第一范式(1NF):属性不可分割,即每个属性都是不可分割的原子项;
第二范式(2NF):满足第一范式,且不存在部分依赖,即非主属性必须完全依赖于主属性;
第三范式(3NF):满足第二范式,且不存在传递依赖,即非主属性不能与非主属性之间有依赖关系,非主属性必须直接依赖于主属性,不能间接依赖主属性。
简单来说:
第一范式:列表字段不可分;
第二范式:有主键且非主键依赖主键;
第三范式:非主键字段不能相互依赖。
什么时候要创建索引?
(1)表经常进行 SELECT 操作;
(2)表很大(记录超多),记录内容分布范围很广;
(3)列名经常在 WHERE 子句或连接条件中出现。
什么时候不要创建索引?
(1)表经常进行 INSERT/UPDATE/DELETE 操作;
(2)表很小(记录超少);
(3)列名不经常作为连接条件或出现在 WHERE 子句中。
常用的索引有哪些类型?
- 唯一索引:唯一索引不允许两行具有相同的索引值;
- 主键索引:为表定义一个主键将自动创建主键索引,主键索引是唯一索引的特殊类型。主键索引要求主键中的每个值是唯一的,并且不能为空;
- 聚集索引(Clustered):表中各行的物理顺序与键值的逻辑(索引)顺序相同,每个表只能有一个;
- 非聚集索引(Non-clustered):非聚集索引指定表的逻辑顺序。数据存储在一个位置,索引存储在另一个位置,索引中包含指向数据存储位置的指针,可以有多个,小于249个。
binlog是什么?有什么作用?
1、数据恢复
只要有数据库在某个时刻的备份以及此时后的所有binlog,就可以恢复数据库的数据。
在我们的日常工作中,我们的DBA学生经常可以帮助我们将数据库的数据恢复到任何一秒。
2、主从复制
为了提高MySQL的效率,经常做读写分离,即一主多从。
一个主库(写库),多个从库(读库)。
此时,从库可以监控主库的binlog日志,同步写库的所有更改操作。
为什么需要 redo log?
mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用,然后使用后台线程去做缓冲池和磁盘之间的同步。
那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?还没来得及执行上面图中红色的操作,这样会导致丢部分已提交事务的修改信息。
所以引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。
总结:redo log是用来恢复数据的 用于保障,已提交事务的持久化特性。
MySQL 的 redo log 和 binlog 区别?
详细的区别如下图所示:
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》