面试问题整理

基础知识

多线程

1、线程与进程

在开始之前先把进程与线程进行区分一下,一个程序最少需要一个进程,而一个进程最少需要一个线程。关系是线程–>进程–>程序的大致组成结构。所以线程是程序执行流的最小单位,而进程是系统进行资源分配和调度的一个独立单位。

2、Thread的几个重要方法

我们先了解一下Thread的几个重要方法。

a、start()方法,调用该方法开始执行该线程;

b、stop()方法,调用该方法强制结束该线程执行;

c、join方法,调用该方法等待该线程结束。

d、sleep()方法,调用该方法该线程进入等待。

e、run()方法,调用该方法直接执行线程的run()方法,但是线程调用start()方法时也会运行run()方法,区别就是一个是由线程调度运行run()方法,一个是直接调用了线程中的run()方法!!

看到这里,可能有些人就会问啦,那wait()和notify()呢?要注意,其实wait()与notify()方法是Object的方法,不是Thread的方法!!同时,wait()与notify()会配合使用,分别表示线程挂起和线程恢复。

这里还有一个很常见的问题,顺带提一下:wait()与sleep()的区别,简单来说wait()会释放对象锁而sleep()不会释放对象锁。这些问题有很多的资料,不再赘述。

3、线程状态

面试问题整理_第1张图片

线程总共有5大状态,通过上面第二个知识点的介绍,理解起来就简单了。

新建状态:新建线程对象,并没有调用start()方法之前

就绪状态:调用start()方法之后线程就进入就绪状态,但是并不是说只要调用start()方法线程就马上变为当前线程,在变为当前线程之前都是为就绪状态。值得一提的是,线程在睡眠和挂起中恢复的时候也会进入就绪状态哦。

运行状态:线程被设置为当前线程,开始执行run()方法。就是线程进入运行状态

阻塞状态:线程被暂停,比如说调用sleep()方法后线程就进入阻塞状态

死亡状态:线程执行结束

4、锁类型

可重入锁:在执行对象中所有同步方法不用再次获得锁

可中断锁:在等待获取锁过程中可中断

公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利

读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写

5、多线程特性

多线程要保证并发程序正确执行,必须遵循三个原则:原子性、有序性、可见性

 

synchronized与Lock的区别与使用

我把两者的区别分类到了一个表中,方便大家对比:

类别

Synchronized

Lock

存在层次

Java的关键字,在jvm层面上

是一个类

锁的释放

1、等待获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁

在finally中必须释放锁,不然容易造成

线程死锁

锁的获取

假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待

分情况而定,Lock有多个锁获取的方式

,具体下面会说道,大致就是可以尝试

获得锁,线程可以不用一直等待

锁状态

无法判断

可以判断

锁类型

可重入 不可中断 非公平

可重入 可中断 可公平(两者皆可)

性能

少量同步

大量同步

 

volatile和ThreadLocal

volatile:其变量修改后将立即刷新到主内存,其他线程即可读取到新值。编译器利用内存屏障的概念禁止上述三条指令的重排序。

 

ThreadLocal:当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

悲观锁和乐观锁

独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

CAS在Java中的实现

AtomicInteger,在没有锁的机制下借助volatile原语,保证线程间的数据是可见的(共享的),然后利用CPUCAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。

 

原理:CAS3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 CAS通过调用JNI的代码实现的。JNI:Java Native InterfaceJAVA本地调用,允许java调用其他语言。

线程池的原理和实现

https://blog.csdn.net/he90227/article/details/52576452

阻塞队列和非阻塞队列

阻塞队列

获取元素的时候等待队列里有元素,否则阻塞,保存元素的时候等待队列里有空间,否则阻塞 

ArrayBlockingQueue:FIFO、数组实现

LinkedBlockingQueue :FIFO、Node链表结构 

SynchronousQueue :无内部容量的阻塞队列,put必须等待take,同样take必须等待put。比较适合两个线程间的数据传递。

DelayQueue :有界阻塞延时队列,当队列里的元素延时期未到时,通过take方法不能获取,会被阻塞,直到有元素延时到期为止

PriorityBlockingQueue :插入的对象必须是可比较的或者通过构造方法实现插入对象的比较器,用来处理一些有优先级的事物

 

注:ArrayBlockingQueue在put,take操作使用了同一个锁,两者操作不能同时进行,而LinkedBlockingQueue使用了不同的锁,put操作和take操作可同时进行,以此来提高整个队列的并发性能。

非阻塞队列

ConcurrentHashMap弱一致性模型

ConcurrentSkipListMap并发程序中可以保证顺序

ConcurrentSkipListSet支持排序且不允许重复的元素

ConcurrentLinkedQueue:提供了并发环境的队列操作。

方法poll当没有获得数据时返回null,如果有数据则移除表头,并将表头进行返回;

方法element当没有数据时,出现异常,如果有数据则返回表头项 

方法peek当没有数据时返回null,有数据则不移除表头,返回表头项

ConcurrentLinkedDeque

CopyOnWriteArrayList 

CopyOnWriteArraySet

注:HashTableVector调用iterator()方法返回Iterator对象后,再调用remove()时会出现ConcurrentModificationException异常,也就是并不支持Iterator并发的删除。HashTableVector调用iterator()方法返回Iterator对象后,再调用remove()时会出现ConcurrentModificationException异常,也就是并不支持Iterator并发的删除。

线程间通信

Condition可以替代传统的线程间通信(synchronize),用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。

注:

为什么方法名不直接叫wait()/notify()/nofityAll()?因为Object的这几个方法是final的,不可重写!

Condition的强大之处在于它可以为多个线程间建立不同的Condition

CountDownLatch、CyclicBarrier、Semaphore共同之处与区别以及各自使用场景

区别:https://blog.csdn.net/jackyechina/article/details/52931453

1.CountDownLatch使一个线程A或者组线程A等待其他线程执行完毕后,

并发工具类/比较

CountDownLatch

CyclicBarrier

 

区别

一组线程A或者组线程A等待其他线程执行完毕后才继续执行

一组线程使用await指定barrier,所有线程都到达各自barrier后,再同时执行barrier下面的代码

减计数的方式,计数为0释放所有等待线程

加计数方式,计数达到构造方法中参数指定的值时释放所有等待线程

当计数为0时,无法被重置

计数达到指定值时,计数置位0重新开始

countDown方法计数减一,调用await方法只进行阻塞,对计数没任何影响

只有一个await方法,一旦调用计数加一,若加一后不等于构造方法的值,则线程阻塞

相同

有一个int类型参数的构造方法,该值作为计数用

都具有await()方法,并且执行此方法会引起线程的阻塞,达到某种条件才能继续执行(这种条件也是两者的不同)


Semaphore:有一个int类型参数的构造方法,该参数用来控制同时访问特定资源的线程数量,当达到semaphore指定资源数量时就不能再访问,线程处于阻塞

JVM

运行机制

1、运行过程概述

https://www.cnblogs.com/lishun1005/p/6019678.html

开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件),再然后字节码被装入内存,一旦字节码进入虚拟机,就会被解释器执行或者是被即时代码发生器有选择的转换成机器码执行。

(1)Java代码到JVM字节码

(2)Java字节码的执行

(3)运行过程包含的机制

(a)Java源码编译机制

Java源码编译由以下三个过程组成:分析和输入到符号表、注解处理、语义分析和生成class文件

最后生成的class文件由以下部分组成:

结构信息:包含class文件格式版本号及各部分的数量和大小的信息

元数据:对应于Java源码中声明与常量的信息。包含类、继承的超类、实现的接口的声明信息、域与方法声明信息和常量池

方法信息:对应于Java源码中语句和表达式对应的信息。包含字节码、异常处理器表、求值栈与局部变量去大小、求值栈的类型记录、调试符号信息

(b)类加载机制

 

  • 加载

JVM的类加载时通过ClassLoader及其子类来完成的,类的层次关系和加载顺序右下图描述

①Bootstrap ClassLoader

负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

②Extension ClassLoader

负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

③App ClassLoader

负责记载classpath中指定的jar包及其目录下的class文件

④Custom ClassLoader

属于应用程序根据自身需要自定义ClassLoader,如Tomcat、Jboss都会根据j2ee规范自行实现ClassLoader

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

 

  • 连接

主要是将已经读到内存的类的二进制数据合并到虚拟机的运行时环境中去。分为三个阶段,

分别为

验证:文件格式验证、元数据验证、字节码验证、符号引用验证等。

准备:主要是为对象和变量分配内存,并为类设置初始值(方法区中)。

对于static类型变量在这个阶段会为其赋值为默认值,比如public static int v=5,在这个阶段会为其赋值为v=0,而对于static final类型的变量,在准备阶段就会被赋值为正确的值 

解析:将符号引用转换为直接引用。

符号引用仅仅是一个字符串,而引用的对象不一定被加载,直接引用指的是将对象的指针或者地址偏移量指向真正的对象,将字符串所指向的对象加载到内存中

 

  • 初始化

主要执行类的构造方法,并且为静态变量赋值为初始值,执行静态块代码。

步骤:

    假如该类没有被加载和链接,那就先加载和链接

    假如该类存在直接的父类,并且这个父类还没被初始化,那就先初始化父类

    假如类中存在初始化语句时,那就依次执行这些初始化语句

    顺序:类的静态成员 --> 类的实例成员 --> 类的构造方法

时机:

所有的java类只有在对类的首次主动使用时才会被初始化。主动使用的情况有六种,分别如下。

创建类的实例、访问某个类或接口的静态变量,或者对该静态变量赋值、调用类的静态方法、反射、初始化一个类的子类、Java虚拟机启动时被标注为启动类的类

注意:1、当Java虚拟机初始化一个类时,要求他的所有父类都已经被初始化,但是这条规则并不适合接口。在初始化一个类或接口时,并不会先初始化它所实现的接口。 
2、只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用。如果静态方法或变量在parent中定义,从子类进行调用,则不会初始化子类。 

(c)类执行机制

JVM是基于堆栈的虚拟机。JVM为每个新创建的线程都分配一个堆栈。

JVM执行class字节码,线程创建后,都会产生程序计数器和栈,程序计数器存放下一条要执行的指令在方法内的偏移量,栈中存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是由局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈用于存放方法执行过程中产生的中间结果。

 

内存空间管理

内存模型

https://blog.csdn.net/suifeng3051/article/details/48292193#t7

其中,方法区和堆是所有线程共享的。

这里写图片描述

(1)   方法区

存放了要加载的类的信息、类中的静态变量、final定义的常量、类中的field、方法信息等。由线程共享。

(2)   堆区

JavaGC机制最重要的区域,由所有线程共享,在虚拟机启动时创建。

存储对象实例及数组,可以认为java中所有通过new创建的对象都在此分配

          分代管理方式

面试问题整理_第2张图片

(3)   本地方法栈

用于支持native方法的执行,存储了每个native方法调用的状态

(4)   程序计数器

一个较小的内存区域,可能是CPU寄存器或者操作系统内存,所以它是线程私有的。

如果程序执行的是Java方法,则计数器记录的时正在执行的虚拟机字节码指令地址;如果程序执行的是本地方法,则计数器的值为Undefined。由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况。

(5)   虚拟机栈

占用的是操作系统内存,每个线程都对应着一个虚拟机栈,是线程私有,而且分配非常高效。一个线程的每个方法在执行的同时,都会创建一个栈帧,栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。

虚拟机栈中定义了两种异常,如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出);不过多数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,直到内存不足,此时,会抛出OutOfMemoryError(内存溢出)。

(6)  Java对象访问方式

句柄:堆区中,会划分一块单独内存区域作为句柄池,句柄池存储了对象实例数据(堆区)和对象类型数据(方法区)的指针,该方式表示地址十分稳定。

面试问题整理_第3张图片

直接指针:reference中存储的就是对象实例数据在堆中的实际地址,对象实例数据包含对象类型数据的指针,HotSpot虚拟机用的就是这种方式。

面试问题整理_第4张图片

JVM内存分配

Java对象所占用的内存主要在堆上实现,因为堆是线程共享的,因此在堆上分配内存时需要进行加锁,这就导致了创建对象的开销比较大。当堆上空间不足时,会触发GC,如果GC后空间仍然不足,则会抛出OutOfMemory异常。

内存分配技术

bump-the-pointer由于Eden区是连续的,因此该技术的核心就是跟踪最后创建的对象,在对象创建时,只需要检查最后一个对象后面是否有足够的内存即可。

TLABThread-LocalAllocation Buffers):针对多线程的,为每个新创建的线程在Eden区分配一块独立的空间,这快空间称为TLABTLAB分配内存时,不需要加锁。

内存回收

1)方式

引用计数器:采用分散式管理,通过计数器记录对象是否被引用,当计数器为0,说明此对象已经不再被使用,可进行回收。弊端有引用计数器需要在每次对象赋值时进行引用计数器的增减,他有一定消耗。另外,引用计数器对于循环引用的场景没有办法实现回收。

跟踪收集器:采用集中式管理,会全局记录数据引用的状态。基于一定条件的触发,执行时需要从根集合来扫描对象的引用关系,这可能会造成应用程序暂停。主要有复制(Copying)、标记-清除(Mark-Sweep)、标记-压缩(Mark-Compact)三种算法。

 

根集合(GC Roots):方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象作为GC roots,被GC roots引用的对象不被GC回收。

A.复制(Copying)

从根集合扫描出存活的对象,并将找到的存活的对象复制到一块新的完全未被使用的空间中。年轻代(Eden)就是采用这个算法,其带来的成本就是增加一块空的内存空间进行对象的移动。

B. 标记-清除(Marking-Deleting

从根集合扫描,对存活的对象标记,标记完毕后,再扫描整个空间未标记的对象进行删除。由于直接回收不存活对象占用的内存,因此会造成内存碎片。

C.标记-压缩(Mark-Compact

跟标记清除一样,是对活的对象进行标记,但清除对象占用的内存后,会把所有活的对象向左端空闲空间移动,然后再更新引用其对象的指针。

2)过程

1.在初始阶段,新创建的对象分配到Eden区,survivor的两块空间都为空

2.Eden区满了,minor garbage被触发

3.经过扫描与标记,存活的对象被复制到S0,不存活的对象被收回

4.下一次Minor GC中,Eden区的情况和上面一致,没有引用的对象被收回,存活的对象复制到survivor区(S1区)。然后在suvivor区,S0所有数据都被复制到S1,需要注意的是,上次minor GC过程中移动到S0中的对象复制到S1后其年龄加1,此时S0区被清空。

5.再下一次Minor GC则重复这个过程,每次survivor的两个区对换。

6.经过几次Minor GC后,当存活对象的年龄达到一个阀值后,就会被年轻代Promotion到老年代。

7.随着Minor GC不断的进行,第六步就会发生

8.最终,Major GC将会在老年代发生,老年代的空间将会被清除和压缩

 

垃圾收集器

1.   组合

注:连线表示可以搭配使用

2.   并行和并发的区别

并行:多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态

并发:用户线程与垃圾收集线程同时执行(不一定是并行,可能会交替执行)

3.   关注点

停顿时间、吞吐量、减少堆的内存空间,获得更好的空间局部性

4.   总结

收集器                                                

特点

使用场景

Serial

针对新生代、单线程、采用复制算法

Client模式下、限定单个CPU的环境、用户的桌面应用场景

ParNew

除了多线程外,其他的与Serial一致

Server模式下、能与CMS配合工作、单个CPU环境中,不会比Serial有更好的效果,因为线程交互开销

Parallel Scavenge

针对新生代、采用复制算法、多线程收集、目标是达到一个控制的吞吐量

高吞吐量为目标即程序主要在后台计算,不需要与用户进行太多的交互,如:批量处理、订单处理等

Serial Old

 Serial的老年代版本,采用标记-整理算法、单线程收集  

 Client模式、在Server模式下有两大用途:(A)在JDK1.5及之前,与Parallel Scavenge搭配使用(JDK1.6Parallel Old可搭配)(B)作为CMS的后备预案,在并发收集发生Concurrent Mode Failure时使用

 Parallel Old  

 Parallel Scavenge收集器的老年代版本,JDK1.6才开始提供,采用标记-整理算法,多线程收集

 JDK1.6及之后用来替代老年代的Serial Old

Server模式,多CPU的情况

注重吞吐量及CPU资源敏感的场景

 CMS 

并发标记清理(Concurrent Mark Sweep CMS),也称为并发低停顿收集器,第一款真正意义上的并发收集器,实现了垃圾收集器与用户线程(基本上)同时工作。针对老年代、基于标记-清除算法(不进行压缩,产生内存碎片)、以获取最短回收停顿时间为目标、并发收集,低停顿、需要更多内存 

 与用户交互较多的场景

希望系统停顿时间最短,注重服务的响应时间

G1

A)并行与并发

充分利用多CPU、多核环境下的硬件优势;可以并行来缩短“Stop The World”停顿时间;也可以并发让垃圾收集与用户程序同时进行

B)分代收集,收集范围含新生代和老年代

独立管理整个GC堆,不需要与其他收集器配合

能够采用不同的方式处理不同的对象

虽然保留分代概念,但java堆的内存布局有很大差别

将这个堆划分为多个大小相等的独立区域(Region

新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合

C)、结合多种垃圾收集算法,空间整合,不产生碎片

      从整体看,是基于标记-整理算法;

      从局部(两个Region间)看,是基于复制算法;

      这是一种类似火车算法的实现;

      都不会产生内存碎片,有利于长时间运行;

D)可预测的停顿:低停顿的同时实现高吞吐量

      G1除了追求低停顿处,还能建立可预测的停顿时间模型;

      可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒;

 

面向服务端应用,针对具有大内存、多处理器的机器;

需要低GC延迟、并具有大堆的应用程序提供解决方案

 

下列情况,使用G1可能比CMS

(1)   超过50%Java堆被活动数据占用

(2)   对象分配频率或年代提升频率变化很大

(3)   GC停顿时间很长(长于0.51秒)


内存调优

#参数名称

·        标准参数(-),所有JVM都必须支持这些参数的功能,而且向后兼容;例如:

·        非标准参数(-X),默认JVM实现这些参数的功能,但是并不保证所有JVM实现都满足,且不保证向后兼容;

·        非稳定参数(-XX),此类参数各个JVM实现会有所不同,将来可能会不被支持,需要慎重使用;

 

#据称下面两个参数可能会引发一个罕见的竞争状态(race condition),慎用。
    -XX:+G1ParallelRSetUpdatingEnabled
    -XX:+G1ParallelRSetScanningEnabled

 #G1实际应用建议:

·        年轻代大小:避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显式设置年轻代大小,因为固定年轻代的大小会覆盖暂停时间目标。

·        暂停时间目标:每当对垃圾回收进行评估或调优时,都会涉及到延迟与吞吐量的权衡。G1是增量垃圾回收器,其吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间。因此,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量。

·        掌握混合垃圾回收:当您调优混合垃圾回收时,请尝试以下选项

·        -XX:InitiatingHeapOccupancyPercent

·        -XX:G1MixedGCLiveThresholdPercent  -XX:G1HeapWastePercent

·        -XX:G1MixedGCCountTarget  -XX:G1OldCSetRegionThresholdPercent

命令

解释

堆内存设置(年轻代+老年代)

-Xms1024M

设置JVM初始内存(不加M,单位为KB

-Xmx2048M

设置JVM最大可用内存空间(单位同上)

-XX:NewRatio=4

设置年轻代与老年代的比值,则1:4

-XX:SurvivorRatio=4

设置年轻代中Survivor区与Eden区的大小比值,则2:4

年轻代

-Xmn1024M

设置年轻代内存值(-Xmn的优先级比-XX:NewRatio

-XX:NewSize=size in bytes

设置年轻代初始内存值(缺省640K

-XX:MaxNewSize=size in bytes

设置年轻代最大内存值

垃圾回收内存设置

-XX:MaxTenuringThreshold=0

设置垃圾最大年龄,每次经历GC没有被回收,则其age加一,达到设置的阀值后不是被回收就是被移到老年代

-XX:MinHeapFreeRatio=40

修改垃圾回收之堆中可用内存的最小百分比,缺省为40

-XX:MaxHeapFreeRatio=40

修改垃圾回收后堆中可用内存的最大百分比,缺省为70

-XX:TargetSurvivorRatio=40

设置Survivor区的目标使用率,缺省为50,一旦存放总大小超过比例,则迁移至年老区。

垃圾回收策略选择(JDK5.0JVM根据当前系统配置智能判断)

-XX:+UseSerialGC

设置串行收集器

-XX:+UseParallelGC

设置并行收集器

-XX:ParallelGCThreads=20

配置并行收集器的线程数(建议配置与CPU数据相等)

-XX:+UseParallelOldGC

配置年老代垃圾收集方式为并行收集。JDK1.6开始支持

-XX:MaxPauseMillis=100

配置每次年轻代垃圾回收的最长时间(毫秒)

-XX:+UseAdaptiveSizePolicy

并行收集器自动调整年轻代EdenSurvivor区的比例(建议使用并行收集器时一直打开)

并发收集器(响应时间优先):垃圾收集器进行垃圾收集时,其他线程的依旧在工作

-XX:+UseConcMarkSweepGC

设置老年代为并发收集,jdk1.4开始

-XX:UseParNewGC

设置年轻代为并发收集,可与CMS收集同时使用。JDK5.0以上,jvm会根据系统配置自行设置,无需在设置此参数

-XX:CMSFullGCsBeforeCompaction=0

打开内存空间的压缩和整理,在Full GC后执行。,可能会影响性能,但可以消除内存碎片

-XX:+CMSIncrementalMode

设置为增量手机模式。一般适用于单CPU情况

-XX:CMSInitatingOccupancy

Fraction=70

表示老年代内存空间使用到70%时就开始执行CMS收集,以确保老年代有足够的空间接纳来自年轻代的对象,避免FULL GC的发生

垃圾优先型(G1

-XX:+UnlockExperimentalVMOptions

-XX:+UseG1GC

开启G1收集器

-XX:GCPauseIntervalMillis=200

使用G1时指定时间间隔

-XX:+G1YoungGenSize=512m

年轻代的大小可以明确指定影响消除暂停时间

-XX:InitatingHeapOccupanyPercent=30

堆占用了多少比例时触发GC

-XX:ParallelGCThreads=3

配置并行收集器的线程数

-XX:ConcGCThreads=3

并发GC使用的线程,一般设置为并行垃圾回收线程数(ParallelGCThread)的1/4

-XX:G1ReservePercent=n

设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险。默认是10%

-XX:G1HeapRegionSize=n

设置G1区域的大小。值是2的幂,范围为1MB32MB。目标是根据最小的Java堆大小划分出约2048个区域

-XX:G1NewSizePercent=5

设置要用作年轻代大小最小值的堆百分比。默认是5%

-XX:G1MaxNewSizePercent=60

设置要要用作年轻代大小最大值得堆百分比。默认值是60%

-XX:G1MixedGCLiveThresholdPercent=65

为混合垃圾回收周期中要包括的旧区域设置占用率阀值。默认占用率为65%

-XX:G1HeapWastePercent=10

设置愿意浪费的堆百分比。如果可回收百分比小于堆废物百分比,则不会启动混合垃圾回收周期。默认是10%

-XX:G1MixedGCCountTarget=8

设置标记周期后,对存活数据上限G1MixedGCLIveThresholdPercent的旧区域执行混合垃圾回收的目标次数

-XX:G1OldCSetRegionThresholdPercent=10

设置混合垃圾回收期间要回收的最大旧区域数。默认值是10%

持久代

-XX:PermSize=512m

初始化持久区内存池大小

-XX:MaxPermSize=2048m

最大持久区内存池大小

Java虚拟机栈

-Xss128k

设置每个线程的堆栈大小。在相同物理内存下,减小这个值能生成更多线程

直接内存设置

-XX:MaxDirectMemorySize

默认跟Xmx相等,所以生产环境中一般不设置Xmx大于物理内存的一半

其他垃圾回收参数

-XX:+ScavengeBeforeFullGC

年轻代GC优于Full GC执行

-XX:+DisableExplicitGC

不响应 System.gc() 代码

-XX:+UseThreadPriorities

启用本地线程优先级API。即使 java.lang.Thread.setPriority() 生效,不启用则无效。

-XX:SoftRefLRUPolicyMSPerMB=0

软引用对象在最后一次被访问后能存活0毫秒(JVM默认为1000毫秒)

辅助信息参数设置

-XX:+CITime

打印消耗在JIT编译的时间

-XX:ErrorFile=./hs_err_pid.log

保存错误日志或数据到指定文件中

-XX:HeapDumpPath=./java_pid.hprof

指定Dump堆内存时的路径,拿到heap文件后可以用Eclipse MAT进行分析,找出引起内存泄漏的class

-XX:+HeapDumpOnOutOfMemoryError

当首次遭遇内存溢出时Dump出此时的堆内存,与-XX:HeapDumpPath一起使用

-XX:OnError=";"

出现致命ERROR后运行自定义命令

XX:OnOutOfMemoryError=";"

当首次遭遇内存溢出时执行自定义命令

-XX:+PrintClassHistogram

按下 Ctrl+Break 后打印堆内存中类实例的柱状信息,同JDK jmap -histo 命令

-XX:+PrintConcurrentLocks

按下 Ctrl+Break 后打印线程栈中并发锁的相关信息,同JDK jstack -l 命令

-XX:+PrintCompilation

当一个方法被编译时打印相关信息

-XX:+PrintGC

每次GC时打印相关信息,与 -verbose:gc 是一样的,可以认为-verbose:gc -XX:+PrintGC的别名,打印日志稍有差异。

-XX:+PrintGCDetails

每次GC时打印详细信息

-XX:+PrintGCTimeStamps

打印每次GC的时间戳

-XX:+TraceClassLoading

跟踪类的加载信息

-XX:+TraceClassLoadingPreorder

跟踪被引用到的所有类的加载信息

-XX:+TraceClassResolution

跟踪常量池

-XX:+TraceClassUnloading

跟踪类的卸载信息

-Xloggc:../logs/gc.log

垃圾收集日志文件的输出路径

实际案例分析

大型网站服务器案例

承受海量访问的动态Web应用

服务器配置:8 CPU, 8G MEM, JDK 1.6.X

参数方案:

-server -Xmx3550m -Xms3550m-Xmn1256m -Xss128k -XX:SurvivorRatio=6 -XX:MaxPermSize=256m

-XX:ParallelGCThreads=8 -XX:MaxTenuringThreshold=0 -XX:+UseConcMarkSweepGC

调优说明:

-Xmx -Xms 相同以避免JVM反复重新申请内存。-Xmx 的大小约等于系统内存大小的一半,即充分利用系统资源,又给予系统安全运行的空间。

-Xmn1256m 设置年轻代大小为1256MB。此值对系统性能影响较大,Sun官方推荐配置年轻代大小为整个堆的3/8

-Xss128k 设置较小的线程栈以支持创建更多的线程,支持海量访问,并提升系统性能。

-XX:SurvivorRatio=6 设置年轻代中Eden区与Survivor区的比值。系统默认是8,根据经验设置为6,则2Survivor区与1Eden区的比值为2:6,一个Survivor区占整个年轻代的1/8

-XX:ParallelGCThreads=8配置并行收集器的线程数,即同时8个线程一起进行垃圾回收。此值一般配置为与CPU数目相等

-XX:MaxTenuringThreshold=0 设置垃圾最大年龄(在年轻代的存活次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率;如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。根据被海量访问的动态Web应用之特点,其内存要么被缓存起来以减少直接访问DB,要么被快速回收以支持高并发海量请求,因此其内存对象在年轻代存活多次意义不大,可以直接进入年老代,根据实际应用效果,在这里设置此值为0

-XX:+UseConcMarkSweepGC 设置年老代为并发收集。CMSConcMarkSweepGC)收集的目标是尽量减少应用的暂停时间,减少Full GC发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代内存,适用于应用中存在比较多的长生命周期对象的情况。

最后从内存溢出的异常来分析下我们设置的每个参数具体对应关系:

1java.lang.OutOfMemoryError: Javaheap space ----JVM Heap(堆)溢出

JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.JVM在启动的时候会自动设置Heap size的值,其初始空间(-Xms)物理内存的1/64,最大空间(-Xmx)物理内存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size的大小是Young GenerationOld Generaion 之和。
提示:在JVM中如果98%的时间是用于GC且可用的Heap size不足2%的时候将抛出此异常信息。

建议:Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms-Xmx选项设置为相同,而-Xmn1/4-Xmx值(与sun推荐的3/8稍有差异)。 
解决方法:调整Heap size大小。

2 java.lang.OutOfMemoryError: PermGen space  ---- PermGenspace溢出

PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。为什么会内存溢出,这是由于这块内存主要是被JVM存放ClassMeta信息的,Class在被Loader的时候被放入PermGen space区域,它和存放类实例(Instance)Heap区域不同。sunGC不会在主程序运行期对PermGen space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGenspace溢出。

建议:这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB 应用下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以达到减少jar 文档重复占用内存的目的。

解决方法:调整XX:MaxPermSize大小。

3java.lang.StackOverflowError   ---- 栈溢出

JVM依然是采用栈式的虚拟机,这个和CPascal都是一样的。函数的调用过程都体现在堆栈和退栈上了。调用构造函数的太多了,以致于把栈区溢出了。通常来讲,一般栈区远远小于堆区的,因为函数调用过程往往不会多于上千层,而即便每个函数调用需要 1K的空间(这个大约相当于在一个C函数内声明了256int类型的变量),那么栈区也不过是需要1MB的空间。通常栈的大小是12MB的。通常递归即使递归的层次不会过多,也很容易溢出。

解决方法:修改程序。

 

设计模式

 

分类    设计模式
创建型

工厂方法(Factory Method)、抽象工厂(Abstract Factory)、建造者模式(Builder)、原型模式(Prototype)、

单例模式(Singleton)

结构型

适配器模式(Adapter)、桥接模式(Bridge)、组合模式(ComPosite)、装饰器模式(Decorator)、门面模式(Facade)、

享元模式(Flyweight)、代理模式(Proxy)

行为型

解释器模式(Interpreter)、模板方法模式(Template Method)、责任链模式(Chain of Responsibility)、命令模式(Command)、

迭代器模式(Iterator)、调节者模式(Mediator)、备忘录模式(Memento)、观察者模式(Observer)、状态模式(State)、

策略模式(Strategy)、访问者模式(Visitor)

 

遵循原则

原则 解释
开闭原则 对扩展开放,对修改关闭
里氏代换原则 面向对象设计的基本原则之一。任何基类可以出现的地方,子类一定可以出现,是对“开-闭”原则的补充
依赖倒转原则 开闭原则的基础,针对接口编程,依赖于抽象而不依赖于具体
接口隔离原则 使用多个隔离的接口,比使用单个接口好
迪米特原则(最少知道原则) 一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立
合成复用原则 j尽量使用合成/聚合的方式,而不是使用继承

设计模式关系

面试问题整理_第5张图片

数据结构

数据结构 存储结构 性能(时间复杂度)
数组 顺序存储

指定下标查找:O(1)

指定值查找:O(n),有序数组可提高为O(logn)

插入删除:O(logn)

线性链表 链式存储

插入、删除:O(1)

查找:O(n)

二叉树 链式存储 插入,查找,删除:O(logn)
哈希表 链式存储 插入、删除,查找等操作:O(1)

哈希表

(1)主干就是数组

(2)哈希冲突

对某个元素进行哈希运算,得到一个存储地址,进行插入的时候,发现已被其他元素占用。

解决方案:开放地址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法、

链地址法

 

JAVA集合类框架

 

面试问题整理_第6张图片

1、Collection是集合接口,Collections是集合类

2、Set和List对比

 

接口 优缺点 子类
Set 检索效率低、删除和插入效率高,不会引起元素位置改变  
List i检索效率高、删除和插入效率低,会引起元素位置改变  

3、Vector

是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用

4、List接口的实现类

ArrayList 动态数组、线程不安全(不同步)、查找速度快、插入和删除效率低
LinkedList 双向链表、线程不安全(不同步)、查找速度慢、插入和删除效率高
Vector 动态数组、线程安全(同步)
Stack 继承自Vector,是一个先进后出的堆栈

5、Set接口的实现类

HashSet 没有重复元素、不保证元素顺序、线程不安全(非同步)、存取和查找性能高
LinkedHashSet 基于LinkedHashMap实现、单向链表、线程不安全(非同步)、有序以元素的添加顺序访问集合元素)
TreeSet

基于TreeMap实现、非线程安全(非同步)、有序(有自然排序和定制排序)、

不是通过hashcode和equals函数来比较元素的.它是通过compare或者comparaeTo函数

来判断元素是否相等

6、Map接口

HashMap 链表数组
LinkedHashMap 保留插入的顺序、迭代顺序可以是插入顺序或访问顺序、双向链表、线程不安全(非同步)
TreeMap

有序(排序方式有:自然排序、定制排序)、线程不安全(非同步)、基于红黑树

 

 

使用自定义类作为key时判断相等的标准是:两个key通过equals()方法返回为true,

并且通过compareTo()方法比较应该返回为0,其他的只要判断compareTo方法返回0

自然排序:所有的Key必须实现Comparable接口,并且所有key是同一类的对象

定制排序:创建一个comparator对象,不需要所有的Key都必须实现Comparable接口

 

SortedMap根据其键的自然自然顺序进行排序的WeakHashMap键是弱键

7、迭代器

Iterator:单向移动、remove是唯一安全修改集合的方式

ListIterator:继承于Iterator,基于只能由于List类型的访问、双向移动、可以通过add、remove、set安全的修改集合

8、集合实现类的原理

(1)HashMap

https://www.cnblogs.com/chengxiao/p/6059914.html#t1

原理:采用hash算法来决定集合中元素的位置

底层数据结构:由数组+链表组成,数组是HashMap的主体,链表是主要是为了解决哈希冲突存在的;

rehash过程:当容量超过负载因子,容器会进行扩充,这是会发生rehash,即重新计算原有元素的位置

初始容量默认为16,负载因子为默认为0.75(即填充到0.75后,数组扩充),扩充后的数组容量为原来的2倍

线程安全问题:数据丢失,链表死循环等问题。可以使用线程安全的Collections.synchronizedMap、ConcurrentHashMap、Hashtable

(1)ConcurrentHashMap

原理:和HashMap类似,当有修改操作时借助synchronize对table[i]的第一个节点进行锁定保证了线程安全以及使用了CAS来保证原子性,

使用volatile保证可见性

 

异常处理机制

面试问题整理_第7张图片

1、关键词

try catch finally throw throws

2、Error和RuntimeException是不需要捕捉

IO

 

IO方式    
同步阻塞BIO    
异步非阻塞AIO    
同步非阻塞NIO    

NIO

核心部分:Channels、Buffers、Selectors

 

核心部分 释义 实现
Channel

既可以从通道读取数据、又可以写数据到通道

通道可以异步写

通道的数据总是要先读到一个Buffer,或者从

一个Buffer读入

一个channel能读取数据到多个buffer,数据能

从多个buffer写到channel中

FileChannel:文件IO

DatagramChannel:UDP网络IO

SocketChannel、ServerSocketChannel:TCP网络IO

Buffer

检测多个NIO通道,并知晓通道是否为诸如

读写事件做好准备

ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、

IntBuffer、LongBuffer、ShortBuffer

这些Buffer覆盖了通过IO发送的基本数据类型:byte、short、

int、long、float、double、char,

还有个MappedByteBuffer表示内存映射文件

Selector 多路复用选择器:允许单线程处理多个channel  

1、Buffer

(1)基本用法

写入数据到Buffer --> 调用flip方法 --> 从Buffer读取数据 --> 调用clear或者compact方法

(2)主要方法

flip:将Buffer从写模式切换到读模式

clear:清空缓冲区

compact:只会清除已经读过得数据,未读的数据都会被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面

allocate:分配存储大小的

rewind:将position设置为0,可以重读Buffer中的所有数据

mark与reset:标记Buffer的特定position,之后调用reset可以恢复到这个特定的position

(3)属性

capacity:读写模式下,含义都是容量大小

position:写模式为从当前位置开始写,读模式为从当前位置开始写

limit:写模式为最多能往Buffer写多少数据,切换为读模式时,limit会被设置成写模式下position值,表示最多能读到多少数据

2、Selector

(1)基本使用

创建:Selector Selector = Selector.open();

注册通道:channel.configureBlocking(false);

SelectionKey key = channel.register(selector,SelectionKey.OP_READ)

注:Selector一起使用时,Channel必须处于非阻塞模式,意味着FileChannel不能与Selector一起使用

(2)Selector监听Channel的事件

SelectionKey.OP_CONNECT:连接就绪(某个channel成功连接到另一个服务器)

SelectionKey.OP_ACCEPT:接收就绪(serverSocketChannel准备好接收新进入的连接)

SelectionKey.OP_READ:读就绪

SelectionKey.OP_WRITE:写就绪

如果对不止一种事件感兴趣,可用“位或”操作符将常量连接起来

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE

 

3、通道的数据传输

如果两个通道中有个是FileChannel,那可以直接从一个channel传输到另一个channel,使用的方法为transferFrom和transferTo

 

4、Pipe

有一个source通道和一个sink通道,数据会被写到sink通道,从source通道读取

5、IO和NIO的区别

IO:面向流、阻塞的,适用于连接不多,这些连接会发送大量连接

NIO是面向缓冲、非阻塞的,channel是双向的,事件驱动模型、单线程处理多任务,IO多路复用大大提高了Java网络应用的可伸缩性和实用性

适用于有大量连接,这些连接每次只是发少量数据。

7、IO框架

(1)Netty

(a)特点

  • 一个高性能、异步事件驱动的NIO框架,提供了TCP、UDP和文件传输的支持
  • 使用更高线的socket底层,对epoll空轮询引起的cpu占用飙升在内部进行了处理,避免了直接使用NIO的陷阱,简化了NIO的处理方式
  • 采用多种编码、解码支持,对TCP粘包/分包进行自动化处理
  • 可使用线程池,提高连接效率,对重连、心跳检测的简单支持
  • 可配置IO线程数、TCP参数,TCP接收和发送缓冲区使用直接内存代替堆内存,通过内存池的方式循环利用ByteBuf
  • 通过引用计数器及时申请释放不再应用的对象,降低了GC频率
  • 使用单线程串行化的方式,高效的Reactor线程模型
  • 大量使用了volatile、CAS、原子类、线程安全类、读写锁

(b)线程模型

单线程模型:所有IO操作都由一个线程完成,即多路复用、事件分发和处理都在一个Reactor线程完成

多线程模型:有一个线程(Acceptor)只负责监听服务端,接收客户端的TCP连接请求;NIO线程池负责网络IO的操作,即消息的读取、解编码和发送

主从多线程模型:Acceptor线程用于绑定监听端口,接收客户端连接,将SocketChannel从主线程池的Reactor多路复用器上移除,重新注册到Sub线程池的线程上,用于处理IO的读写等操作,从而保证mainReactor值负责接入认证、握手的操作

(c)TCP粘包/拆包

原因:

  • 应用程序写入的字节大小大于套接字发送缓冲区的大小,会发生拆包现象,而应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,会发生粘包现象
  • 进行MSS大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包
  • 以太网帧的payload大于MTU进行ip分片

解决办法

 

  • 消息定长:FixedLengthFrameDecoder
  • 包尾增加特殊字符分隔:行分隔符类LineBasedFrameDecoder或自定义分隔符类DelimiterBasedFrameDecoder
  • 将消息分为消息头和消息体:LengthFieldBasedFrameDecoder

(d)序列化协议

 

协议 有点 缺点 应用场景
XML

人机可读性好,可指定元素或特性

的名称

序列化数据只包含数据本身以及类的结果,

不包含类型标识和程序集名称;

只能序列化公共属性和字段,不能序列化方法;

文件庞大、文件格式复杂,传输占带宽

当做配置文件存储数据,实时数据转换
JSON

兼容性高、数据格式比较简单;

易于读写、序列化后数据较小;

可扩展性好,兼容性好;

数据描述性差、不适合性能要求为ms级别、

额外空间开销大;

夸防火墙访问、可调式要求高、

基于Web browser的Ajax请求、

传输数据量相对较小、实时性要求低

Fastjson

接口简单易用、目前java语言中最快

的json库

过于注重快,偏离了标准及功能性、代码质量

不高,文档不全

协议交互、Web输出、Android客户端
Thrift

序列化后体积小,速度快;

支持多种语言和丰富的数据类型;

对于数据字段的增删具有较强的

兼容性;

支持二进制压缩编码;

使用者较少、夸防火墙时不安全;

不具有可读性,调试代码相对困难;

不能与其他传输层协议共同使用;

无法支持想持久层直接读写数据,

即不适合做数据持久化序列化协议;

分布式系统的RPC解决方案
Avro

支持丰富的数据类型、简单的动态

语言结合功能、具有自我描述属性、

提高了数据解析速度、快速可压缩

的二进制形式;

实现了远程过程调用RPC支持夸

编程语言实现

对于习惯性静态类型语音的用户不直观;

在Hadoop做Hive、Pig和MapReduce

的持久化数据格式

Protobuf

序列化后码流小、性能高;

结构化数据存储格式;

通过标识字段的顺序,可以实现

协议的前向兼容、结构化的文档

更容易维护

需要依赖于工具生成代码、支持的语音较少

对性能要求高的RPC调用,具有良好的

夸防火墙的访问属性、适合应用层的

持久化

(e)零拷贝实现

 

  • Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行socket读写,不需要进行字节缓冲区的二次拷贝
  • CompositeByteBuf类可以将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了传统内存拷贝的方式将几个小Buffer合并成一个大Buffer。
  • 通过FileRegion包装的FileChannel.transferTo方法实现文件传输,可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题
  • 通过wrap方法,我们可以将byte数组,ByteBuf、ByteBuffer。等包装成一个Netty ByteBuf对象,进而避免了拷贝操作

(f)高性能体现

 

  • 心跳:在服务端定时清除闲置会话,在客户端检测回话是否断开、是否重来、检测网络延迟,其中idleStateHandler类用来检测回话状态
  • 串行五锁化:消息的处理尽量在同一线程完成,期间不进行线程切换,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行
  • 可靠性:链路有效性检测:链路空闲检测机制,读/写空闲超时机制;内存保护机制:通过内存重用ByteBuf;ByteBuf的解码保护;优雅停机:不再接收新消息、推出前的预处理操作、资源的释放操作
  • 安全性:支持的安全协议:SSL V2和V3,TLS、SSL单向认证、双向认证和第三方认证
  • 高效并发编程:volatile、CAS、原子类的广泛使用;线程安全容器的使用;通过读写锁提升并发性能。IO通信性能三原则:传输(AIO)、协议(HTTP)、线程(主从多线程)
  • 流量整型(变压器):防止由于上下游网元性能不均衡导致下游网元被压垮,业务流中断;防止由于通信模块接收消息过快,后端业务线程处理不及时而导致撑死问题
  • TCP参数设置:SO_RECBUF和SO_SNDBUF:建议值128K或256K;SO_TCPNODELAY:NAGLE算法通过将缓冲区的小封包自动相连,组成较大的封包,阻止大量小封包的发送阻塞网络,从而提高网络应用效率,但是对于时延敏感的应用场景需要关闭该优化算法

(g)NIOEventLoopGroup源码

这里写图片描述

  • NioEventLoopGroup(其实是MultithreadEventExecutorGroup) 内部维护一个类型为 EventExecutor children [], 默认大小是处理器核数 * 2, 这样就构成了一个线程池,初始化EventExecutor时NioEventLoopGroup重载newChild方法,所以children元素的实际类型为NioEventLoop。

  • 线程启动时调用SingleThreadEventExecutor的构造方法,执行NioEventLoop类的run方法,首先会调用hasTasks()方法判断当前taskQueue是否有元素。如果taskQueue中有元素,执行 selectNow() 方法,最终执行selector.selectNow(),该方法会立即返回。如果taskQueue没有元素,执行 select(oldWakenUp) 方法

  • select ( oldWakenUp) 方法解决了 Nio 中的 bug,selectCnt 用来记录selector.select方法的执行次数和标识是否执行过selector.selectNow(),若触发了epoll的空轮询bug,则会反复执行selector.select(timeoutMillis),变量selectCnt 会逐渐变大,当selectCnt 达到阈值(默认512),则执行rebuildSelector方法,进行selector重建,解决cpu占用100%的bug。

  • rebuildSelector方法先通过openSelector方法创建一个新的selector。然后将old selector的selectionKey执行cancel。最后将old selector的channel重新注册到新的selector中。rebuild后,需要重新执行方法selectNow,检查是否有已ready的selectionKey。

  • 接下来调用processSelectedKeys 方法(处理I/O任务),当selectedKeys != null时,调用processSelectedKeysOptimized方法,迭代 selectedKeys 获取就绪的 IO 事件的selectkey存放在数组selectedKeys中, 然后为每个事件都调用 processSelectedKey 来处理它,processSelectedKey 中分别处理OP_READ;OP_WRITE;OP_CONNECT事件。

  • 最后调用runAllTasks方法(非IO任务),该方法首先会调用fetchFromScheduledTaskQueue方法,把scheduledTaskQueue中已经超过延迟执行时间的任务移到taskQueue中等待被执行,然后依次从taskQueue中取任务执行,每执行64个任务,进行耗时检查,如果已执行时间超过预先设定的执行时间,则停止执行非IO任务,避免非IO任务太多,影响IO任务的执行。

  • 每个NioEventLoop对应一个线程和一个Selector,NioServerSocketChannel会主动注册到某一个NioEventLoop的Selector上,NioEventLoop负责事件轮询。

  • Outbound 事件都是请求事件, 发起者是 Channel,处理者是 unsafe,通过 Outbound 事件进行通知,传播方向是 tail到head。Inbound 事件发起者是 unsafe,事件的处理者是 Channel, 是通知事件,传播方向是从头到尾。

  • 内存管理机制,首先会预申请一大块内存Arena,Arena由许多Chunk组成,而每个Chunk默认由2048个page组成。Chunk通过AVL树的形式组织Page,每个叶子节点表示一个Page,而中间节点表示内存区域,节点自己记录它在整个Arena中的偏移地址。当区域被分配出去后,中间节点上的标记位会被标记,这样就表示这个中间节点以下的所有节点都已被分配了。大于8k的内存分配在poolChunkList中,而PoolSubpage用于分配小于8k的内存,它会把一个page分割成多段,进行内存分配。

  • ByteBuf的特点:支持自动扩容(4M),保证put方法不会抛出异常、通过内置的复合缓冲类型,实现零拷贝(zero-copy);不需要调用flip()来切换读/写模式,读取和写入索引分开;方法链;引用计数基于AtomicIntegerFieldUpdater用于内存回收;PooledByteBuf采用二叉树来实现一个内存池,集中管理内存的分配和释放,不用每次使用都新建一个缓冲区对象。UnpooledHeapByteBuf每次都会新建一个缓冲区对象。

数据结构和算法

1、队列、栈

存取结构可以是数组或者链表

2、二叉树

树的性质:

 

  • 二叉树第i层上的节点数目最多为2i-1
  • 深度为k的二叉树至多有2k-1个结点
  • 包含n个节点的二叉树的高度至少为(log2n)+1
  • 任意一颗二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1

上述第四点由此推出:

 

三类结点加起来为总结点个数,于是便可得到:n=n0+n1+n2 (1)

由度之间的关系可得第二个等式:n=n0*0+n1*1+n2*2+1即n=n1+2n2+1 (2)

将(1)(2)组合在一起可得到n0=n2+1

 

树的遍历:前序、中序、后序、层序遍历

树的类型

满二叉树:高度为h,并且由2h-1个结点组成的二叉树,称为满二叉树

完全二叉树:只有最下面两层节点的度可以小于2,并且最下层的叶节点在靠左的若干位置上(多为1的结点个数为0或者1)

二叉树的插入、删除操作 https://blog.csdn.net/fengrunche/article/details/52305748

平衡二叉树:构建树时要求任何结点的深度不得过深(子树高度相差不超过1)

平衡二叉树的插入、修改操作 https://blog.csdn.net/javazejian/article/details/53892797

3、链表

合并两有序的链表、判断链表是否有环

4、字符串

KMS:https://blog.csdn.net/v_july_v/article/details/7041827

动态规划:https://blog.csdn.net/likailonghaha/article/details/53561646

5、海量数据处理

(1)处理技巧

 

  • 选用优秀的数据库工具
  • 编写优良的程序代码
  • 对海量数据进行分区操作
  • 建立广泛的索引
  • 建立缓存机制
  • 加大虚拟内存
  • 分批处理
  • 使用临时表和中间表
  • 优化查询SQL语句
  • 使用文本格式进行处理
  • 定制强大的清洗规则和出错处理机制
  • 建立视图或物化视图
  • 避免使用32位机子
  • 考虑操作系统问题
  • 使用数据仓库和多维数据库存储
  • 使用采样数据,进行数据挖掘

(2)处理方法

 

算法 原理 使用场景
Bloom Filter

https://blog.csdn.net/hguisu/article/details/7866173/

利用位数组表示一个集合,用hash函数映射元素到位数组的某一位

检索一个元素是否在一个集合中,有误判的概率
Hash

https://www.cnblogs.com/bjxsky/p/4622660.html

通过散列函数将任意长度的输入编程固定长度的输出,也可以

将不同的输入映射成为相同的输出,而且输出范围也是可控制,

所以起到了很好的压缩映射和等价映射功能

分而治之,降低数据规模
Bit-Map

https://blog.csdn.net/wang_zhenwei/article/details/52126669

用一个bit位来标记某个元素对应的Value

可进行数据的快速查找,判重,删除

去重数据达到压缩数据

https://blog.csdn.net/juanqinyang/article/details/51418629

也称为优先队列,可以看成二叉树表示,因为堆中元素有序,

所以使用数组来表示

得到最大或最小的k个值
双层桶划分

https://blog.csdn.net/luyafei_89430/article/details/13017815

因为元素范围很大,不能利用直接寻址表,所以通过多次划分,

逐步确定范围,然后最后在一个可以接受的范围内进行。

可以通过多次缩小,双层只是一个例子,分治才是其根本

(只是“只分不治”)。

第k大,中位数,不重复或重复的数字
数据库索引

 

 
倒排索引

存储在全文搜索下某个单词在一个文档或者一组文档中的

存储位置的映射

搜索引擎、关键字查询
外排序

https://blog.csdn.net/qisefengzheng/article/details/46008575

在内存外的排序,采取排序-归并的策略

大数据的排序,去重
Trie

https://blog.csdn.net/ts173383201/article/details/7858598

字典树,又称单词查找或键树

文本词频统计
MapReduce

https://blog.csdn.net/qisefengzheng/article/details/46008893

将数据交给不同的机器去处理,数据划分,结果归约

数据量大,但是数据种类小可以放入内存

 

Linux

 

常用命令

https://www.cnblogs.com/ccy1106/p/6637661.html

nautilus 目录:从命令行打开文件夹

/文字:在打开的文件中查找

gg:跳到文件头

G:跳到文件尾

Spring

JAVA应用程序的开发提供综合、广泛的基础性支持的JAVA平台。帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发。Spring主要分为20多个模块,主要分为核心容器、数据访问/继承、Web、AOP、工具、消息和测试模块。如图

面试问题整理_第8张图片

1、控制反转(IOC)、依赖注入

控制反转:是指在运行时使用装配器对象来绑定耦合对象的一种编程技巧,对象之间耦合关系在编译时通常是未知的。在使用控制反转的情况下,业务逻辑的流程是由对象关系图来决定的,该对象关系图由装配器负责实例化,这种实现方式还可以将对象之间的关联关系的定义抽象化,而绑定过程是通过“依赖注入”实现的。

 

依赖注入:在编译阶段尚未知所需的功能是来自哪个类的情况下,将其他对象所依赖的功能对象实例化的模式。这就需要一种机制用来激活相应的组件以提供特定的功能,所以依赖注入是控制反转的基础。依赖注入有三种实现方式:构造器注入、Setter方法注入、接口注入

2、Spring设置元数据的方式

基于XML、注解、JAVA的配置

3、SpringMVC原理

M-Model 模型(完成业务逻辑:有javaBean构成,service+dao+entity)

V-View 视图(做界面的展示  jsp,html……)

C-Controller 控制器(接收请求—>调用模型—>根据结果派发页面)

 

第一步:用户发起请求到前端控制器(DispatcherServlet)

第二步:前端控制器请求处理器映射器(HandlerMappering)去查找处理器(Handle):通过xml配置或者注解进行查找

第三步:找到以后处理器映射器(HandlerMappering)向前端控制器返回执行链(HandlerExecutionChain)

第四步:前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)

第五步:处理器适配器去执行Handler

第六步:Handler执行完给处理器适配器返回ModelAndView

第七步:处理器适配器向前端控制器返回ModelAndView

第八步:前端控制器请求视图解析器(ViewResolver)去进行视图解析

第九步:视图解析器像前端控制器返回View

第十步:前端控制器对视图进行渲染

第十一步:前端控制器向用户响应结果

4、Bean的生成和生命周期(BeanFactory和ApplicationContext)

 

  • 实例化Bean之前,如果容器注册了InstantiationAwareBeanPostProcessor,则在实例化Bean之前,调用接口的postProcessBeforeInstantiation方法
  • 根据配置情况调用Bean构造函数或工厂方法实例化Bean
  • 如果容器注册了InstantiationAwareBeanPostProcessor,则实例化Bean之后,调用该接口的postProcessAfterInstantiation方法
  • 如果配置了属性信息,那么容器就将配置值设置到Bean对应的属性中,在设置之前会调用InstantiationAwareBeanPostProcessor接口的postProcessPropertyValue方法
  • 调用Bean的属性设置方法设置属性值
  • 如果Bean实现了BeanNameAware,将调用setBeanName方法,将配置文件中该Bean对应的名称设置到Bean中
  • 如果Bean实现了BeanFactoryAware,将调用setBeanFactory方法,将BeanFactory实例设置到Bean中
  • 如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文(同样这个方式也可以实现步骤4的内容,但比4更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法)                 注:该步骤在ApplicationContext的Bean生命周期调用
  • 如果BeanFactory装配了BeanPostProcessor,则将调用BeanPostProcessor的postProcessBeforeInitialization
    (Object bean,String beanName)方法对Bean进行加工处理(如AOP、动态代理等)
  • 如果Bean实现了InitializingBean接口,则将调用接口的afterPropertiesSet方法
  • 如果中通过init-method属性定义了初始化方法,则将执行这个方法
  • 如果BeanFactory装配了BeanPostProcessor,则将调用BeanPostProcessor的postProcessAfter 
  • 如果中指定Bean的作用范围为Prototype,则将Bean返回给调用者,调用者负责Bean的生命周期管理,Spring不再管理这个Bean的生命周期,如果是singleton,则将Bean放入Ioc容器的缓存池中,并将引用返回给调用者,Spring继续对这些Bean进行后续的生命周期管理
  • 对于singleton的Bean,容器关闭时,将触发Spring对Bean后续生命周期的管理工作。如果Bean实现了DisposableBean接口,则将调用接口的destroy方法,可以在此编写释放资源、记录日志等操作
  • 对于singleton的Bean,如果通过的destory-method指定Bean的销毁方法,Spring将执行这个方法

5、AOP

一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如日志、缓存、事务管理等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ,动态代理的代表是Spring AOP。

AOP框架静态代理:在编译阶段织入Java字节码中,运行的时候就是经过增强后的AOP对象

AOP框架动态代理:在运行阶段动态生成新的代理类,有两种方式分别为JDK动态代理和CGLIB

6、如何保持高并发?高并发下如何保持性能?

单例模式+ThreadLocal。

单例模式大大节省了对象的创建和销毁,提高了性能,Spring把每个线程可能存在安全问题的参数放进了ThreadLocal,不同线程下的数据时相互隔离,保证了线程安全。

注:SpringMVC和Servlet都是方法级别的线程安全,如果单例的Controller或Servlet中存在实例变量,则是线程不安全的。

解决方法:

 

  •  在Controller中使用ThreadLocal变量,将不安全实例变量封装进ThreadLocal
  • 声明Controller为原型(prototype),每个请求都创建新的Controller实例
  • Controller不使用实例变量

7、事务管理的使用和原理以及传播属性

使用:在Service之上或者Service的方法之上,添加@Transaction注解

原理:

 

  • 配置文件开启注解驱动,在相关类或方法通过@Transaction标识
  • spring在启动的时候会去解析生成相关Bean,这时候会查看相关拥有相关注解的类和方法,并为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关事务处理了
  • 真正的数据库层的事务提交和回滚是通过binlog和redo log实现的

传播属性:

 

常量名称 常量解释
PROPAGATION_REQUIRED(默认) 支持当前事务,如果当前没有事务,就新建一个事务
PROPAGATION_REQUIRED_NEW 新建事务,如果当前存在事务,把当前事务挂起
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行
PROPAGATION_MANDATORY 支持当前事务,如果当前没有存在事务,就抛出异常
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
PROPAGATION_NEVER 以非事务执行,如果当前存在事务,则抛出异常
PROPAGATION_NESTED

如果一个活动的事务存在,则运行在一个嵌套的事务中。

如果没有活动事务存在,则按REQUIRED属性执行。

它使用了一个单独的事务,该事务拥有多个可以回滚的保存点。

内部事务回滚不会对外部造成影响。

只对DataSourceTransactionManager事务管理器有效。

8、解决循环依赖引用

注:只支持singleton作用域的,setter方式的循环依赖,不支持构造器方式和prototype的循环依赖。

 

  • A对象先通过构造函数创建对象引用,如果依赖对象还没创建引用,则先创建引用
  • 如果依赖对象还有依赖对象,则按照A对象处理
  • 最后一个被依赖对象创建引用后,上一个对象调用setter方法
  • 层层创建引用,再层层回溯调用setter。

9、过滤器和拦截器

过滤器:依赖于Servlet容器,在实现上基于函数回调,可以对所有的请求进行过滤,但一个过滤器实例在一次完整请求中只被调用一次

拦截器:依赖于web框架,在实现上基于Java的反射机制,属于AOP的一种应用,一个拦截器实例在一个Controller生命周期之内多次调用,但是只能对Controller请求进行拦截,对其他的如静态资源的请求则没办法拦截。

 

 

MySQL数据库

SQL优化

语句优化

(1) 减少使用select * 、null判断、!=或<>、or、对字段进行表达式计算或函数操作、join操作、in或not in或having(使用exists、not exists替代)、两端模糊匹配

(2)尽量用join代替子查询

(3)尽量用union all代替union

(4)开启查询缓存,并为查询缓存优化查询语句

合适的数据类型

(1)使用可存下数据的最小数据类型

(2)使用合理的字段属性长度,固定长度的表会更快

(3)尽可能使用not null定义字段

(4)尽量少用text,非用不可最好分表

合适的索引列

(1)查询频繁的列,在where、group by、order by、on从句中出现的列

(2)where条件中<,<=,=,>,>=,between,in,以及like字符串+通配符(%)出现的列

(3)长度小的列,索引字段越小越好,因为数据的存储单位是页,一页可能存下的数据越多越好

(4)离散度大(不同的值多)的列,放在联合索引前面

使用命令分析

(1)SHOW查看状态

SHOW [SESSION|GLOBAL] STATUS LIKE '%Status_name%';

session(默认):取出当前窗口的执行
global:从mysql启动到现在

(a)查看查询次数(插入次数com_insert、修改次数com_update、删除次数com_delete、连接数connections、数据运行时间uptime、慢查询次数slow_queries、索引使用情况handler_read%)

 

mysql> SHOW STATUS LIKE 'com_select';

 handler_read_key:这个值越高越好,越高表示使用索引查询到的次数。

handler_read_rnd_next:这个值越高,说明查询低效

(b)显示系统变量

 

mysql> SHOW VARIABLES LIKE '%Variables_name%';

(c)显示InnoDB存储引擎的状态

 

mysql> SHOW ENGINE INNODB STATUS;

(2)EXPLAIN 分析查询

查询出来的各列含义

 

列名 含义
table 表名
type 连接的类型
 

-const

主键、索引

-eq_reg

主键、索引的范围查找

-ref

连接的查找(join

-range

索引的范围查找

-index

索引的扫描

-all

全表扫描

possible_keys 可能用到的索引
key 实际使用的索引
key_len 索引的长度,越短越好
ref 索引的一列被使用了
rows MySQL认为必须检查的用来返回请求数据的行数
extra

using filesort、using temporary(常出现在使用order by)时需优化

-using filesort额外排序。看到这个时,查询就需要优化了

-using temporary 使用了临时表。看到这个时,也需要优化

select_type  
partitions  
filtered  

(3)PROFILE

(a)开启profile,查看当前SQL执行时间

 

mysql> SET PROFILING=ON; 
mysql> SHOW profiles;

(b)查看所有用户的当前连接。包括执行状态、是否锁表等

mysql> SHOW processlist;

(c)PROCEDURE ANALYSE()取得建议

通过分析select查询结果对现有的表的每一列给出优化的就建议

 

mysql> SELECT column_name FROM table_name PROCEDURE ANALYSE();

(d)OPTIMIZE TABLE回收闲置的数据库空间

 

mysql> OPTIMIZE TABLE table_name;

MyISAM表:当表上的数据行被删除时,所占据的磁盘空间并没有立即被回收,使用该命令后这些空间将被回收,并对磁盘上的数据行进行重排。

InnoDB表:该命令被映射到ALTER TABLE,这会重建表。重建操作能更新索引统计数据并释放簇索引中的未使用的空间。

该命令只需在批量删除数据行后,或定期进行一次数据表(针对特定的表)优化操作即可。

(e)REPAIR TABLE修复被破坏的表

 

mysql> REPAIR TABLE table_name;

(f)CHECK TABLE检查表是否有错误

 

mysql> CHECK TABLE table_name;

开启慢查询

(1)简介

开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好优化数据系统的性能。

(2)参数说明

slow_query_log 慢查询日志开启状态
slow_query_log_file 慢查询日志存放的位置(需MySQL的运行账号的可写权限)
long_query_time 查询超过多少秒才记录

(3)pt-query-digest

用于分析mysql慢查询的一个工具,它可以分析binlog、General log、slowlog,也可以通过SHOWPROCESSLIST或者通过tcpdump抓取的MySQL协议数据来进行分析。可以把分析结果输出到文件中,分析过程是先对查询语句的条件进行参数化,然后对参数化以后的查询进行分组统计,统计出各查询的执行时间、次数、占比等,可以借助分析结果找出问题进行优化。

分区分表

分区

1.分区类型

 

分区类型 解释 示例
Range 把连续区间按范围划分
create table user(
    id int(11),
    money int(11) unsigned not null,
    date datetime
)
partition by range(YEAR(date))(
    partition p2014 values less than (2015),
    partition p2015 values less than (2016),
    partition p2016 values less than (2017),
    partition p2017 values less than maxvalue
);
List

把离散值分成集合,按集合划分,

适合有固定取值列的表

create table user(
    a int(11),
    b int(11)
)
partition by list(b)(
    partition p0 values in (1,3,5,7,9),
    partition p1 values in (2,4,6,8,0)
);
Hash 随机分配,分区数固定
create table user(
    a int(11),
    b datetime
)
partition by hash(YEAR(b))
partitions 4;
Key

l类似Hash,区别是只支持一列或多列,

且MySQL提供自身的Hash函数

create table user(
    a int(11),
    b datetime
)
partition by key(b)
partitions 4;

2、分区管理

(a)新增分区

 

ALTER TABLE sale_data ADD PARTITION (PARTITION p201710 VALUES LESS THAN (201711));

(b)删除分区

 

--当删除了一个分区,也同时删除了该分区中所有的数据。
ALTER TABLE sale_data DROP PARTITION p201710;

(c)合并分区

 

ALTER TABLE sale_data
REORGANIZE PARTITION p201701,p201702,p201703,
p201704,p201705,p201706,
p201707,p201708,p201709 INTO
(
    PARTITION p2017Q1 VALUES LESS THAN (201704),
    PARTITION p2017Q2 VALUES LESS THAN (201707),
    PARTITION p2017Q3 VALUES LESS THAN (201710)
);

3、注意事项

(a)做分区时,要么不定义主键,要么把分区字段加入到主键中

(b)分区字段不能为NULL

分表

1、垂直分表

(a)把常用和不常用的字段分开放

(b)把大字段单独存放在一张表中

2、水平分表

 

分表方式 解释 实例
按时间
平板式 查询时比较繁琐,分页不易实现
article_201701
article_201702
article_201703
归档式 可以解决性能问题,但是旧表容量还是对象较大
article_old
article_new
     

 
 
按板块
对应式

一个板块对应一张表,缺点是如果板块数量大且不易确定,

分出的表容易很多

news_category
news_article
sports_category
sports_article
冷热式

这个表汽车、火箭表是属于热门表,定义为新建的

块放在unite表里面,待到其超过一万张主贴的时候才开对应表结构

tieba_汽车
tieba_飞机
tieba_火箭
tieba_unite

 
 
按Hash

哈希结构通常用于博客之类的基于用户的场合,在博客这样的系统里有几个特点

,1是用户数量非常多,2是每个用户发的文章数量都较少,3是用户发文章不定

期,4是每个用户发得不多,但总量仍非常之大
blog_aa
blog_ab
blog_ac


 锁

1、 锁类型

表级锁:开销小、加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高

页面锁:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般 

注:

1、MyISAM和MEMORY存储引擎采用的是表级锁(table-level-locking);

BDB存储引擎采用的是页面锁(page-level-locking),同时也支持表级锁;

InnoDB存储引擎既支持行级锁,也支持表级锁,默认情况下是采用行级锁

2、InnoDB行锁

通过给索引项加锁实现,即只有通过索引条件检索数据,InnoDB才使用行级锁。

共享锁:允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁

排它锁:允许获得排它锁的事务更新数据,阻止其他事务获得相同数据集的共享读锁和排他写锁

3、意向锁

为了允许行锁和表锁共存,实现多粒度机制;同时还有两种内部使用的意向锁(都是表锁),分别为意向共享锁和意向排它锁

事物

原子性:事务是一个院子操作单元,其对数据修改,要么全部执行,要么全部不执行

一致性:在事务开始和完成时,数据都必须保持一致状态

隔离性:数据库系统提供一定的隔离机制,保证事务在不受外部并发影响的“独立”环境下执行

持久性:事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持

(1)并发事务

问题类型 原因
更新丢失 最后的更新覆盖了由其他事务所做的更新
脏读 一个事务对一条记录修改,在该事务未提交之前,第二个事务读取了该记录,并据此做进一步处理
不可重复读 一个事务在读取某些数据后某个时间再次读取该数据却发现读出的数据已经发生改变或者删除
幻读 一个事务按相同的查询条件重新读取以前检索过得数据,却发现其他事务插入满足其查询条件的新数据

(2)隔离

(a)在读取数据前,对其加锁,阻止其他事务对数据进行修改

(b)不加任务锁,通过一定机制生成一个数据请求时间点的一致性数据快照,并用此快照来提供一定级别(语句级或事务级)的一致性读取,

该技术叫做数据多版本控制并发控制

(c)隔离级别

隔离级别 一致性 脏读 不可重复读 幻读
未提交读 只能保证不读物理上损坏的数据
已提交读 语句级
可重复读 事务级
可序列化 最高级别,事务级

存储引擎

InnoDB

    支持事务处理、外键、行锁;不支持FULLTEXT类型的索引;不保存表的具体行数,扫描表来计算由多少行;不加锁读取;

    DELETE表时是一行一行的删除;数据和索引放在表空间里面;跨平台可直接拷贝使用;必须包含AUTO_INCREMENT类型字段的索引;表格很难被压缩

MyISAM

    不支持事务,回滚将造成完全回滚,不具有原子性、外键;支持全文搜索;保存表的行数,不带where时,直接返回保存的行数;DELETE表时,先drop表,然后重建表;表存放在三个文件:frm存放表格定义,myd是数据文件,myi是索引文件跨平台很难直接拷贝;可以使用AUTO_INCREMENT类型字段建立联合索引;表格可以被压缩

 

选择:因为MyISAM相对简单所以在效率上要优于InnoDB。如果系统读多写少,对原子性要求低,选择MyISAM;如果系统读少,写多的时候,尤其并发写入高的时候,选择InnoDB。

 

 

索引

1、定义

索引是帮助MySQL高效获取获取的数据结构

2、实现

目前大部分数据库系统及文件系统采用B-Tree和B+Tree作为索引结构

3、原理

IO次数取决于b+树的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量时m,则h=log(m+1)N,当数据量N一定的情况下,m越大,h越小,而m=磁盘块的大小/数据项的大小,磁盘块大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量小。

悲观锁和乐观锁

悲观锁:只允许一个线程操作同一记录

乐观锁:一种检测机制,一般通过版本号或时间戳实现

Redis

1、数据结构

 

结构类型 结构存储的值 结构的读写能力 使用场景
String 字符串、整数、浮点数

操作整个或部分字符串

整数和浮点数自增或者

自减操作

缓存功能:

字符串最经典的使用场景,redis最为缓存层,Mysql作为储存层,

绝大部分请求数据都是redis中获取,由于redis具有支撑高并发特性,

所以缓存通常能起到加速读写和降低 后端压力的作用。

 

计数器:

许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、

查询缓存的功能,同时数据可以一步落地到其他的数据源。

如:视频播放数系统就是使用redis作为视频播放数计数的基础组件。

 

共享session:出于负载均衡的考虑,分布式服务会将用户信息的访问

均衡到不同服务器上,用户刷新一次访问可能会需要重新登录,

为避免这个问题可以用redis将用户session集中管理,

在这种模式下只要保证redis的高可用和扩展性的,

每次获取用户更新或查询登录信息都直接从redis中集中获取。

 

限速:

处于安全考虑,每次进行登录时让用户输入手机验证码,

为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率。

list

链表:链表上每个节

点都包含了一个字符串

从链表的两端推入或弹出元素;

根据偏移量对链表进行修剪;

读取单个或多个元素;

根据值查找或者移除元素;

消息队列

redis的lpush+brpop命令组合即可实现阻塞队列

多个客户端保证了消费的负载均衡

使用列表技巧: 
lpush+lpop=Stack(栈) 
lpush+rpop=Queue(队列) 
lpush+ltrim=Capped Collection(有限集合) 
lpush+brpop=Message Queue(消息队列)

set

包含字符串的无序

收集器,并

且是不重复的

添加、获取、移除单个元素;

检查一个元素是否存在于集合中;

计算交集、并集、差集;

从集合里随机获取元素;

标签(tag)

sadd=tagging(标签)

spop/srandmember=random item(生成随机数,比如抽奖)

sadd+sinter=social Graph(社交需求)

hash 包含键值对的无序散列表

添加、添加、移除单个键值对,

获取所有键值对

哈希结构相对于字符串序列化缓存信息更加直观,

并且在更新操作上更加便捷。

所以常常用于**用户信息**等管理,

但是哈希类型和关系型数据库有所不同,

哈希类型是稀疏的,而关系型数据库是完全结构化的,

关系型数据库可以做复杂的关系查询,

而redis去模拟关系型复杂查询开发困难,维护成本高

zset

字符串成员与浮点数分值

之间的有序映射,

元素的排列顺序由分值

大小决定

获取、添加、删除单个元素,

根据分值范围或者成员获取元素

排行榜

Hash类型底层的两种实现

 

2、持久化机制

(1)RDB

原理:RDB方式的持久化是通过快照完成的,当符合一定条件时Redis会自动将内存中所有数据进行快照并存储在硬盘中。实现快照流程如下

 

  • redis使用fork函数复制一份当前进程(父进程)的副本(子进程)
  • 父进程继续接收并处理客户端发来的命令,而子进程开始讲内存中的数据写入硬盘中的临时文件
  • 当子进程写入玩所有数据后会用该临时文件替换旧的RDB文件,至此一次快照操作完成

在执行fork函数时,操作系统(类Unix操作系统)会使用写时复制策略,即fork函数发生的一刻父子进程共享同一内存数据,当父进程要更改其中某片数据时,操作系统会将该片数据复制一份以保证子进程的数据不受影响,所以新的RDB文件存储的是fork函数开始执行到完成时完整的内存数据。

配置:

save 900 1

900秒钟内只有一个键被更改则进行更改

可以存在多个条件,条件之间是“或”关系

禁用自动快照,只需将所有的save参数删除即可

可以手动发送SAVE或BGSAVE命令让Redis执行快照,两个命令的区别在于,前者是自由进程进行快照,会阻塞其他请求,后者会通过fork子进程进行快照操作

(2)AOF

原理:开启AOF持久化后每执行一条会更改Redis中数据的命令,Redis就会将该命令写入硬盘中的AOF文件[只针对写数据命令];

虽然每次执行更改数据库内容的操作时,AOF都会讲命令记录在AOF文件中,但由于操作系统缓存机制,数据并没有真正写入硬盘,而是进入系统的硬盘缓存。默认情况下系统每30秒执行一次同步操作。

配置:

appendonly yes 开启

auto-aof-rewrite-percentage 100 当前AOF文件大小超过上次重写时AOF文件大小的百分之多少时再次重写,如果没有重写过,则以启动时AOF文件大小为依据

auto-aof-rewrite-min-size 64mb 允许重写的最小AOF文件大小

appendfsync everysec/always/no 每秒执行一次同步操作/每次执行写入都会执行同步/不主动进行同步

重新启动Redis后,Redis会使用AOF文件来恢复数据

 

版本控制

SVN/CVS:集中式版本控制系统,有一个包含所有版本文件的单个服务器和一个数字(版本号),客户端从该Server上检出文件(只是文件,本地没有仓库的概念)

git:分布式版本控制系统(跟SVN一样有自己的集中式版本库),客户端不只是检出文件的最新快照,它们有完全镜像的存储库(本地有仓库,这就是分布式的意义)

集中式svn和分布式(git)的区别

 

  • git是每个历史版本都存储完整的文件,便于恢复;svn是存储差异文件,历史版本不可恢复
  • git可离线完成大部分操作,svn则不能
  • git有着更优雅的分支和合并实现
  • git有着更强的撤销和修改历史版本的能力
  • git速度更快,效率更高

基于以上区别,git有了更明显的优势,特别在于它具有的本地仓库

 

 

============================================================================

Springboot

1、简介

用来简化spring应用的初始搭建以及开发过程,使用特定方式来进行配置(properties或者yml文件),有以下特点:

  • 创建独立的spring引导程序main方法运行
  • 嵌入的Tomcat无需部署war文件
  • 简化maven配置
  • 自动配置spring添加对应功能starter自动化配置

2、常用的starter

spring-boot-starter-web:嵌入Tomcat与web开发需要servlet与jsp支持

spring-boot-starter-websocket:支持使用Tomcat开发websocket应用

spring-boot-starter-data-jpa:数据库支持

spring-boot-starter-data-solr:solr支持

spring-boot-starter-redis: redis数据库支持

spring-boot-starter-jdbc:支持jdbc访问数据库

spring-boot-starter-aop:面向切面编程

spring-boot-starter-freemarker:支持FreeMarker模板引擎

spring-boot-starter-log4j:添加Log4j的支持

 

3、自动配置原理

在springmain方法中添加@SpringBootApplication或者@EnableAutoConfiguration,使用SpringFactoriesLoader读取每个starter中的spring.factories文件,该文件配置了所有需要被创建spring容器中的bean。

SpringApplication执行流程

1)如果使用的是SpringApplication的静态run方法,那么这个方法里面首先创建一个SpringApplication对象实例,然后调用这个创建好的SpringApplication的实例方法。实例化之前,会先做几件事情:

 

  • 根据classpath里面是否存在某个特征类(ConfigurableWebApplicationContext)来决定是否应该创建一个为Web应用使用的ApplicationContext类型
  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer
  • 使用SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationListener
  • 推断并设置main方法的定义类

2)实例化后,就开始执行run方法逻辑,首先遍历执行所有通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListener,调用它们的started方法,告诉这些SpringApplicationRunListener,SpringBoot要开始执行

3)创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)

4)遍历调用SpringApplicationRunListener的environmentPrepared方法,通知它们SpingBoot应用使用的Environment准备好了

5)如果SpringApplication的showBanner属性被设置为true,则打印banner

6)根据用户是否明确设置了applicationContextClass类型以及初始化的推断结果,决定改为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成,然后根据条件是否添加ShutdownHook,决定是否使用BeanNameGenerator以及ResourceLoader,然后将Environment设置给创建好的ApplicationContext使用

7)ApplicationContext创建好之后,SpringApplication会再次借助SpringFactoriesLoader,查找并加载classpath中所有可用的ApplicationContextInitializer,然后遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法对已经创建好的ApplicationContext进一步处理。

8)遍历调用所有SpringApplicationRunListener的contextPrepared方法

9)最核心的一步,将之前的@EnableAutoConfiguration获取的所有配置以及其他形式的Ioc容器配置加载到已经准备好的ApplicationContext

10)遍历调用所有SpringApplicationRunListener的contextLoader方法

11)调养ApplicationContext的refresh方法,完成Ioc容器可用的最后一道工序

12)查找当前ApplicationContext是否注册有CommandLineRunner,如果有,遍历执行它们

13)正常情况下,遍历执行SpringApplicationRunListener的finished方法(如果出现异常,依然会调用所有的SpringApplicationRunListener的finished方法,只不过会将异常信息一并传入)

 

4、读取配置文件的方式

默认读取配置文件为application.properties或者是application.yml

SpringCloud

1、实现服务的注册和实现

注册:服务在发布时指定对应的服务名(服务名包括了IP地址和端口)将服务注册到注册中心(eureka/consul/ZooKeeper),

实现:该过程SpringCloud自动实现,只需在 main方法添加@EnableDiscoveryClient,同一服务修改端口就可以启动多个实例

调用:传递服务名称通过注册中心获取所有可用实例,通过负载均衡策略调用(ribbon和feign)对应的策略

ribbon和feign的区别

ribbon:添加依赖spring-starter-ribbon,使用@RibbonClient(value="服务名称")和RestTemplate调用远程服务对应的方法

feign:添加依赖spring-starter-feign,服务提供方提供对外接口,调用方在接口上使用@FeignClient("指定服务名")

 

  • 启动类使用注解:ribbon用的是@RibbonClient;Feign用的是@EnableFeignClients
  • 服务的指定位置:Ribbon是在@RibbonClient注解上声明;Feign则在定义抽象方法的接口中使用@FeignClient声明
  • 调用方式:Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务;Feign采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求,不过需要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致

2、断路器的作用

当一个服务调用另一个服务由于网络原因或者自身原因出现问题时,调用者就会等待被调用者的响应,当更多服务请求到这些资源时,导致更多的请求等待,就会发生连锁效应,断路器用来解决这一问题。

断路器状态

完全打开:一定时间内,达到一定次数无法调用,并且多次检测没有恢复的迹象,断路器完全打开,那么下次请求就不会请求到该服务

半开:短时间内有恢复迹象,断路器会将部分请求分给该服务,当能正常调用时,断路器关闭

关闭:当服务一直处于正常状态,能正常调用,断路器关闭

3、模块

Config:配置管理开发工具包,可把配置放到远程服务器,目前支持本地存储、Git及Subversion

Bus:事件、消息总线,用于在集群中传播状态变化,可与Config联合实现部署

Netflix:针对多种Netflix组件提供的开发工具包

 

  • Eureka:云端负载均衡,一个基于REST的服务,用于定位服务,以实现云端的负载均衡和中间层服务器的故障转移
  • Hystrix:容错管理工具,旨在通过控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力
  • Zuul:边缘服务工具,是提供动态路由、监控、弹性、安全等的边缘服务
  • Archaius:配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等

For Cloud Foundry:通过Oauth2协议绑定服务到CloudFoundry,CloudFoundry是VMware推出的开源PaaS云平台

Foundry Service Broker:为建立管理云托管服务的服务代理提供了一个起点

Cluster:基于ZooKeeper、Redis、Hazelcast、Consul实现的领导选举和平民状态模式的抽象和实现

Consul:封装了Consul操作,consul是一个服务发现与配置工具,与Docker容器可以无缝集成

Security:安全工具包,为应用添加安全机制,主要是指OAuth2

Sleuth:日志收集工具包,封装了Dapper,Zipkin和HTrace操作

Data Flow:云本地程序和操作模型,组成数据微服务在一个结构化的平台上

Stream:数据流操作开发包,封装了与Redis、Rabbit、Kafka等发送接收消息

Stream App Starters:基于 Spring Boot 为外部系统提供 Spring 的集成

Task:短生命周期的微服务,为 Spring Boot 应用简单声明添加功能和非功能特性

Task App Starters:可以是任何进程包括不再执行的Spring批任务,并且在有限的数据处理之后停止的Spring Boot应用

ZooKeeper:操作ZooKeeper的工具包,用于使用ZooKeeper方式的服务注册与发现

for Amazon Web Services:快速和亚马逊网络服务集成

Connectors:便于 PaaS 应用在各种平台上连接到后端像数据库和消息经纪服务

CLI:基于Spring Boot CLI,可以以命令行方式快速建立云组件

Contract:实现消费者驱动契约的方法

Gateway:基于项目反应堆的智能和可编程路由器

OpenFeign:通过自动配置以及绑定到Spring环境和其他Spring编程模型的习惯,为Spring Boot应用提供集成

你可能感兴趣的:(面试问题整理)