面试集锦200道

一、java基础

1.JDK 和 JRE 有什么区别?

JDK是java开发工具包,是程序员使用java语言编写java程序所需的开发工具包,是提供给程序员使用的。JDK包含了JRE;JRE是java运行时环境,包含了java虚拟机,java基础类库。

 

2.==和 equals 的区别是什么?

==”在进行数据比较时, 如果数据是基本数据类型,比较的是数据的值;如果数据是引用类型,“==”比较的是对象的内存地址。equals方法,在比较数据是否相同时,只能比较引用类型,所在Object类类中,equals方法额实现,是比较内存直至,如果我们想要比较对象的值,必须重写equals方法,给予比较的标准。在重写equals的同时,也要重写hashCode方法.

 

3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?

不一定相同。正常情况下,因为equals()方法比较的就是对象在内存中的值,如果值相同,那么Hashcode值也应该相同。但是如果不重写hashcode方法,就会出现不相等的情况。

 

4.final 在 java 中有什么作用?

在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量),当final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。  当final修饰的方法时,表示此方法不能被重写;当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;当final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。

 

5.java 中的 Math.round(-1.5) 等于多少?

round是四舍五入,注意负数5是舍的,例如:Math.round(1.5)值是2,Math.round(-1.5)值是-1;

floor就是直接去掉小数保留整数,即如果参数是正数则小数部分全舍,参数是负数则小数部分全入。 例如:Math.floor(2.6)的值是2,Math.floor(-2.1)的值是-3。

 

6.String 属于基础的数据类型吗?

java 中String 是个对象,是引用类型 ,基础类型与引用类型的区别是,基础类型只表示简单的字符或数字,引用类型可以是任何复杂的数据结构 ,基本类型仅表示简单的数据类型,引用类型可以表示复杂的数据类型,还可以操作这种数据类型的行为 。

java虚拟机处理基础类型与引用类型的方式是不一样的,对于基本类型,java虚拟机会为其分配数据类型实际占用的内存空间,而对于引用类型变量,他仅仅是一个指向堆区中某个实例的指针。

7.java 中操作字符串都有哪些类?它们之间有什么区别?

String、StringBuffer、StringBuilder

String : final修饰,String类的方法都是返回new String。即对String对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。

StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。

StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以new StringBuilder对象,调用StringBuilder对象的append、replace、delete等方法修改字符串。

执行效率:String

 

8.String str1="str1"与 String str1=new String("str1")一样吗?

String str1 = “str1”:

1、 在栈中创建str1的引用。

2、 去常量池中查看是否有相同Unicode编码的字符串常量。如果有将str1指向该常量,如果没有则创建一个内容为”str1”的字符串常量,将str1的引用指向该常量。

3、 如果str1进行了赋值str1= “123”,则栈中的str1的引用指向新的内容为“123”的字符串常量。

4、 String str1 = “str1”; String str2 = “str1”;str2的引用指向常量池中已经存在的内容为“str1”的常量,不会重新创建新的常量,str1和str2的引用指向同一个常量。

String str1 = new String(“str1”):

1、 在常量池中创建内容为“str1”的对象,在堆中创建内容为“str1”的对象。

2、 String str1 = new String(“str1”); String str2 = new String(“str1”);str2不会指向之前创建的对象,而是重新创建一个对象。str1 != str2;

 

9.如何将字符串反转?

方法1 通过 charAt(int index)返回char值进行字符串拼接

方法2 把字符串转换成字符数组首位对调位置

方法3 把字符串转换成字符数组倒叙拼接然后返回值

方法4 调用StringBuffer中的reverse方法

方法5 递归方法

 

10.String 类的常用方法都有那些?

获取方法:length()、charAt(int index)、substring(beginIndex, intendIndex)、int indexOf(String str);

比较方法:equals()、equalsIgnoreCase(string s)、startsWith(String prefix)、endsWith()

大小写转换:toUpperCase()、toLowerCase()

其他方法:trim()、split(String str)、replace(char oldChar, charnewChar)、 contains(String s)

 

11.抽象类必须要有抽象方法吗?

抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象(构造用private修饰)。如果一个类中有一个抽象方法,这个类必须声明为抽象类,否则编译通不过。

 

12.普通类和抽象类有哪些区别?

1.抽象类不能被实例化。

2.抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态。

3.抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体

4.含有抽象方法的类必须申明为抽象类

5.抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类。

 

13.抽象类能使用 final 修饰吗?

1.final修饰变量,则等同于常量

2.final修饰方法中的参数,称为最终参数。

3.final修饰类,则类不能被继承

4.final修饰方法,则方法不能被重写。

final 不能修饰抽象类,final修饰的方法可以被重载 但不能被重写

 

14.接口和抽象类有什么区别?

区别:

a. 抽象类可以有构造方法,接口中不能有构造方法。

b. 抽象类中可以有普通成员变量,接口中没有普通成员变量。

c. 抽象类中可以包含非抽象普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的方法。

d. 抽象类中的抽象方法的访问权限可以是 public、protected 和(默认类型,虽然 eclipse 不报错,但也不能用,默认类型子类不能继承),接口中的抽象方法只能是 public 类型的,并且默认即为 public abstract 类型。

e. 抽象类中可以包含静态方法,在 JDK1.8 之前接口中不能不包含静态方法,JDK1.8 以后可以包含。

f. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问权限可以是任意的,但接口中定义的变量只能是 public static final 类型的,并且默认即为 public static final 类型。

g. 一个类可以实现多个接口,用逗号隔开,但只能继承一个抽象类,接口不可以实现接口,但可以继承接口,并且可以继承多个接口,用逗号隔开。

 

15.java 中 IO 流分为几种?

根据流性质划分分为以下类型:

按流向分(站在程序角度考虑):输入流(input)、输出流(output)

按类型分:字节流(InputStream/OutputStream),任何文件都可以通过字节流进行传输。

字符流(Reader/Writer),非纯文本文件,不能用字符流,会导致文件格式破坏,不能正常执行。

节点流(低级流:直接跟输入输出源对接)

FileInputStream/FileOutputStream/FileReader/FileWriter/PrintStream/PrintWriter.

处理流(高级流:建立在低级流的基础上)

转换流:InputStreamReader/OutputStreamWriter,字节流转字符流/字符流转字节流

缓冲流:BufferedInputStream/BufferedOutputStream   BufferedReader/BufferedReader可对节点流经行包装,使读写更快。

 

16.BIO、NIO、AIO 有什么区别?

IO的方式通常分:同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO

BIO是一个连接一个线程。NIO是一个请求一个线程。AIO是一个有效请求一个线程

 

17.Files的常用方法都有哪些?

创建:    createNewFile()创建一个空文件、mkdir()  创建指定单级文件夹。mkdirs() 创建指定多级文件夹。

renameTo(File dest)如果目标文件与源文件是在同一个路径下,那么renameTo的作用是重命名, 如果目标文件与源文件不是在同一个路径下,那么renameTo的作用就是剪切,而且还不能操作文件夹。

删除:  delete()  删除文件或者一个空文件夹,不能删除非空文件夹,马上删除文件,返回一个布尔值。

      deleteOnExit()jvm退出时删除文件或者文件夹,用于删除临时文件,无返回值。

判断:  exists()  文件或文件夹是否存在。

isFile()  是否是一个文件,如果不存在,则始终为false。

isDirectory()  是否是一个目录,如果不存在,则始终为false。

isHidden()  是否是一个隐藏的文件或是否是隐藏的目录。

isAbsolute()  测试此抽象路径名是否为绝对路径名。

获取:    getName()  获取文件或文件夹的名称,不包含上级路径。

getAbsolutePath()获取文件的绝对路径,与文件是否存在没关系

length()  获取文件的大小(字节数),如果文件不存在则返回0L,如果是文件夹也返回0L。

getParent()  返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null。

lastModified()获取最后一次被修改的时间。

文件夹相关:static File[] listRoots()列出所有的根目录(Window中就是所有系统的盘符)

list()  返回目录下的文件或者目录名,包含隐藏文件。对于文件这样操作会返回null。

listFiles()  返回目录下的文件或者目录对象(File类实例),包含隐藏文件。对于文件操作会返回null。

list(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件操作会返回null。

listFiles(FilenameFilter filter)返回指定当前目录中符合过滤条件的子文件或子目录。对于文件这样操作会返回null。

 

二、容器

18.java 容器都有哪些?

数组,String,java.util下的集合容器

 

19.Collection 和 Collections 有什么区别?

java.util.Collection 是一个集合接口(集合类的一个顶级接口),Collections则是集合类的一个工具类/帮助类

 

20.List、Set、Map 之间的区别是什么?

List(列表):

 List的元素以线性方式存储,可以存放重复对象,List主要有以下两个实现类:

ArrayList : 长度可变的数组,可以对元素进行随机的访问,向ArrayList中插入与删除元素的速度慢。 JDK8 中ArrayList扩容的实现是通过grow()方法里使用语句newCapacity = oldCapacity + (oldCapacity >> 1)(即1.5倍扩容)计算容量,然后调用Arrays.copyof()方法进行对原数组进行复制。

LinkedList: 采用链表数据结构,插入和删除速度快,但访问速度慢。

Set(集合):

 Set中的对象不按特定(HashCode)的方式排序,并且没有重复对象,Set主要有以下两个实现类:

HashSet: HashSet按照哈希算法来存取集合中的对象,存取速度比较快。当HashSet中的元素个数超过数组大小*loadFactor(默认值为0.75)时,就会进行近似两倍扩容(newCapacity = (oldCapacity << 1) + 1)。

TreeSet :TreeSet实现了SortedSet接口,能够对集合中的对象进行排序。

Map(映射):

 Map是一种把键对象和值对象映射的集合,它的每一个元素都包含一个键对象和值对象。 Map主要有两个实现类:

HashMap:HashMap基于散列表实现,其插入和查询的开销是固定的,可以通过构造器设置容量和负载因子来调整容器的性能。 

LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得的顺序是其插入次序,或者是最近最少使用(LRU)的次序。

TreeMap:TreeMap基于红黑树实现。查看时,它们会被排序。TreeMap是唯一的带有subMap()方法的Map,subMap()可以返回一个子树

 

21.HashMap 和 Hashtable 有什么区别?

底层都是数组+链表实现

Hashtable:

1.无论是key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个Hashtable,效率低

2.初始size为11,扩容:newsize=oldsize*2+1

Hashmap:

1.可以存储null键和null值,线程不安全

2.初始size为16,扩容:newsize =oldsize*2,size一定为2的n次幂

null可以作为键,这样的键只有一个,但可以有一个或多个键所对应的值为null.当get()方法返回null值时,即可以表示Hashmap中没有该key,也可以表示该key所对应的value为null。因此,在Hashmap中不能由get()方法来判断Hashmap中是否存在某个key,应该用containsKey()方法来判断。

 

22.如何决定使用 HashMap 还是 TreeMap?

HashMap 查询 基于散列表实现(推荐作为常规Map使用。)。

TreeMap 增加、快速创建 基于红黑树实现。

 

23.说一下 HashMap 的实现原理?

HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好

 

24.说一下 HashSet 的实现原理?

对于HashSet而言,它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet 的实现比较简单,相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成

 

25.ArrayList 和 LinkedList 的区别是什么?

共性:ArrayList与LinkedList都是List接口的实现类,因此都实现了List的所有未实现的方法,只是实现的方式有所不同。

区别:List接口的实现方式不同

1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。

3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。

 

26.如何实现数组和 List 之间的转换?

List转数组:toArray(arraylist.size()方法

数组转List:Arrays的asList(a)方法

 

27.ArrayList 和 Vector 的区别是什么?

1、 Vector的方法都是同步的(Synchronized),是线程安全的(thread-safe),而ArrayList的方法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能比Vector好。

2、 当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样,ArrayList就有利于节约内存空间。

 

28.Array 和 ArrayList 有何区别?

1、存储内容比较:

Array 数组可以包含基本类型和对象类型,

ArrayList 却只能包含对象类型。

Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。

 

2、空间大小比较:

Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。

ArrayList 的空间是动态增长的,而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。

3.方法上的比较:

ArrayList 方法上比 Array 更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。

 

29.在 Queue 中 poll()和 remove()有什么区别?

Queue中 add/offer,element/peek,remove/poll中的三个方法均为重复的方法,在选择使用时不免有所疑惑,这里简单区别一下:

1、add()和offer()区别:

add()和offer()都是向队列中添加一个元素。一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,调用 add() 方法就会抛出一个 unchecked 异常,而调用 offer() 方法会返回 false。因此就可以在程序中进行有效的判断!

 2、poll()和remove()区别:

remove() 和 poll() 方法都是从队列中删除第一个元素。如果队列元素为空,调用remove() 的行为与 Collection 接口的版本相似会抛出异常,但是新的 poll() 方法在用空集合调用时只是返回 null。因此新的方法更适合容易出现异常条件的情况。

3、element() 和 peek() 区别:

element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。

下面是Java中Queue的一些常用方法:

add         增加一个元索                      如果队列已满,则抛出一个IIIegaISlabEepeplian异常

remove   移除并返回队列头部的元素     如果队列为空,则抛出一个NoSuchElementException异常

element  返回队列头部的元素              如果队列为空,则抛出一个NoSuchElementException异常

offer       添加一个元素并返回true        如果队列已满,则返回false

poll         移除并返问队列头部的元素     如果队列为空,则返回null

peek       返回队列头部的元素              如果队列为空,则返回null

put         添加一个元素                       如果队列满,则阻塞

take        移除并返回队列头部的元素   

 

30.哪些集合类是线程安全的?

1、线程安全(Thread-safe)的集合对象:

Vector、HashTable、java.util.concurrent下的集合

2、非线程安全的集合对象:

ArrayList、LinkedList 、HashMap、HashSet、TreeMap、TreeSet

 

31.迭代器 Iterator 是什么?

提供了一些方法专门处理集合中的元素.例如删除和获取集合中的元素.该对象就叫做迭代器(Iterator).

 

32.Iterator 怎么使用?有什么特点?

使用方法:hasNext() 、next()、remove()

特点:

1、Iterator遍历集合元素的过程中不允许线程对集合元素进行修改,否则会抛出ConcurrentModificationEception的异常。

2、Iterator遍历集合元素的过程中可以通过remove方法来移除集合中的元素。

3、Iterator必须依附某个Collection对象而存在,Iterator本身不具有装载数据对象的功能。

4、Iterator.remove方法删除的是上一次Iterator.next()方法返回的对象。

强调以下next()方法,该方法通过游标指向的形式返回Iterator下一个元素

 

33.Iterator 和 ListIterator 有什么区别?

区别:

1. ListIterator有add()方法,可以向List中添加对象,而Iterator不能

2. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。

3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

4. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改

 

34.怎么确保一个集合不能被修改?

为了防止别人改变此List或Map,就使其不可修改:Collections.unmodifiableMap(xxxMap);

 

三、多线程

35.并行和并发有什么区别?

并行是多线程的一种形式,多线程是并发的一种形式。异步也是并发的一种形式

 

36.线程和进程的区别?

区别:

地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。

一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程

执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

线程是处理器调度的基本单位,但是进程不是。两者均可并发执行。

 

37.守护线程是什么?

守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,

1、守护线程,比如垃圾回收线程,就是最典型的守护线程。

2、用户线程,就是应用程序里的自定义线程

 

38.创建线程有哪几种方式?

1、继承Thread类

2、实现 Runnable接口

3、实现Callable接口,通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的线程。

 

39.说一下 runnable 和 callable 有什么区别?

区别:

(1)、Callable规定的方法是call(),Runnable规定的方法是run().

(2)、Callable的任务执行后可返回值,而Runnable的任务是不能返回值得

(3)、call方法可以抛出异常,run方法不可以

(4)、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

 

40.线程有哪些状态?

创建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)

 

41.sleep() 和 wait() 有什么区别?

1、这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。

sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。

2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。

Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。

3、使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

synchronized(x){

      x.notify()

     //或者wait()

   }

4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

 

42.notify()和 notifyAll()有什么区别?

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁

 

43.线程的 run()和 start()有什么区别?

1、方法的定义

start()方法在java.lang.Thread类中定义;而run()方法在java.lang.Runnable接口中定义,必须在实现类中重写。

2、新线程创建

当程序调用start()方法时,会创建一个新线程,然后执行run()方法。但是如果我们直接调用run()方法,则不会创建新的线程,run()方法将作为当前调用线程本身的常规方法调用执行,并且不会发生多线程。

3、多次调用

start()方法不能多次调用,否则抛出java.lang.IllegalStateException;而,run()方法可以进行多次调用,因为它只是一种正常的方法调用。

 

44.创建线程池有哪几种方式?

newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

 

45.线程池都有哪些状态?

线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated

 

46.线程池中 submit()和 execute()方法有什么区别?

1、接收的参数不一样execute(Runnable x)、

2、submit有返回值,而execute没有

用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么?然后我就可以把所有失败的原因综合起来发给调用者。

3、submit方便Exception处理

如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

 

47.在 java 程序中怎么保证多线程的运行安全?

线程安全在三个方面体现:

1.原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);

2.可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized,volatile);

3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则

synchronized保证原子性和可见性,volatile保证可见性和有序性,

 

48.多线程锁的升级原理是什么?

在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。

 

49.什么是死锁?

 线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

 

50.怎么防止死锁?

避免死锁的技术:加锁顺序、加锁时限、死锁检测

 

51.ThreadLocal 是什么?有哪些使用场景?

早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。为每个线程都创建一个这样的变量(以ThreadLocal对象为键、任意对象为值的存储结构),这个变量被附带在线程上,每个线程之接相互隔离,互不干扰,该变量副本只能创建它的线程能使用。

JDBC数据访问连接线程池

 

52.说一下 synchronized 底层实现原理?

 

 

53.synchronized 和 volatile 的区别是什么?

区别:

1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的

3.volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性

4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

 

54.synchronized 和 Lock 有什么区别?

区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题

 

55.synchronized 和 ReentrantLock 区别是什么?

相同点:这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的(操作系统需要在用户态与内核态之间来回切换,代价很高,不过可以通过对锁优化进行改善)。

区别:这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。

 

56.说一下 atomic 的原理?

Atomic包是java.util.concurrent下的另一个专门为线程安全设计的Java包,包含多个原子操作类。这个包里面提供了一组原子变量类

 

四、反射

57.什么是反射?

Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。

 

58.什么是 java 序列化?什么情况下需要序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,将数据分解成字节流,以便存储在文件中或在网络上传输。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。

 

59.动态代理是什么?有哪些应用?

动态代理:当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。

动态代理的应用:Spring的AOP,加事务,加权限,加日志。

 

60.怎么实现动态代理?

动态代理实现:首先必须定义一个接口,还要有一个InvocationHandler(将实现接口的类的对象传递给它)处理类。再有一个工具类Proxy(习惯性将其称为代理类,因为调用他的newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。

JDK动态代理:基于Java反射机制实现,必须要实现了接口的业务类才能用这种办法生 成代理对象。新版本也开始结合ASM机制。

CGLIB动态代理:基于ASM机制实现,通过生成业务类的子类作为代理类。不能应用到被代理对象的final方法上

 

五、对象拷贝

61.为什么要使用克隆?

clone方法是在复制一个对象,赋值的对象是单独独立的,有独自的内存空间。clone方法是java中顶层父类Object中的一个方法,此方法在java中为实现,是一个native方法,也就是本地方法(可以调用底层操作系统的方法),在调用本地方法创建对象,比直接new创建对象效率高.

 

62.如何实现对象克隆?

在使用clone方法创建对象时,必须实现一个接口:Cloneable,此接口没有任何方法,是一个标识接口。由于Object类中,clone方法的权限修饰是protected,所以我们在克隆对象时,被克隆的对象要重写克隆方法。并且将权限修改成public,在别的地方可以调用这个克隆方法。

 

63.深克隆和浅克隆区别是什么?

        * 浅克隆,在clone对象时,只会把基本数据类型的数据进行复制过去;如果是引用类型,只会把引用复制过去,也就是被克隆对象和原始对象信息,共同引用一个引用类型的属性。

        * 深克隆:在克隆时,会把基本数据类型的数据和引用类型的数据,同时复制。克隆对象和原始对象不共同引用一个引用类型

缺点:在深克隆时,如果引用对象关系比较复杂,克隆时会很麻烦,因为每一个对象都要克隆。

解决方案:可以使用序列化进行解决。

 

六、Java Web

64.jsp 和 servlet 有什么区别?

区别和联系:

1.jsp经编译后就变成了Servlet.(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)

2.jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.

3.Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.

Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。

 

65.jsp 有哪些内置对象?作用分别是什么?

九大内置对象:Page,pageContext,request,response,session,application,out,config,exception

Page指的是JSP被翻译成Servlet的对象的引用.

pageContext对象可以用来获得其他8个内置对象,可以作为JSP的域范围对象使用.pageContext中存的值是当前的页面的作用范围

request代表请求对象,可以用于获得客户机的信息,也可以作为域对象来使用,使用request保存的数据在一次请求范围内有效。

Session代表一次会话,可以用于保存用户的私有的信息,也可以作为域对象使用,使用session保存的数据在一次会话范围有效

Application:代表整个应用范围,使用这个对象保存的数据在整个web应用中都有效。

Response是响应对象,代表的是从服务器向浏览器响应数据.

Out:JSPWriter是用于向页面输出内容的对象

Config:指的是ServletConfig用于JSP翻译成Servlet后 获得Servlet的配置的对象.

Exception:在页面中设置isErrorPage=”true”,即可使用,是Throwable的引用.用来获得页面的错误信息。

 

66.说一下 jsp 的 4 种作用域?

page:用户请求的当前页面;

Request:用户请求访问的当前组件,以及和当前web组件共享同一用户请求的web组件。如:被请求的jsp页面和  该页面用指令包含的页面以及标记包含的其它jsp页面;  

Session:同一个http会话中的web组件共享它;

Application:整个web应用的所用web组件共享它。

作用范围:

1. page指当前页面。在一个jsp页面里有效

2.request 指从http请求到服务器处理结束,返回响应的整个过程。在这个过程中使用forward方式跳转多个jsp。在这些页面里你都可以使用这个变量。

3.Session 有效范围当前会话,从浏览器打开到浏览器关闭这个过程。

4.application它的有效范围是整个应用。

 

67.session 和 cookie 有什么区别?

区别:

1、cookie数据存放在客户的浏览器上,session数据放在服务器上。

2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。

3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能;考虑到减轻服务器性能方面,应当使用COOKIE。

4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

(建议:将登陆信息等重要信息存放为SESSION, 其他信息如果需要保留,可以放在COOKIE中)

 

68.说一下 session 的工作原理?

当用户访问到一个服务器,如果服务器启用Session,服务器就要为该用户创建一个SESSION,在创建这个SESSION的时候,服务器首先检查这个用户发来的请求里是否包含了一个SESSION ID,如果包含了一个SESSION ID则说明之前该用户已经登陆过并为此用户创建过SESSION,那服务器就按照这个SESSION ID把这个SESSION在服务器的内存中查找出来(如果查找不到,就有可能为他新创建一个),如果客户端请求里不包含有SESSION ID,则为该客户端创建一个SESSION并生成一个与此SESSION相关的SESSION ID。这个SESSION ID是唯一的、不重复的、不容易找到规律的字符串,这个SESSION ID将被在本次响应中返回到客户端保存,而保存这个SESSION ID的正是COOKIE,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。

 

69.如果客户端禁止 cookie 能实现 session 还能用吗?

可以有其他机制在COOKIE被禁止时仍然能够把Session id传递回服务器。有一种技术叫做URL重写,就是把Session id直接附加在URL路径的后面一种是作为URL路径的附加信息,表现形式为:

http://…/xxx;jSession=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764;

另一种是作为查询字符串附加在URL后面,表现形式为:http://…/xxx?jSession=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764

还有一种就是表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把Session id传递回服务器。

 

70.spring mvc 和 struts的区别是什么?

1、项目中使用了spring,相对于融合来说,spring和SpringMVC是非常完美的无缝链接。

2、SpringMVC是方法级别的拦截,而Struts是类级别的拦截

3、入口不同,SpringMVC的入口是servlet,而Struts是filter

4、注解开发,SpringMVC基本上是零配置,而Struts需要配置很多。

(注:在Struts中可以配置开发者模式,当修改了一个URL访问,可以不用重启工程。但是SpringMVC需要重启。)

 

71.如何避免 sql 注入?

对于jsp而言:

1、PreparedStatement采用预编译语句集,它内置了处理SQL注入的能力,使用它的setXXX方法传值;

2.使用正则表达式过滤传入的参数;

3.字符串过滤;

4.jsp中调用该函数检查是否包含非法字符;

5.JSP页面判断代码;

对于MyBatis而言:

通过使用#{}代替${}变量赋值,防止SQL注入。(注:#{}是经过预编译的,是安全的;${}是未经过预编译的,仅仅是取变量的值,是非安全的)

 

72.什么是 XSS 攻击,如何防御?

XSS即跨站脚本攻击,是一种常见于web应用程序中的计算机安全漏洞.XSS通过在用户端注入恶意的可运行脚本,若服务器端对用户输入不进行处理,直接将用户输入输出到浏览器,然后浏览器将会执行用户注入的脚本。

防御:1、对输入和URL参数进行过滤(白名单和黑名单);2、对输出进行编码  (注:cookie.setHttpOnly(true))

 

73.什么是 CSRF 攻击,如何防御?

CSRF即跨站请求伪造,是一种夹持用户在已经登陆的web应用程序上执行非本意的操作的攻击方式。相比于XSS,CSRF是利用了系统对页面浏览器的信任,XSS则利用了系统对用户的信任。

服务器端防御:

1、重要数据交互采用POST进行接收,当然是用POST也不是万能的,伪造一个form表单即可破解

2、使用验证码,只要是涉及到数据交互就先进行验证码验证,这个方法可以完全解决CSRF。但是出于用户体验考虑,网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段,不能作为主要解决方案。

3、验证HTTP Referer字段,该字段记录了此次HTTP请求的来源地址,最常见的应用是图片防盗链。PHP中可以采用APache URL重写规则进行防御,可参考:http://www.cnblogs.com/phpstudy2015-6/p/6715892.html

4、为每个表单添加令牌token并验证(注:a.要确保同一页面中每个表单都含有自己唯一的令牌,b.验证后需要删除相应的随机数)。

 

七、异常

74.throw 和 throws 的区别?

1、throws出现在方法函数头;而throw出现在函数体。

2、throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

3、两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

 

75.final、finally、finalize 有什么区别?

final :

1、修饰符(关键字) 如果一个类被声明为final,意味着它不能再派生新的子类,不能作为父类被继承。因此一个类不能及被声明为abstract,又被声明为final的。

2、将变量或方法声明为final,可以保证他们使用中不被改变。被声明为final的变量必须在声明时给定初值,而以后的引用中只能读取,不可修改,被声明为final的方法也同样只能使用,不能重载。

finally:

在异常处理时提供finally块来执行清楚操作。如果抛出一个异常,那么相匹配的catch语句就会执行,然后控制就会进入finally块,如果有的话。

finalize:

finalize是Object类的方法,作用在垃圾收集器将对象从内存中清除之前做必要的清理工作。此方法是在垃圾收集器在确定了,被清理对象没有被引用的情况下调用的。

 

76.try-catch-finally 中哪个部分可以省略?

try语句后面是可以省略catch语句的,但是必须有finally语句。也可以省略finally语句,但是必须要有catch语句。try语句后面必须要有别的语句:try-catch、try-finally、try-catch-finally(切记:catch和finally语句不能同时省略)

 

77.try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

       不管有没有异常,finally中的代码都会执行

 

78.常见的异常类有哪些?

空指针异常类型:NullPointerException

类型强制转换类型:ClassCastException

数组下标越界异常:ArrayIndexOutOfBoundsException

违背安全原则异常:SecturityException

文件未找到异常:FileNotFoundException

字符串转换为数字异常:NumberFormatException

操作数据库异常:SQLException

输入输出异常:IOException

方法未找到异常:NoSuchMethodException

算数异常类:ArithmeticExecption

安全异常:SecurityException

 

八、网络

79.http 响应码 301 和 302 代表的是什么?有什么区别?

301 redirect: 301 代表永久性转移(Permanently Moved)、302 redirect: 302 代表暂时性转移(Temporarily Moved ),区别:301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。

 

80.forward 和 redirect 的区别?

1、请求方不同:redirect:客户端发起的请求;forward:服务端发起的请求

2、浏览器地址表现不同:redirect:浏览器地址显示被请求的url;forward:浏览器地址不显示被请求的url

3、参数传递不同:redirect:重新开始一个request,原页面的request生命周期结束;forward:forward另一个连接的时候。request变量是在其生命周期内的。另一个页面也可以使用,其实质是把目标地址include。

4、底层运作不同:redirect:发送的请求信息又回送给客户机,让客户机再转发到另一个资源上,需要在服务器和客户机之间增加一次通信;forward:服务器端直接找到目标,并include过来。

5、定义不同:直接转发方式(Forward,RequestDispatcher类的forward()方法):客户端和浏览器只发出一次请求,Servlet、HTML、JSP或其它信息资源,由第二个信息资源响应该请求,在请求对象request中,保存的对象对于每个信息资源是共享的;间接转发方式(Redirect,HttpServletRequest类的sendRedirect()方法)实际是两次HTTP请求,服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。

 

81.简述 TCP 和 UDP的区别?

1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。TCP通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。

3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。

4.每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

5、TCP对系统资源要求较多,UDP对系统资源要求较少。

 

82.TCP为什么要三次握手,四次挥手?

这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

 

83.说一下 TCP 粘包是怎么产生的?

(1)发送方原因

   TCP默认使用Nagle算法。而Nagle算法主要做两件事:1)只有上一个分组得到确认,才会发送下一个分组; 2)收集多个小分组,在一个确认到来时一起发送。所以,正是Nagle算法造成了发送方有可能造成粘包现象。

(2)接收方原因

TCP接收到分组时,并不会立刻送至应用层处理,或者说,应用层并不一定会立即处理;实际上,TCP将收到的分组保存至接收缓存里,然后应用程序主动从缓存里读收到的分组。这样一来,如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会被存至缓存,应用程序读时,就会读到多个首尾相接粘到一起的包。

 

84.OSI 的七层模型都有哪些?

应用层:用户接口,无限接近用户。数据单位是APDU (telnet,HTTP,FTP,WWW,NFS,SMTP)

表示层:数据的表现形式,特定功能的实现,比如加密压缩等。数据单位是PPDU (加密,ASII等)

会话层:对应用会话的管理,同步。确定网络数据是否要经过远程会话 。数据单位是SPDU (RPC,SQL等)

传输层:确定传输的可靠性以及每种协议的端口号,传输前的错误检测,流控。数据单位是TPDU (TCP,UDP)

网络层:提供逻辑地址(IP地址)、选路(选择传输路线)。数据单位是报文 (IP,IPX等)

数据链路层:成帧,用Mac地址访问媒介,错误检测与修正。数据单位是帧 (ATM,FDDI等)

物理层:设备之间比特流的传输,物理接口,电气特性等等。常见的设备有网线,网卡等等。数据单位是比特

 

85.GET和POST 请求有哪些区别?

GET和POST是HTTP协议中的两种发送请求的方法。HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,即GET/POST都是TCP链接。GET和POST能做的事情是一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。

区别:GET产生一个TCP数据包;POST产生两个TCP数据包。(对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200之后,再返回数据。)

 

86.如何实现跨域请求?

跨域是由浏览器的同源策略引起的,是指页面请求的url地址,必须与浏览器上url地址处于同域上(即域名,端口,协议相同)。为了防止某域名下的接口被其他域名下的网页非法调用,是浏览器对javascript施加的安全限制

解决方法: jsonp、CORS 、

 

87.说一下 JSONP 实现原理?

原理:通过script标签引入的js不受同源策略的限制,而XmlHttpRequest 对象受到同源策略的影响,可以加载跨域服务器上的脚本,用 JSONP 获取的不是 JSON 数据,而是可以直接运行的 JavaScript 语句。jsonp包含两个参数:回调函数和数据。回调函数是当响应到来时要放在当前页面被调用的函数。数据就是传入回调函数中的json数据,也就是回调函数的参数了。

 

 

九、设计模式

88.说一下你熟悉的设计模式?

创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

 

 

89.简单工厂和抽象工厂有什么区别?

简单工厂:模式本身很简单而且使用在业务较简单的情况下。一般用于小项目或者具体产品很少扩展的情况(这样工厂类才不用经常更改)。

工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,根据逻辑不同,产生具体的工厂产品。如例子中的Driver类。

抽象产品角色:它一般是具体产品继承的父类或者实现的接口。由接口或者抽象类来实现。如例中的Car接口。

具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现,如例子中的Benz、Bmw类。

抽象工厂:

抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。

具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它由具体的类来实现。

抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。

具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。

 

十、Spring/Spring MVC

90.为什么要使用 Spring?

Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。它是为了解决企业应用开发的复杂性而创建的。好处:

1.方便解耦,便于开发(Spring是一个大工厂,IOC可以将所有对象的创建和依赖关系维护都交给spring管理)

2.spring支持AOP编程(spring提供面向切面编程,方便对应用的日志记录、权限、安全、事务管理)

3.声明式事务的支持(通过配置就完成对事务的支持,不需要手动编程)

4.方便程序的测试,spring 对junit4支持,可以通过注解方便的测试spring 程序

5.方便集成各种优秀的框架(Struts 、Hibernate、MyBatis等)

6.降低javaEE API的使用难度(Spring 对javaEE开发中非常难用的一些API 例如JDBC,javaMail,远程调用等,都提供了封装,是这些API应用难度大大降低)

 

91.解释一下什么是 AOP?

即面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

 

92.解释一下什么是IOC?

IOC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IOC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。

 

93.Spring 有哪些主要模块?

Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。

1,Spring Core:Core是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能,Sprign的所有功能都是借助IOC实现的。

2,AOP:AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截器,供用户自定义和配置。

3,ORM:Spring 的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支持常用的Hibernate,ibtas,jdao等框架的支持,Spring本身并不对ORM进行实现,仅对常见的ORM框架进行封装,并对其进行管理

4,DAO模块:Spring 提供对JDBC的支持,对JDBC进行封装,允许JDBC使用Spring资源,并能统一管理JDBC事物,并不对JDBC进行实现。(执行sql语句)

5,WEB模块:WEB模块提供对常见框架如Struts1,WEBWORK(Struts 2),JSF的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器。

6,Context模块:提供框架式的Bean访问方式,其他程序可以通过Context访问Spring的Bean资源,相当于资源注入。

7,MVC模块:为Spring提供了一套轻量级的MVC实现,在Spring的开发中,我们既可以用Struts也可以用Spring自己的MVC框架,相对于Struts,Spring自己的MVC框架更加简洁和方便

 

94.Spring 常用的注入方式有哪些?

Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入,setter注入,基于注解的注入

 

95.Spring 中的 bean 是线程安全的吗?

Spring作为一个容器,没有保证这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码。

 

96.spring 支持几种 bean 的作用域?

singleton:默认的scope,每个scope为singleton的bean都会被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。

prototype:bean被定义为在每次注入时都会创建一个新的对象。

request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。

session:bean被定义为在一个session的生命周期内创建一个单例对象。

global Session:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效

 

97.Spring 自动装配 bean 有哪些方式?

支持5种自动装配模式:

no – 缺省情况下,自动配置是通过“ref”属性手动设定

byName – 根据属性名称自动装配。如果一个bean的名称和其他bean属性的名称是一样的,将会自装配它。

byType – 按数据类型自动装配。如果一个bean的数据类型是用其它bean属性的数据类型,兼容并自动装配它。

constructor – 在构造函数参数的byType方式。

autodetect – 如果找到默认的构造函数,使用“自动装配用构造”; 否则,使用“按类型自动装配”。(在Spring3.0以后的版本被废弃,已经不再合法了)

 

98.Spring 事务实现方式有哪些?

(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。

(2)基于 TransactionProxyFactoryBean的声明式事务管理

(3)基于 @Transactional 的声明式事务管理

(4)基于Aspectj AOP配置事务

 

99.说一下Spring 的事务隔离?

ISOLATION_DEFAULT、ISOLATION_READ_UNCOMMITTED 、ISOLATION_READ_COMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE         

 

100.说一下 Spring MVC 运行流程?

1、 用户向服务器发送请求,请求被 Spring 前端控制 Servelt DispatcherServlet 捕获(捕获)

2、 DispatcherServlet对请求  URL进行解析,得到请求资源标识符(URI)。然后根据该  URI,调用 HandlerMapping获得该Handler配置的所有相关的对象(包括  Handler对象以及   Handler对象对应的拦截器),最后以 HandlerExecutionChain对象的形式返回;(查找   handler)

3、 DispatcherServlet  根据获得的 Handler,选择一个合适的  HandlerAdapter。  提取Request 中的模型数据,填充 Handler 入参,开始执行 Handler(Controller), Handler执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象(执行  handler)

4、DispatcherServlet  根据返回的 ModelAndView,选择一个适合的 ViewResolver(必须是已经注册到 Spring 容器中的 ViewResolver) (选择  ViewResolver)

5、通过 ViewResolver 结合 Model 和 View,来渲染视图,DispatcherServlet 将渲染结果返回给客户端。(渲染返回)

(简:核心控制器捕获请求、查找Handler、执行Handler、选择ViewResolver,通过ViewResolver渲染视图并返回)

 

101.Spring MVC有哪些组件?

前端控制器:捕获来自浏览器、前端的请求

处理器控制器:执行Handler处理器

处理器映射器:根据url查找Handler处理器

视图解析器:进行视图解析和渲染,根据逻辑视图名解析成真正的视图

Handler处理器:由适配器去执行Handler处理器

 

102.@RequestMapping 的作用是什么?

RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。

 

103.@Autowired和@Component 的作用是什么?

@Autowired表示被修饰的类需要注入对象,Spring会扫描所有被@Autowired标注的类,然后根据类型在IOC容器中找到匹配的类注入。

@Component把普通pojo实例化到spring容器中,相当于配置文件中的

 

十一、Spring Boot/Spring Cloud

104.什么是 SpringBoot?

  SpringBoot是一个框架,一种全新的编程规范,他的产生简化了框架的使用,所谓简化是指简化了Spring众多框架中所需的大量且繁琐的配置文件,所以 SpringBoot是一个服务于框架的框架,服务范围是简化配置文件。

 

105.为什么要用 SpringBoot?

SpringBoot让文件配置变的相当简单、让应用部署变的简单(SpringBoot内置服务器,并装备启动类代码),可以快速开启一个Web容器进行开发。

 

优点:(1)一个简单的SpringBoot工程是不需要在pom.xml手动添加什么配置的,如果与其他技术合用 比如postMan(文档在线自动生成、开发功能测试的一套工具)、Swagger(文档在线自动生成、开发功能测试的一套工具),则需要在pom.xml中添加依赖,由程序自动加载依赖jar包等配置文件。

  (2)我们之前在利用SSM或者SSH开发的时候,在resources中储存各种对应框架的配置文件,而现在我们只需要一个配置文件即可,配置内容也大体有 服务器端口号、数据库连接的地址、用户名、密码。这样,虽然简单 但在一定问题上而言,这也是极不安全的,将所有配置,放在一个文件里,是很危险的,但对于一般项目而言并不会有太大影响。

   (3)在SpringBoot创建时会自动创建Bootdemo1Application启动类,代表着本工程项目和服务器的启动加载,在springBoot中是内含服务器的,所以不需手动配置Tomact,但注意端口号冲突问题。

 

106.SpringBoot核心注解和配置文件?

@SpringBootConfiguration:这是Spring Boot 项目的相关配置注解,其实它也是一个组合注解。

@EnableAutoConfiguration:启用自动配置,该注解会使Spring Boot根据项目中依赖的jar包自动配置项目的配置项:如:我们添加了spring-boot-starter-web的依赖,项目中也就会引入SpringMVC的依赖,并且Spring Boot会自动配置tomcat 和SpringMVC。

@ComponentScan:默认扫描@SpringBootApplication 所在类的同级目录以及它的子目录

@ConfigurationProperties(prefix = "xxx", locations = {"classpath:/xxx.properties"})

全局的配置文件application.properties或者是application.yml,在resources目录下或者类路径下的/config下,一般我们放到resources下。在这个配置文件中你可以做一些服务器与Spring 的相关配置以及日志打印等;

 

107.SpringBoot 配置文件有哪几种类型?它们有什么区别?

Spring Boot配置文件支持.yml 也支持.properties,

 

108.SpringBoot 有哪些方式可以实现热部署?

①、spring-boot-devtools(SpringBoot 1.3后才拥有SpringBoot devtools热部署)   ②、Spring Loaded

 

109.JPA和 Hibernate有什么区别,Hibernate和Spring Data JPA有什么区别?

Hibernate是JPA规范的一个具体实现

Hibernate有JPA没有的特性

Hibernate的效率更快

JPA 有更好的移植性,通用性

区别:Hibernate是一个JPA实现,而Spring Data JPA是一个JPA数据访问抽象。Spring Data提供了GenericDao自定义实现的解决方案,它还可以通过方法名称约定代表您生成JPA查询。

通过Spring Data,您可以使用Hibernate、Eclipse Link或任何其他JPA提供程序。一个非常有趣的好处是您可以使用@Transactional注释以声明方式控制事务边界。

Spring Data JPA不是一个实现或JPA提供者,它只是一个抽象,用于显著减少为各种持久性存储实现数据访问层所需的代码量。Hibernate提供了Java Persistence API的参考实现,使其成为具有松散耦合优势的ORM工具的绝佳选择

 

110.什么是 Spring Cloud?

 Spring Cloud是一个微服务框架,相比Dubbo等RPC框架, Spring Cloud提供的全套的分布式系统解决方案。 Spring Cloud对微服务基础框架Netflix的多个开源组件进行了封装,同时又实现了和云端平台以及和Spring Boot开发框架的集成。Spring Cloud为微服务架构开发涉及的配置管理,服务治理,熔断机制,智能路由,微代理,控制总线,一次性token,全局一致性锁,leader选举,分布式session,集群状态管理等操作提供了一种简单的开发。Spring Cloud 为开发者提供了快速构建分布式系统的工具,开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接

 

111. Spring Cloud断路器的作用是什么?

 在Spring Cloud中使用Hystrix 来实现断路器的功能。Hystrix是Netflix开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备拥有回退机制和断路器功能的线程和信号隔离,请求缓存和请求打包,以及监控和配置等功能。

 

112. Spring Cloud 的核心组件有哪些?

服务发现——Netflix Eureka

客服端负载均衡——Netflix Ribbon

断路器——Netflix Hystrix

服务网关——Netflix Zuul

分布式配置——Spring Cloud Config

 

十二、Hibernate

113.为什么要使用 Hibernate?

1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。

2. Hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM实现。很大程度的简化DAO层的编码工作

3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。

4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。

 

114.什么是 ORM 框架?

对象关系映射,是一种为了解决面向对象与关系型数据库存在不匹配现象的技术,简单说,ORM通过描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中

ORM解决的主要问题是对象关系映射,域模型和关系模型分别建立在概念模型的基础上,域模型是面向对象的,而关系模型是面向关系的,一般情况下,一个持久类和一个表的对应,类的每个实例对应表中的一条记录,类的每个属性对应表的每个字段

优点:1、提高了开发效率,由于ORM可以自动对Entity对象与数据库表中进行字段和属性的映射,所以我们就不需要了数据访问层;2、ORM提供了对数据库的映射,不用写sql语句就可以操作数据库

 

115.Hibernate中如何在控制台查看打印的 sql 语句?

只需要在配置文件里修改: hibernate.show_sql=true

      org.hibernate.dialect.MySQLDialect

     true

 

116.Hibernate有几种查询方式?

1.HQL查询(适用情况:常用方法,比较传统,类似jdbc。缺点:新的查询语言,适用面有限,仅适用于Hibernate框架)

2.对象化查询Criteria方法(适用情况:面向对象操作,革新了以前的数据库操作方式,易读。缺点:适用面较HQL有限。)

3.动态查询DetachedCriterid(适用情况:面向对象操作,分离业务与底层,不需要字段属性摄入到Dao实现层。  缺点:适用面较HQL有限。)

4.例子查询(适用情况:面向对象操作。   缺点:适用面较HQL有限,不推荐。)

5.SQL查询(适用情况:不熟悉HQL的朋友,又不打算转数据库平台的朋友,万能方法   缺点:破坏跨平台,不易维护,不面向对象。)

6.命名查询(适用情况:万能方法,有点像ibatis轻量级框架的操作,方便维护。  缺点:不面向对象。基于hql和sql,有一定缺陷。)

 

117.Hibernate实体类可以被定义为 final 吗?

可以,但这种做法并不好。因为Hibernate会使用代理模式在延迟关联的情况下提高性能,如果你把实体类定义成final类之后,因为Java不允许对final类进行扩展 ,所以Hibernate就无法再使用代理了,如此一来就限制了使用可以提升性能的手段。不过,如果持久化类实现了一个接口而且在该接口中声明了所有定义于实体类中的所有public的方法的话,就能够避免出现前面所说的不利后果。

 

118.在 Hibernate中使用 Integer 和 int 做映射有什么区别?

a),如果你的数据库字段是允许为空的,使用包装类。如果不允许为空,使用包装的时候,如果你往数据库插入null值,此时就会抛出异常。然后你就可以对异常进行捕获并处理。

b),使用基本数据类型的时候,如果字段是NULL,那么JDBC会返回0,但是这里会有一个问题。有可能0在你的业务逻辑代表着特定含义,这时候就可能出现一些意想不到的后果。

总结:在项目中去使用包装类型而不是基本数据类型,这样可以使你编写代码更简单,除非有人给你一个更好的理由去使用基本数据类型或者你的上头要求一定要使用基本数据类型。

 

119.Hibernate是如何工作的?

Hibernate的工作流程:

1.通过Configuration config = new Configuration().configure();//读取并解析hibernate.cfg.xml配置文件

2.由hibernate.cfg.xml中的读取并解析映射信息

3.通过SessionFactory sf = config.buildSessionFactory();//创建SessionFactory

4.Session session = sf.openSession();//打开Sesssion

5.Transaction tx = session.beginTransaction();//创建并启动事务Transation

6.persistent operate操作数据,持久化操作

7.tx.commit();//提交事务

8.关闭Session

9.关闭SesstionFactory

 

120.get()和 load()的区别?

区别

1、 查询的时机不一样

            get方法任何时刻都是立即加载,只要调用get方法,就马上发起数据库查询。

            load方法默认情况下是延迟加载,真正用到对象的非OID字段数据才发起查询。

            load方法是可以通过配置的方式改为立即加载。

           配置的方式:由于load方法是hibernate的方法所以只有XML的方式:

        

 

2、返回的结果不一样

           get方法永远返回查询的实体类对象

            load方法返回的是代理对象

    立即加载:是不管用不用马上查询。

    延迟加载:是等到真正用的时候才发起查询。

 

121.说一下 Hibernate的缓存机制?

Hibernate缓存包括两大类:Hibernate一级缓存和 Hibernate二级缓存。

1.Hibernate一级缓存又称为“Session的缓存”。Session缓存内置不能被卸载,Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个数据库事务或者一个应用事务)。一级缓存中,持久化类的每个实例都具有唯一的 OID。

2.Hibernate二级缓存又称为“SessionFactory的缓存”。由于  SessionFactory 对象的生命周期和应用程序的整个过程对应,因此 Hibernate  二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。第二级缓存是可选的,是一个可配置的插件,默认下 SessionFactory不会启用这个插件。Hibernate提供了  org.hibernate.cache.CacheProvider接口,它充当缓存插件与 Hibernate之间的适配器。

总结:Hibernate中的缓存分一级缓存和二级缓存。一级缓存就是  Session 级别的缓存,在事务范围内有效是,内置的不能被卸载。二级缓存是 SesionFactory级别的缓存,从应用启动到应用结束有效。是可选的,默认没有二级缓存,需要手动开启。保存数据库后,缓存在内存中保存一份,如果更新了数据库就要同步更新。

 

什么样的数据适合存放到第二级缓存中?

1)很少被修改的数据   帖子的最后回复时间

2)经常被查询的数据   电商的地点

3)  不是很重要的数据,允许出现偶尔并发的数据

4)  不会被并发访问的数据

5)  常量数据

 

122.Hibernate对象有哪些状态?

hibernate里对象有三种状态:

1,Transient 瞬时态 :对象刚new出来,还没设id,设了其他值。

2,Persistent 持久态:调用了save()、saveOrUpdate(),就变成Persistent,有id

3,Detached  游离态: 当session  close()完之后,变成Detached。

 

123.在 Hibernate中 getCurrentSession 和 openSession 的区别是什么?

1. openSession 是打开一个新的session对象,而且每次使用都是打开一个新的session,假如连续使用多次,则获得的session不是同一个对象,并且使用完需要调用close方法关闭session。

2. getCurrentSession 是获取当前上下文一个session对象,当第一次使用此方法时,会自动产生一个session对象,并且连续使用多次时,得到的session都是同一个对象,这就是与openSession的区别之一,简单而言,getCurrentSession 就是:如果有已经使用的,用旧的,如果没有,建新的。在实际开发中,往往使用getCurrentSession多,因为一般是处理同一个事务(即是使用一个数据库的情况),所以在一般情况下比较少使用openSession或者说openSession是比较老旧的一套接口了;

 

124.Hibernate实体类必须要有无参构造函数吗?为什么?

是的,因为hibernate框架会调用这个默认构造方法来构造实例对象。即Class类的newInstance方法 这个方法就是通过调用默认构造方法来创建实例对象的 ,如果你没有提供任何构造方法,虚拟机会自动提供默认构造方法(无参构造器),但是如果你提供了其他有参数的构造方法的话,虚拟机就不再为你提供默认构造方法,这时必须手动把无参构造器写在代码里,否则new Xxxx()是会报错的,所以默认的构造方法不是必须的,只在有多个构造方法时才是必须的,这里“必须”指的是“必须手动写出来”。

 

 

十三、Mybatis

125.mybatis 中 #{}和 ${}的区别是什么?

1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id".

2. $将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id,  如果传入的值是id,则解析成的sql为order by id.

3. #方式能够很大程度防止sql注入。  

4.$方式无法防止Sql注入。

5.$方式一般用于传入数据库对象,例如传入表名.

6.一般能用#的就别用$.            MyBatis排序时使用order by 动态参数时需要注意,用$而不是#

 

126.mybatis 有几种分页方式?

1.数组分页

原理:进行数据库查询操作时,获取到数据库中所有满足条件的记录,保存在应用的临时数组中,再通过List的subList方法,获取到满足条件的所有记录。

实现:首先在dao层,创建StudentMapper接口,用于对数据库的操作。在接口中定义通过数组分页的查询方法

2.借助Sql语句进行分页

通过数组分页,发现不能每次都对数据库中的所有数据都检索。然后在程序中对获取到的大量数据进行二次操作,这样对空间和性能都是极大的损耗。所以我们希望能直接在数据库语言中只检索符合条件的记录,不需要在通过程序对其作处理。这时,Sql语句分页技术横空出世。

实现:通过sql语句实现分页也是非常简单的,只是需要改变我们查询的语句就能实现了,即在sql语句后面添加limit分页语句。首先还是在StudentMapper接口中添加sql语句查询的方法

3.基于RowBounds进行分页

原理:拦截器

RowBounds:在mapper.java中的方法中传入RowBounds对象。

RowBounds rowBounds = new RowBounds(offset, page.getPageSize()); // offset起始行 // limit是当前页显示多少条数据

public List findRecords(HashMap map,RowBounds rowBounds);

mappep.xml里面正常配置,不用对rowBounds任何操作。mybatis的拦截器自动操作rowBounds进行分页。

4.基于PageHelper进行分页

原理:拦截器

PageHelper:在调用查询方法之前调用。PageHelper只对紧跟着的第一个SQL语句起作用.

Page page = PageHelper.startPage(pageNum,pageSize);

 List pagelist = queryForList( xxx.class, "queryAll" , param);

 

127.RowBounds 是一次性查询全部结果吗?为什么?

RowBounds在处理分页时,只是简单的把offset之前的数据都skip掉,超过limit之后的数据不取出,MyBatis中的DefaultResultSetHandler类。跳过offset之前的数据是由方法skipRows处理,判断数据是否超过了limit则是由shouldProcessMoreRows方法进行判断。简单点说道,就是先把数据全部查询到ResultSet,然后从ResultSet中取出offset和limit之间的数据,这就实现了分页查询。

 

128.mybatis 逻辑分页和物理分页的区别是什么?

    逻辑分页 内存开销比较大,在数据量比较小的情况下效率比物理分页高;在数据量很大的情况下,内存开销过大,容易内存溢出,不建议使用

    物理分页 内存开销比较小,在数据量比较小的情况下效率比逻辑分页还是低,在数据量很大的情况下,建议使用物理分页

 

129.mybatis 是否支持延迟加载?延迟加载的原理是什么?

Mybatis的延迟加载功能默认是关闭的。需要在SqlMapConfig.xml文件中通过setting标签配置来开启延迟加载功能

开启延迟加载的属性:

lazyLoadingEnabled:全局性设置懒加载。如果设为‘false',则所有相关联的都会被初始化加载。默认为false

aggressiveLazyLoading:当设置为‘true'的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。默认为true

 

130.说一下 mybatis 的一级缓存和二级缓存?

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。

一级缓存的工作原理:用户发起查询请求,查找某条数据,sqlSession先去缓存中查找,是否有该数据,如果有,读取;如果没有,从数据库中查询,并将查询到的数据放入一级缓存区域,供下次查找使用。但sqlSession执行commit,即增删改操作时会清空缓存。这么做的目的是避免脏读。如果commit不清空缓存,会有以下场景:A查询了某商品库存为10件,并将10件库存的数据存入缓存中,之后被客户买走了10件,数据被delete了,但是下次查询这件商品时,并不从数据库中查询,而是从缓存中查询,就会出现错误。

二级缓存原理:

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

UserMapper有一个二级缓存区域(按namespace分),其它mapper也有自己的二级缓存区域(按namespace分)。每一个namespace的mapper都有一个二级缓存区域,两个mapper的namespace如果相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。

 

既然有了一级缓存,那么为什么要提供二级缓存呢?

实际开发中,MyBatis通常和Spring进行整合开发。Spring将事务放到Service中管理,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 每次查询之后都要进行关闭sqlSession,关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。

 

131.mybatis 和 hibernate 的区别有哪些?

1 开发方面

        在项目开发过程当中,就速度而言:

            hibernate开发中,sql语句已经被封装,直接可以使用,加快系统开发;

            Mybatis 属于半自动化,sql需要手工完成,稍微繁琐;

        但是,凡事都不是绝对的,如果对于庞大复杂的系统项目来说,发杂语句较多,选择hibernate 就不是一个好方案。

2 sql优化方面

        Hibernate 自动生成sql,有些语句较为繁琐,会多消耗一些性能;

        Mybatis 手动编写sql,可以避免不需要的查询,提高系统性能;

3 对象管理比对

        Hibernate 是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可;

        Mybatis 需要自行管理 映射关系;

      4 缓存方面    

 相同点:

Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓    存方案,创建适配器来完全覆盖缓存行为。

不同点:

Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存。

MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。

比较:

 Hibernate 具有良好的管理机制,用户不需要关注SQL,如果二级缓存出现脏数据,系统会保存,;

 Mybatis 在使用的时候要谨慎,避免缓存CAche 的使用。

Hibernate优势

Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。

Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。

Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。

Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。

Mybatis优势

MyBatis可以进行更为细致的SQL优化,可以减少查询字段。

MyBatis容易掌握,而Hibernate门槛较高。

(Mybatis:小巧、方便、高效、简单、直接、半自动化,Hibernate:强大、方便、高效、复杂、间接、全自动)

 

132.mybatis 有哪些执行器(Executor)?

Mybatis有三种基本的Executor执行器:SimpleExecutor、ReuseExecutor、BatchExecutor。

SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。

BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

 

133.mybatis 分页插件的实现原理是什么?

分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。

 

134.mybatis 如何编写一个自定义插件?

Mybatis自定义插件针对Mybatis四大对象(Executor、StatementHandler 、ParameterHandler 、ResultSetHandler )进行拦截,具体拦截方式为:

1、Executor:拦截执行器的方法(log记录)

2、StatementHandler :拦截Sql语法构建的处理

3、ParameterHandler :拦截参数的处理

4、ResultSetHandler :拦截结果集的处理  

 

十四、RabbitMQ

135.RabbitMQ 的使用场景有哪些?

1、单发送单接收;2、单发送多接收;3、Publish/Subscribe;4、Routing (按路线发送接收)

 

136.RabbitMQ 有哪些重要的角色?

none:不能访问 management plugin

management:用户可以通过AMQP做的任何事外加:

列出自己可以通过AMQP登入的virtual hosts  

查看自己的virtual hosts中的queues, exchanges 和 bindings

查看和关闭自己的channels 和 connections

查看有关自己的virtual hosts的“全局”的统计信息,包含其他用户在这些virtual hosts中的活动。

policymaker :management可以做的任何事外加:

查看、创建和删除自己的virtual hosts所属的policies和parameters

monitoring :management可以做的任何事外加:

列出所有virtual hosts,包括他们不能登录的virtual hosts

查看其他用户的connections和channels

查看节点级别的数据如clustering和memory使用情况

查看真正的关于所有virtual hosts的全局的统计信息

administrator :policymaker和monitoring可以做的任何事外加:

创建和删除virtual hosts

查看、创建和删除users

查看创建和删除permissions

关闭其他用户的connections

 

137.RabbitMQ 有哪些重要的组件?

 

 

 

138.RabbitMQ 中 vhost 的作用是什么?

vhost本质上是一个mini版的RabbitMQ服务器,拥有自己的队列、绑定、交换器和权限控制;vhost通过在各个实例间提供逻辑上分离,允许你为不同应用程序安全保密地运行数据;vhost是AMQP概念的基础,必须在连接时进行指定,RabbitMQ包含了默认vhost:“/”;当在RabbitMQ中创建一个用户时,用户通常会被指派给至少一个vhost,并且只能访问被指派vhost内的队列、交换器和绑定,vhost之间是绝对隔离的。

 

139.RabbitMQ 的消息是怎么发送的?

客户端发送给服务器Tx.Select(开启事务模式)

服务器端返回Tx.Select-Ok(开启事务模式ok)

推送消息

客户端发送给事务提交Tx.Commit

服务器端返回Tx.Commit-Ok

 

140.RabbitMQ 怎么保证消息的可靠性?

publisher confirms(发布方确认):

确保producer到broker节点消息的可靠性。如可能发生消息到了broker但是还没有投递到queue,broker突然宕机这种情况;

message持久化:

将message存储到硬盘,如果是未持久化的消息会存在内存中,broker宕机后重启内存中的消息会丢失

acknowledgement(consumer确认):

该机制能够保证,只有consumer成功消费了消息,才将其从queue中移除。

 

141.RabbitMQ 怎么避免消息丢失?

1.消息持久化

2.ACK确认机制

3.设置集群镜像模式

4.消息补偿机制

 

142.RabbitMQ 要保证消息持久化成功的条件有哪些?

1、RabbitMQ在服务端没有声明队列queue持久化(durable=True)时,队列是存在内存中的,服务端down机了,队列也随之消失。

2、RabbitMQ在服务端只声明queue持久化,但是在发送消息时,没有声明持久化(properties=pika.BasicProperties(delivery_mode=2,)),服务器down机,只保留队列,但是不保留消息。

3、RabbitMQ在服务端声明queue持久化,并且在发消息时也声明了持久化,服务down机重启后,队列和消息都能得到保存。

4、服务端声明持久化,客户端想接受消息的话,必须也要声明queue时,也要声明持久化,不然的话,客户端执行会报错。

 

143.RabbitMQ 持久化有什么缺点?

MQ持久化是将消息记录到磁盘,对性能有影响,但是一般情况下为了消息可靠允许性能损失。还有一种情况是事务,但是事务对性能影响极大,大概降低2-10倍吞吐量,而且会使生产者应用程序产生同步。

 

144.RabbitMQ 有几种广播类型?

三种广播模式:fanout广播模式、direct广播模式、topic广播模式

 

145.RabbitMQ 怎么实现延迟消息队列?

延迟任务通过消息的TTL和Dead Letter Exchange来实现。我们需要建立2个队列,一个用于发送消息,一个用于消息过期后的转发目标队列。生产者输出消息到Queue1,并且这个消息是设置有有效时间的,比如3分钟。消息会在Queue1中等待3分钟,如果没有消费者收掉的话,它就是被转发到Queue2,Queue2有消费者,收到,处理延迟任务。完成延迟任务的实现。

 

146.RabbitMQ 集群有什么用?

允许消费者和生产者在Rabbit节点崩溃的情况下继续运行;

通过增加节点来扩展Rabbit处理更多的消息,承载更多的业务量;

 

147.RabbitMQ 集群搭建需要注意哪些问题?

1. erlang的版本需要一致,新增加的节点可以选择更高版本的erlang,运行时向下兼容;erlang.cookie文件需要在集群中拷贝一致windows操作系统中该文件位于 C:\Users\[当前操作系统登录用户]\.erlang.cookie 和 C:\Windows 目录下,切记两处都要同步。

2.windows的服务列表中存在RabbitMQ的服务,保持其持续的运行状态即可,RabbitMQ的操作通过命令行完成,RabbleMQ的windows服务可以通过rabbitmq-service.bat remove\install 进行删除 安装。

3.注意集群间端口的开放,默认4369端口

4.集群中搭建完毕后发现集群存在节点状态为rabbitmq Node statistics not available异常 说明缺少插件启动,可以通过命令 rabbitmq-plugins enable rabbitmq_management将插件全部启动

5.配置RabbitMq的环境变量事可以通过设置系统环境变量完成,此步骤需要在安装RabbitMq客户端前进行配置,比如:安装rabbitmq前,设置系统环境变量RABBITMQ_BASE为D:\Data\RabbitMQ 则rabbitmq的数据和日志文件都会存到这里,也可以通过设置RABBITMQ_NODENAME设置RabbitMQ的节点名称等,参考环境变量设置

6.加入集群前需要配置本地Host DNS文件 C:\Windows\System32\drivers\etc,配置ip地址的值为集群中其他节点的主机名(启动RabbitMq时配置的RabbitMq的节点名称,默认为主机名)

 

148.RabbitMQ 每个节点是其他节点的完整拷贝吗?为什么?

默认情况下RabbitMQ不将所有队列内容和状态复制到所有节点,有两个原因:

存储空间——如果每个节点都拥有所有队列的完全拷贝,这样新增节点不但没有新增存储空间,反而增加了更多的冗余数据。

性能——如果消息的发布需安全拷贝到每一个集群节点,那么新增节点对网络和磁盘负载都会有增加,这样违背了建立集群的初衷,新增节点并没有提升处理消息的能力,最多是保持和单节点相同的性能甚至是更糟。

所以其他非所有者节点只知道队列的元数据,和指向该队列节点的指针。

 

149.RabbitMQ集群节点的类型有哪些?

节点的存储类型分为两种:磁盘节点、内存节点;

磁盘节点就是配置信息和元信息存储在磁盘上,内次节点把这些信息存储在内存中,当然内次节点的性能是大大超越磁盘节点的。

单节点系统必须是磁盘节点,否则每次你重启RabbitMQ之后所有的系统配置信息都会丢失。

RabbitMQ要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点。

 

150.RabbitMQ 集群中唯一一个磁盘节点崩溃了会发生什么情况?

如果唯一磁盘的磁盘节点崩溃了,不能进行如下操作:不能创建队列、不能创建交换器、不能创建绑定、不能添加用户、不能更改权限、不能添加和删除集群几点;

总结:如果唯一磁盘的磁盘节点崩溃,集群是可以保持运行的,但你不能更改任何东西。

解决方案:在集群中设置两个磁盘节点,只要一个可以,你就能正常操作。

 

151.RabbitMQ集群节点的类型有哪些?RabbitMQ对集群节点启停顺序有要求吗?

节点的存储类型分为两种:磁盘节点、内存节点;

磁盘节点就是配置信息和元信息存储在磁盘上,内次节点把这些信息存储在内存中,当然内次节点的性能是大大超越磁盘节点的。

单节点系统必须是磁盘节点,否则每次你重启RabbitMQ之后所有的系统配置信息都会丢失。

RabbitMQ要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点。

集群重启的顺序是固定的,并且是相反的。如下所述:

启动顺序:磁盘节点 --> 内存节点

关闭顺序:内存节点 --> 磁盘节点

最后关闭必须是磁盘节点,不然可能回造成集群启动失败、数据丢失等异常情况。

 

十五、Kafka

152.kafka 可以脱离 zookeeper 单独使用吗?为什么?

kafka使用ZooKeeper用于管理、协调代理。每个Kafka代理通过Zookeeper协调其他Kafka代理,当Kafka系统中新增了代理或某个代理失效时,Zookeeper服务将通知生产者和消费者。生产者与消费者据此开始与其他代理协调工作。Zookeeper在Kakfa中扮演的角色:Kafka将元数据信息保存在Zookeeper中,但是发送给Topic本身的数据是不会发到Zk上的。

 

153.kafka 有几种数据保留的策略?

1)N天前的删除。2)保留最近的多少Size数据。

 

154.kafka 同时设置了 7 天和 10G 清除数据,到第五天的时候消息达到了 10G,这个时候 kafka 将如何处理?

Kafka Broker默认的消息保留策略是:要么保留一定时间,要么保留到消息达到一定大小的字节数。当消息达到设置的条件上限时,旧消息就会过期并被删除,所以,在任何时刻,可用消息的总量都不会超过配置参数所指定的大小。topic可以配置自己的保留策略,可以将消息保留到不再使用他们为止。因为在一个大文件里查找和删除消息是很费时的事,也容易出错,所以,分区被划分为若干个片段。默认情况下,每个片段包含1G或者一周的数据,以较小的那个为准。在broker往leader分区写入消息时,如果达到片段上限,就关闭当前文件,并打开一个新文件。当前正在写入数据的片段叫活跃片段。当所有片段都被写满时,会清除下一个分区片段的数据,如果配置的是7个片段,每天打开一个新片段,就会删除一个最老的片段,循环使用所有片段。

 

155.什么情况会导致 kafka 运行变慢?

kafka发送producer为单实例并且使用同步发送,发送kafka使用线程池执行发送任务,会使运行变慢。

策略:将producer改为多实例,使用数组将多个实例缓存起来,每次使用producer是在数组轮询取出一个进行使用,修改之后连续长稳环境跑了24小时,打开日志发现发送kafka时延还是有少量100ms以上的长时延,但基本都在100ms-500ms之间,不算很长,并且当不会连续出现了,nginx也没有出现504这种情况了,说明多实例修改是有效果的。

 

156.使用 kafka 集群需要注意什么?

配置好zookeeper之后,关闭防火墙

# systemctl stop firewalld.service        //关闭firewall

#systemctl disable firewalls.service          //开机禁用

#firewall-cmd  --state                   //查看防火墙的状态(关闭是not    running)

注:

2888端口号是zookeeper服务之间通信的端口。

3888端口是zookeeper与其他应用程序通信的端口。

2181端口是zookeeper客户端请求接口。

防火墙必须放开这三个端口,zk才能正常启动

启动zk服务

在bin目录下,三台依次启动

# sh zkServer.sh start

ZooKeeper JMX enabled by default

Using config: /home/260206/software/zookeeper/zookeeper-3.4.13/bin/../conf/zoo.cfg

Starting zookeeper ... STARTED

 

查看状态

# sh zkServer.sh status

ZooKeeper JMX enabled by default

Using config: /home/260206/software/zookeeper/zookeeper-3.4.13/bin/../conf/zoo.cfg

Error contacting service. It is probably not running.

 

Zookeeper 无法正常启动原因:防火墙未关闭,但按照上文查看防火墙状态,显示的是not running,已关闭

Zookeeper 的端口被占用,打开终端,输入# netstat -apn | grep 2181,如无输出,则未被占用,若有输出,则可以看到占用端口的进程号,kill -9 +进程号,即可。

zookeeper_server.pid中的内容没有被清空。在Zookeeper 的conf目录下,找到zoo.cfg文件,查看此文件中你配置的dataDir的路径,将里面zookeeper_server.pid清空即可。

查看Zookeeper 文件夹的conf目录下是否有zoo.cfg和zoo_sample.cfg,(一般zookeeper下载下来的压缩包解压后只有zoo_sample.cfg,一般都重命名,mv命令就可以),但有些可能是将zoo_sample.cfg文件复制了一份,复件名为zoo.cfg,所以两个文件会同时存在,而两个文件是不能同时存在的,且默认读取的是zoo.cfg,所以要删掉zoo_sample.cfg。

data下的myid必须是和zoo.cfg文件下的服务名是一一对应的。

 

十六、Zookeeper

157.Zookeeper 是什么?底层实现?

ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,它是集群的管理者,监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终,将简单易用的接口和性能高效、功能稳定的系统提供给用户:zookeeper=文件系统+监听通知机制

ZooKeeper是Hadoop Ecosystem中非常重要的组件,它的主要功能是为分布式系统提供一致性协调(Coordination)服务,与之对应的Google的类似服务叫Chubby。Zookeeper 很像数据结构当中的树,也很像文件系统的目录。树是由节点所组成,Zookeeper的数据存储也同样是基于节点,这种节点叫做Znode。不同于树的节点,Znode的引用方式是路径引用,类似于文件路径,这样的层级结构,让每一个Znode节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离。

 

158.Zookeeper 都有哪些功能?

    ZooKeeper 是一个开源的分布式协调服务,由雅虎创建,是 Google Chubby 的开源实现。分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、配置维护,名字服务、分布式同步、分布式锁和分布式队列等功能。

 

159.Zookeeper 有几种部署模式?

Zookeeper的部署分为三种模式:单机模式、集群模式和伪集群模式。

 

160.Zookeeper 怎么保证主从节点的状态同步?

zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 zab 协议。  zab 协议有两种模式,分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,zab 就进入了恢复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保证了 leader 和 server 具有相同的系统状态。

 

161.Zookeeper 集群中为什么要有主节点?

主节点负责分发任务,从节点负责处理任务。

 

162.Zookeeper 集群中有 3 台服务器,其中一个节点宕机,这个时候 Zookeeper 还可以使用吗?

可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。

 

163.说一下 Zookeeper 的通知机制?

 客户端在向 ZooKeeper 服务器注册 Watcher 的同时,会将 Watcher 对象存储在客户端的 WatchManager 中。当ZooKeeper 服务器触发 Watcher 事件后,会向客户端发送通知,客户端线程从 WatchManager 的实现类中取出对应的 Watcher 对象来执行回调逻辑。

 

十七、Dubbo

164.Dubbo 是什么?

Dubbo 是一款高性能、轻量级的开源 RPC 框架,提供服务自动注册、自动发现等高效服务治理方案, 可以和 Spring 框架无缝集成。

 

165.Dubbo 的使用场景有哪些?

透明化的远程方法调用:就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。

软负载均衡及容错机制:可在内网替代 F5 等硬件负载均衡器,降低成本,减少单点。

服务自动注册与发现:不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。

 

166.Dubbo 核心功能有哪些?

Remoting:网络通信框架,提供对多种NIO框架抽象封装,包括“同步转异步”和“请求-响应”模式的信息交换方式。

Cluster:服务框架,提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。

Registry:服务注册,基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

 

167.Dubbo 核心组件有哪些?

Provider:暴露服务的服务提供方

Consumer:调用远程服务消费方

Registry:服务注册与发现注册中心

Monitor:监控中心和访问调用统计

Container:服务运行容器

 

168.Dubbo 服务器注册与发现的流程?

Provider(提供者)绑定指定端口并启动服务。

提供者连接注册中心,并发本机 IP、端口、应用信息和提供服务信息发送至注册中心存储。

Consumer(消费者),连接注册中心 ,并发送应用信息、所求服务信息至注册中心。

注册中心根据消费者所求服务信息匹配对应的提供者列表发送至 Consumer 应用缓存。

Consumer 在发起远程调用时基于缓存的消费者列表择其一发起调用。

Provider 状态变更会实时通知注册中心、在由注册中心实时推送至 Consumer。

 

169.Dubbo 支持哪些协议,它们的优缺点有哪些?

Dubbo: 单一长连接和 NIO 异步通讯,适合大并发小数据量的服务调用,以及消费者远大于提供者。传输协议 TCP,异步 Hessian 序列化。

RMI: 采用 JDK 标准的 RMI 协议实现,传输参数和返回参数对象需要实现 Serializable 接口,使用 Java 标准序列化机制,使用阻塞式短连接,传输数据包大小混合,消费者和提供者个数差不多,可传文件,传输协议 TCP。 多个短连接 TCP 协议传输,同步传输,适用常规的远程服务调用和 RMI 互操作。在依赖低版本的 Common-Collections 包,Java 序列化存在安全漏洞。

WebService:基于 WebService 的远程调用协议,集成 CXF 实现,提供和原生 WebService 的互操作。多个短连接,基于 HTTP 传输,同步传输,适用系统集成和跨语言调用。

HTTP: 基于 Http 表单提交的远程调用协议,使用 Spring 的 HttpInvoke 实现。多个短连接,传输协议 HTTP,传入参数大小混合,提供者个数多于消费者,需要给应用程序和浏览器 JS 调用。

Hessian:集成 Hessian 服务,基于 HTTP 通讯,采用 Servlet 暴露服务,Dubbo 内嵌 Jetty 作为服务器时默认实现,提供与 Hession 服务互操作。多个短连接,同步 HTTP 传输,Hessian 序列化,传入参数较大,提供者大于消费者,提供者压力较大,可传文件。

Memcache:基于 Memcache实现的 RPC 协议。

Redis:基于 Redis 实现的RPC协议。

 

170.Dubbo 推荐什么协议?

推荐使用 Dubbo 协议。

 

171.Dubbo 有哪些注册中心?

Multicast 注册中心:Multicast 注册中心不需要任何中心节点,只要广播地址,就能进行服务注册和发现,基于网络中组播传输实现。

Zookeeper 注册中心:基于分布式协调系统 Zookeeper 实现,采用 Zookeeper 的 watch 机制实现数据变更。

Redis 注册中心:基于 Redis 实现,采用 key/map 存储,key 存储服务名和类型,map 中 key 存储服务 url,value 服务过期时间。基于 Redis 的发布/订阅模式通知数据变更。

Simple 注册中心。

 

172.Dubbo 的注册中心集群挂掉,发布者和订阅者之间还能通信么?

可以通讯。启动 Dubbo 时,消费者会从 Zookeeper 拉取注册的生产者的地址接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用。

 

173.Dubbo 使用的是什么通信框架?

默认使用 Netty 作为通讯框架。

 

174.Dubbo集群提供了哪些负载均衡策略?

Random LoadBalance: 随机选取提供者策略,有利于动态调整提供者权重。截面碰撞率高,调用次数越多,分布越均匀。

RoundRobin LoadBalance: 轮循选取提供者策略,平均分布,但是存在请求累积的问题。

LeastActive LoadBalance: 最少活跃调用策略,解决慢提供者接收更少的请求。

ConstantHash LoadBalance: 一致性 Hash 策略,使相同参数请求总是发到同一提供者,一台机器宕机,可以基于虚拟节点,分摊至其他提供者,避免引起提供者的剧烈变动。

默认为 Random 随机调用。

 

175.Dubbo的集群容错方案有哪些?

Failover Cluster:失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。

Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

Forking Cluster:并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=”2″ 来设置最大并行数。

Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错 。通常用于通知所有提供者更新缓存或日志等本地资源信息。

默认的容错方案是 Failover Cluster。

 

176.Dubbo 支持哪些序列化方式?

默认使用 Hessian 序列化,还有 Duddo、FastJson、Java 自带序列化。

 

177.Dubbo 超时设置有哪些方式?

Dubbo 超时设置有两种方式:

服务提供者端设置超时时间,在Dubbo的用户文档中,推荐如果能在服务端多配置就尽量多配置,因为服务提供者比消费者更清楚自己提供的服务特性。

服务消费者端设置超时时间,如果在消费者端设置了超时时间,以消费者端为主,即优先级更高。因为服务调用方设置超时时间控制性更灵活。如果消费方超时,服务端线程不会定制,会产生警告。

 

178.服务调用超时会怎么样?

dubbo 在调用服务不成功时,默认是会重试两次。

 

179.Dubbo 在安全方面有哪些措施?

Dubbo 通过 Token 令牌防止用户绕过注册中心直连,然后在注册中心上管理授权。

Dubbo 还提供服务黑白名单,来控制服务所允许的调用方。

 

180.Dubbo 类似的分布式框架还有哪些?

比较著名的就是 Spring Cloud。

 

181.Dubbo 和 Spring Cloud 有什么关系?

Dubbo 是 SOA 时代的产物,它的关注点主要在于服务的调用,流量分发、流量监控和熔断。而 Spring Cloud 诞生于微服务架构时代,考虑的是微服务治理的方方面面,另外由于依托了 Spring、Spring Boot 的优势之上,两个框架在开始目标就不一致,Dubbo 定位服务治理、Spring Cloud 是打造一个生态。

 

182.Dubbo 和 Spring Cloud 有什么哪些区别?

Dubbo 底层是使用 Netty 这样的 NIO 框架,是基于 TCP 协议传输的,配合以 Hession 序列化完成 RPC 通信。

Spring Cloud 是基于 Http 协议 Rest 接口调用远程过程的通信,相对来说 Http 请求会有更大的报文,占的带宽也会更多。但是 REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更为合适,至于注重通信速度还是方便灵活性,具体情况具体考虑。

 

十八、MySql

183.数据库的三范式是什么?

范式:简言之就是,数据库设计对数据的存储性能,还有开发人员对数据的操作都有莫大的关系。所以建立科学的,规范的的数据库是需要满足一些规范的来优化数据数据存储方式。在关系型数据库中这些规范就可以称为范式。

第一范式:当关系模式R的所有属性都不能在分解为更基本的数据单位时,称R是满足第一范式的,简记为1NF。满足第一范式是关系模式规范化的最低要求,否则,将有很多基本操作在这样的关系模式中实现不了。

每一列属性都是不可再分的属性值,确保每一列的原子性

两列的属性相近或相似或一样,尽量合并属性一样的列,确保不产生冗余数据。

第二范式:如果关系模式R满足第一范式,并且R得所有非主属性都完全依赖于R的每一个候选关键属性,称R满足第二范式,简记为2NF。

每一行的数据只能与其中一列相关,即一行数据只做一件事。只要数据列中出现数据重复,就要把表拆分开来。

第三范式:设R是一个满足第一范式条件的关系模式,X是R的任意属性集,如果X非传递依赖于R的任意一个候选关键字,称R满足第三范式,简记为3NF.

数据不能存在传递关系,即没个属性都跟主键有直接关系而不是间接关系。像:a-->b-->c 属性之间含有这样的关系,是不符合第三范式的

三大范式只是一般设计数据库的基本理念,可以建立冗余较小、结构合理的数据库。如果有特殊情况,当然要特殊对待,数据库设计最重要的是看需求跟性能,需求>性能>表结构。所以不能一味的去追求范式建立数据库。

 

184.一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了一条数据,此时 id 是几?

一般情况下,我们创建的表的类型是InnoDB,如果新增一条记录(不重启mysql的情况下),这条记录的id是8;但是如果重启(上文中提到的)MySQL的话,这条记录的ID是5。因为InnoDB表只把自增主键的最大ID记录到内存中,所以重启数据库或者对表OPTIMIZE操作,都会使最大ID丢失。 但是,如果我们使用表的类型是MylSAM,那么这条记录的ID就是8。因为MylSAM表会把自增主键的最大ID记录到数据文件里面,重启MYSQL后,自增主键的最大ID也不会丢失。

 

185.如何获取当前数据库版本?

select version()

 

186.说一下 ACID 是什么?

数据库事务 :一个事务一般是指多个操作的集合,比如插入数据库分两段插入,第二次插入错误,第一次插入操作也需要回退

1.Atomicity 原子性

原子性,指的是整个事务是一个独立的单元,是不可分割的工作单位,要么操作成功,要么操作不成功,事务必须要保持和系统处于一致的状态,只有使据库中所有的操作执行成功,才算整个事务成功;事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。

2.Consistency 一致性

指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNTS表中Tom和Jack的存款总额为2000元。

3.Isolation 隔离性

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。

4.Durability 持久性

指的是只要事务成功结束,它对数据库所做的更新就必须永久保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

 

187.char 和 varchar 的区别是什么?

char:定长,效率高,一般用于固定长度的表单提交数据存储 ;例如:身份证号,手机号,电话,密码等

varchar:不定长,效率偏低

存数据时的区别

char定义的是固定长度,长度范围为0-255,存储时,如果字符数没有达到定义的位数,会在后面用空格补全存入数据库中,在上例中,name实际存储在数据中的数据为'zejin ';

varchar是变长长度,长度范围为0-65535,存储时,如果字符没有达到定义的位数,也不会在后面补空格,在上例subject字段中,实际存储在数据中的数据为'zejin',当然还有一或两个字节来描述该字节长度

取数据时的区别

数据库取char的数据时,会把后面的空格全部丢弃掉,譬如上例中的description字段取出来时只剩zejin

而数据库在取varchar数据时,尾部空格会保留,譬如subject字段

 

188.float 和 double 的区别是什么?

double精度高,有效数字16位,float精度7位。但double消耗内存是float的两倍,double的运算速度比float慢得多;

float数值类型用于表示单精度浮点数值,而double数值类型用于表示双精度浮点数值,float和double都是浮点型,而decimal是定点型;

MySQL 浮点型和定点型可以用类型名称后加(M,D)来表示,M表示该值的总共长度,D表示小数点后面的长度,M和D又称为精度和标度,如float(7,4)的 可显示为-999.9999,MySQL保存值时进行四舍五入,如果插入999.00009,则结果为999.0001。

FLOAT和DOUBLE在不指 定精度时,默认会按照实际的精度来显示,而DECIMAL在不指定精度时,默认整数为10,小数为0。

 

189.mysql 的内连接、左连接、右连接有什么区别?

1.内连接,显示两个表中有联系的所有数据;

2.左链接,以左表为参照,显示所有数据;

3.右链接,以右表为参照显示数据;

 

190.mysql 索引是怎么实现的?

索引是一个排序的列表,在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址,在数据十分庞大的时候,索引可以大大加快查询的速度,这是因为使用索引后可以不用扫描全表来定位某行的数据,而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据

索引的优缺点

优势:可以快速检索,减少I/O次数,加快检索速度;根据索引分组和排序,可以加快分组和排序;

劣势:索引本身也是表,因此会占用存储空间,一般来说,索引表占用的空间的数据表的1.5倍;索引表的维护和创建需要时间成本,这个成本随着数据量增大而增大;构建索引会降低数据表的修改操作(删除,添加,修改)的效率,因为在修改数据表的同时还需要修改索引表;

索引的分类

常见的索引类型有:主键索引、唯一索引、普通索引、全文索引、组合索引

1、主键索引:即主索引,根据主键pk_clolum(length)建立索引,不允许重复,不允许空值;

2、唯一索引:用来建立索引的列的值必须是唯一的,允许空值

3、普通索引:用表中的普通列构建的索引,没有任何限制

4、全文索引:用大文本对象的列构建的索引(下一部分会讲解)

5、组合索引:用多个列组合构建的索引,这多个列中的值不允许有空值

 

191.索引的使用策略?什么时候要使用索引?

主键自动建立唯一索引;

经常作为查询条件在WHERE或者ORDER BY 语句中出现的列要建立索引;

作为排序的列要建立索引;

查询中与其他表关联的字段,外键关系建立索引

高并发条件下倾向组合索引;

用于聚合函数的列可以建立索引,例如使用了max(column_1)或者count(column_1)时的column_1就需要建立索引

什么时候不要使用索引?

经常增删改的列不要建立索引;

有大量重复的列不建立索引;

表记录太少不要建立索引。只有当数据库里已经有了足够多的测试数据时,它的性能测试结果才有实际参考价值。如果在测试数据库里只有几百条数据记录,它们往往在执行完第一条查询命令之后就被全部加载到内存里,这将使后续的查询命令都执行得非常快--不管有没有使用索引。只有当数据库里的记录超过了1000条、数据总量也超过了MySQL服务器上的内存总量时,数据库的性能测试结果才有意义。

索引失效的情况:

在组合索引中不能有列的值为NULL,如果有,那么这一列对组合索引就是无效的。

在一个SELECT语句中,索引只能使用一次,如果在WHERE中使用了,那么在ORDER BY中就不要用了。

LIKE操作中,'%aaa%'不会使用索引,也就是索引会失效,但是‘aaa%’可以使用索引。

在索引的列上使用表达式或者函数会使索引失效,例如:select * from users where YEAR(adddate)<2007,将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′。其它通配符同样,也就是说,在查询条件中使用正则表达式时,只有在搜索模板的第一个字符不是通配符的情况下才能使用索引。

在查询条件中使用不等于,包括<符号、>符号和!=会导致索引失效。特别的是如果对主键索引使用!=则不会使索引失效,如果对主键索引或者整数类型的索引使用<符号或者>符号不会使索引失效。

在查询条件中使用IS NULL或者IS NOT NULL会导致索引失效。

字符串不加单引号会导致索引失效。更准确的说是类型不一致会导致失效,比如字段email是字符串类型的,使用WHERE email=99999 则会导致失败,应该改为WHERE email='99999'。

在查询条件中使用OR连接多个条件会导致索引失效,除非OR链接的每个条件都加上索引,这时应该改为两次查询,然后用UNION ALL连接起来。

如果排序的字段使用了索引,那么select的字段也要是索引字段,否则索引失效。特别的是如果排序的是主键索引则select * 也不会导致索引失效。

尽量不要包括多列排序,如果一定要,最好为这队列构建组合索引;

 

192. 怎么验证 mysql 的索引是否满足需求?

在select语句前加上explain就可以用来查看sql的执行计划,

explain显示了MySQL如何使用索引来处理select语句以及连接表

possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句

key: 实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引

 

193.说一下数据库的事务隔离?

未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据

提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)

可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读

串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

① 脏读: 脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

② 不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

③ 幻读:第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

 

194.说一下 mysql 常用的引擎?

a.Innodb引擎

Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。它本身实际上是基于Mysql后台的完整的系统。Mysql运行的时候,Innodb会在内存中建立缓冲池,用于缓冲数据和索引。但是,该引擎是不支持全文搜索的。同时,启动也比较的慢,它是不会保存表的行数的。当进行Select count(*) from table指令的时候,需要进行扫描全表。所以 当需要使用数据库的事务时,该引擎就是首选。由于锁的粒度小,写操作是不会锁定全表的。所以在并发度较高的场景下使用会提升效率的。

b.MyIASM引擎

是MySql的默认引擎,但不提供事务的支持,也不支持行级锁和外键。因此当执行Insert插入和Update更新语句时,即执行写操作的时候需要锁定这个表。所以会导致效率会降低。不过和Innodb不同的是,MyIASM引擎是保存了表的行数,于是当进行Select count(*) from table语句时,可以直接的读取已经保存的值而不需要进行扫描全表。所以, 如果表的读操作远远多于写操作时,并且不需要事务的支持的。可以将MyIASM作为数据库引擎的首先。

 

195.说一下 mysql 的行锁和表锁?

表级锁:每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;

行级锁:每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;

 

196.说一下乐观锁和悲观锁?共享锁与排他锁?

乐观锁

乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。

悲观锁

与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

应用场景

案例:

某商品,用户购买后库存数应-1,而某两个或多个用户同时购买,此时三个执行程序均同时读得库存为n,之后进行了一些操作,最后将均执行update table set 库存数=n-1,那么,很显然这是错误的。

解决:

1.使用悲观锁(其实说白了也就是排他锁)

|--程序A在查询库存数时使用排他锁(select * from table where id=10 for update)

|--然后进行后续的操作,包括更新库存数,最后提交事务。

|--程序B在查询库存数时,如果A还未释放排他锁,它将等待。

|--程序C同B……

2.使用乐观锁(靠表设计和代码来实现)

|--一般是在该商品表添加version版本字段或者timestamp时间戳字段

|--程序A查询后,执行更新变成了:

update table set num=num-1 where id=10 and version=23

这样,保证了修改的数据是和它查询出来的数据是一致的,而其他执行程序未进行修改。当然,如果更新失败,表示在更新操作之前,有其他执行程序已经更新了该库存数,那么就可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次

数(阿里巴巴开发手册规定重试次数不低于三次)。

总结:对于以上,可以看得出来乐观锁和悲观锁的区别。

1.悲观锁使用了排他锁(写锁),当程序独占锁时,其他程序就连查询都是不允许的,导致吞吐较低。如果在查询较多的情况下,可使用乐观锁。

2.乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入较频繁,对吞吐要求不高,可使用悲观锁。

也就是一句话:读频繁用乐观锁,写频繁用悲观锁。

 

共享锁与排他锁

首先说明:数据库的增删改操作默认都会加排他锁,而查询不会加任何锁。

共享锁:对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。要想修改就必须等所有共享锁都释放完之后。语法为:

select * from table lock in share mode

排他锁:对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作。语法为:

select * from table for update --增删改自动加了排他锁

 

197.mysql 问题排查都有哪些手段?

EXPLAIN + SELECT 查询

 

198.如何做 mysql 的性能优化?

缓存

索引

 

十八、Redis

199.Redis 是什么?都有哪些使用场景?它的优势?

Redis是一个开源的key—value型数据库,支持string、list、set、zset和hash类型数据。对这些数据的操作都是原子性的,Redis为了保证效率会定期持久化数据。

适用场景:

数据高并发的读写

海量数据的读写

对扩展性要求高的数据

不适场景:

需要事务支持(非关系型数据库)

基于sql结构化查询储存,关系复杂

redis 最适合的场景

Redis最适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就会有疑问,似乎Redis更像一个加强版的Memcached,那么何时使用Memcached,何时使用Redis呢?

如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点:

1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

2 、Redis支持数据的备份,即master-slave模式的数据备份。

3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

(1)、会话缓存(Session Cache)

最常用的一种使用Redis的情景是会话缓存(session cache)。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化。当维护一个不是严格要求一致性的缓存时,如果用户的购物车信息全部丢失,大部分人都会不高兴的,现在,他们还会这样吗?

幸运的是,随着 Redis 这些年的改进,很容易找到怎么恰当的使用Redis来缓存会话的文档。甚至广为人知的商业平台Magento也提供Redis的插件。

(2)、全页缓存(FPC)

除基本的会话token之外,Redis还提供很简便的FPC平台。回到一致性问题,即使重启了Redis实例,因为有磁盘的持久化,用户也不会看到页面加载速度的下降,这是一个极大改进,类似PHP本地FPC。

再次以Magento为例,Magento提供一个插件来使用Redis作为全页缓存后端。

此外,对WordPress的用户来说,Pantheon有一个非常好的插件 wp-redis,这个插件能帮助你以最快速度加载你曾浏览过的页面。

(3)、队列

Reids在内存存储引擎领域的一大优点是提供 list 和 set 操作,这使得Redis能作为一个很好的消息队列平台来使用。Redis作为队列使用的操作,就类似于本地程序语言(如Python)对 list 的 push/pop 操作。

如果你快速的在Google中搜索“Redis queues”,你马上就能找到大量的开源项目,这些项目的目的就是利用Redis创建非常好的后端工具,以满足各种队列需求。例如,Celery有一个后台就是使用Redis作为broker,你可以从这里去查看。

(4),排行榜/计数器

Redis在内存中对数字进行递增或递减的操作实现的非常好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些操作的时候变的非常简单,Redis只是正好提供了这两种数据结构。所以,我们要从排序集合中获取到排名最靠前的10个用户–我们称之为“user_scores”,我们只需要像下面一样执行即可:

当然,这是假定你是根据你用户的分数做递增的排序。如果你想返回用户及用户的分数,你需要这样执行:

ZRANGE user_scores 0 10 WITHSCORES

Agora Games就是一个很好的例子,用Ruby实现的,它的排行榜就是使用Redis来存储数据的,你可以在这里看到。

(5)、发布/订阅

最后(但肯定不是最不重要的)是Redis的发布/订阅功能。发布/订阅的使用场景确实非常多。我已看见人们在社交网络连接中使用,还可作为基于发布/订阅的脚本触发器,甚至用Redis的发布/订阅功能来建立聊天系统!(不,这是真的,你可以去核实)。

redis 的优势:

(1) 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)

(2) 支持丰富数据类型,支持string,list,set,sorted set,hash

(3) 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

(4) 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除

 

200.Redis 有哪些功能?

 

 

201.Redis 和 memecache 有什么区别?

1)、存储方式

Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。

Redis有部份存在硬盘上,这样能保证数据的持久性。

2)、数据支持类型

Memcache对数据类型支持相对简单。

Redis有复杂的数据类型。

3)、使用底层模型不同

它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。

Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

4)、value大小

redis最大可以达到1GB,而memcache只有1MB

redis相比memcached有哪些优势

(1) memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型

(2) redis的速度比memcached快很多

(3) redis可以持久化其数据

 

202.Redis 为什么是单线程的?

Redis为什么这么快,尤其是其采用单线程

因为它的所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题;

而且正因为Redis是单线程,所以要小心使用Redis指令,对于那些耗时的指令(比如keys),一定要谨慎使用,一步小心就可能会导致Redis卡顿;

Redis单线程处理多个并发客户端连接:IO多路复用

Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。

Nginx也是采用IO多路复用原理解决C10k问题

 

203.什么是缓存穿透? 缓存雪崩?怎么解决?

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层。

缓存雪崩:是指在某一个时间段,缓存集中过期失效。其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮.

缓存穿透的原因

缓存穿透的问题,肯定是再大并发情况下。依此为前提,我们分析缓存穿透的原因如下:

1、恶意攻击,猜测你的key命名方式,然后估计使用一个你缓存中不会有的key进行访问。

2、第一次数据访问,这时缓存中还没有数据,则并发场景下,所有的请求都会压到数据库。

3、数据库的数据也是空,这样即使访问了数据库,也是获取不到数据,那么缓存中肯定也没有对应的数据。这样也会导致穿透。

缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。缓存穿透问题可能会使后端存储负载加大,由于很多后端存储不具备高并发性,甚至可能造成后端存储宕掉。

解决的步骤如下:

1、再web服务器启动时,提前将有可能被频繁并发访问的数据写入缓存。—这样就规避大量的请求在第3步出现排队阻塞。

2、规范key的命名,并且统一缓存查询和写入的入口。这样,在入口处,对key的规范进行检测。–这样保存恶意的key被拦截。

3、Synchronized双重检测机制,这时我们就需要使用同步(Synchronized)机制,在同步代码块前查询一下缓存是否存在对应的key,然后同步代码块里面再次查询缓存里是否有要查询的key。 这样“双重检测”的目的,还是避免并发场景下导致的没有意义的数据库的访问(也是一种严格避免穿透的方案)。

这一步会导致排队,但是第一步中我们说过,为了避免大量的排队,可以提前将可以预知的大量请求提前写入缓存。

4、不管数据库中是否有数据,都在缓存中保存对应的key,值为空就行。–这样是为了避免数据库中没有这个数据,导致的平凡穿透缓存对数据库进行访问。

5、第4步中的空值如果太多,也会导致内存耗尽。导致不必要的内存消耗。这样就要定期的清理空值的key。避免内存被恶意占满。导致正常的功能不能缓存数据。

 

204.Redis 支持的数据类型有哪些?

REDIS_STRING 字符串对象

REDIS_LIST 列表对象

REDIS_HASH 哈希对象

REDIS_SET 集合对象

REDIS_ZSET 有序集合对象

 

205.Redis 支持的 java 客户端都有哪些?

Redis的Java客户端很多,官方推荐的有三种:Jedis、Redisson和lettuce。

在这里对Jedis和Redisson进行对比介绍

Jedis:

轻量,简洁,便于集成和改造

支持连接池

支持pipelining、事务、LUA Scripting、Redis Sentinel、Redis Cluster

不支持读写分离,需要自己实现

Redisson:

基于Netty实现,采用非阻塞IO,性能高

支持异步请求

支持连接池

支持pipelining、LUA Scripting、Redis Sentinel、Redis Cluster

不支持事务,官方建议以LUA Scripting代替事务

支持在Redis Cluster架构下使用pipelining

支持读写分离,支持读负载均衡,在主从复制和Redis Cluster架构下都可以使用

内建Tomcat Session Manager,为Tomcat 6/7/8提供了会话共享功能

可以与Spring Session集成,实现基于Redis的会话共享

对于Jedis和Redisson的选择,同样应遵循前述的原理,尽管Jedis比起Redisson有各种各样的不足,但也应该在需要使用Redisson的高级特性时再选用Redisson,避免造成不必要的程序复杂度提升

 

206.Jedis和 Redisson 有哪些区别?

概况对比

Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis特性。Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。2.2. 编程模型Jedis中的方法调用是比较底层的暴露的Redis的API,也即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能熟练的使用Jedis。而Redisson中的方法则是进行比较高的抽象,每个方法调用可能进行了一个或多个Redis方法调用

可伸缩性

Jedis使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。Redisson使用非阻塞的I/O和基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作。

数据结构

Jedis仅支持基本的数据类型如:String、Hash、List、Set、Sorted Set。Redisson不仅提供了一系列的分布式Java常用对象,基本可以与Java的基本数据结构通用,还提供了许多分布式服务,其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)。在分布式开发中,Redisson可提供更便捷的方法。

总结

功能需求不多,上Jedis即可

功能需求复杂使用Redisson,虽然臃肿但是功能齐全

 

207.怎么保证缓存和数据库数据的一致性?

首先尝试从缓存读取,读到数据则直接返回;如果读不到,就读数据库,并将数据会写到缓存,并返回。

需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)。

 

208.Redis 持久化有几种方式?

Redis 提供两种方式进行持久化,一种是RDB持久化(原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化),另外一种是AOF(append only file)持久化(原理是将Reids的操作日志以追加的方式写入文件)。那么这两种持久化方式有什么区别呢,改如何选择呢

区别

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储;

AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录;

优缺点

RDB存在哪些优势呢?

1). 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。

2). 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。

3). 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。

4). 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB又存在哪些劣势呢?

1). 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。

2). 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF的优势有哪些呢?

1). 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。

2). 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。

3). 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。

4). AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

AOF的劣势有哪些呢?

1). 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

2). 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。

 

209.Redis 怎么实现分布式锁?

分布式锁一般有三种实现方式:1. 数据库乐观锁;2. 基于Redis的分布式锁;3. 基于ZooKeeper的分布式锁。4.布隆过滤器;这里介绍第二种方式,基于Redis实现分布式锁

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

互斥性。在任意时刻,只有一个客户端能持有锁。

不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了

public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";

//意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;

    private static final String SET_IF_NOT_EXIST = "NX";

//意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。

    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**

     * 尝试获取分布式锁

     * @param jedis Redis客户端

     * @param lockKey 锁

     * @param requestId 请求标识

     * @param expireTime 超期时间

     * @return 是否获取成功

     */

    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);



        if (LOCK_SUCCESS.equals(result)) {

            return true;

        }

        return false;

    }

}

从上可以看出,我们的加锁代码满足我们可靠性里描述的三个条件。首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端

public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**

     * 释放分布式锁

     * @param jedis Redis客户端

     * @param lockKey 锁

     * @param requestId 请求标识

     * @return 是否释放成功

     */

    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

      //Lua脚本代码

        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

//作用,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。

        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));



        if (RELEASE_SUCCESS.equals(result)) {

            return true;

        }

        return false;

    }

}

为什么要使用Lua语言来实现呢?因为要确保上述操作是原子性的,

就是在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。

注:如果Redis是多机部署的,那么可以尝试使用Redisson实现分布式锁

 

210.Redis 分布式锁有什么缺陷?

单机故障

 

211.Redis 如何做内存优化?

 

 

212.Redis 淘汰策略有哪些?

voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据

 

213.Redis 常见的性能问题有哪些?该如何解决?

(1) Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

(2) 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库

(5) 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3…

这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了,可以立刻启用Slave1做Master,其他不变。

 

十九、JVM

214.说一下 jvm 的主要组成部分?及其作用?

JVM 整体组成可分为以下四个部分:

类加载器(ClassLoader)

运行时数据区(Runtime Data Area)

执行引擎(Execution Engine)

本地库接口(Native Interface)

各个组成部分的用途:

程序在执行之前先要把java代码转换成字节码(class文件),jvm首先需要把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area) ,而字节码文件是jvm的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native Interface)来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。

而我们通常所说的jvm组成指的是运行时数据区(Runtime Data Area),因为通常需要程序员调试分析的区域就是“运行时数据区”,或者更具体的来说就是“运行时数据区”里面的Heap(堆)模块,那接下来我们来看运行时数据区(Runtime Data Area)是由哪些模块组成的。

 

215.说一下 jvm 运行时数据区?

jvm的运行时数据区,不同虚拟机实现可能略微有所不同,但都会遵从Java虚拟机规范,Java 8 虚拟机规范规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域:

程序计数器(Program Counter Register)

Java虚拟机栈(Java Virtual Machine Stacks)

本地方法栈(Native Method Stack)

Java堆(Java Heap)

方法区(Methed Area)

接下来我们分别介绍每个区域的用途。

①、Java程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解析器的工作是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

特性:内存私有

由于jvm的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,也就是任何时刻,一个处理器(或者说一个内核)都只会执行一条线程中的指令。因此为了线程切换后能恢复到正确的执行位置,每个线程都有独立的程序计数器。

异常规定:无

如果线程正在执行Java中的方法,程序计数器记录的就是正在执行虚拟机字节码指令的地址,如果是Native方法,这个计数器就为空(undefined),因此该内存区域是唯一一个在Java虚拟机规范中没有规定OutOfMemoryError的区域。

②、Java虚拟机栈

Java虚拟机栈(Java Virtual Machine Stacks)描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个线帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每个方法从调用直至执行完成的过程,都对应着一个线帧在虚拟机栈中入栈到出栈的过程。

特性:内存私有,它的生命周期和线程相同。

异常规定:StackOverflowError、OutOfMemoryError

1、如果线程请求的栈深度大于虚拟机所允许的栈深度就会抛出StackOverflowError异常。

2、如果虚拟机是可以动态扩展的,如果扩展时无法申请到足够的内存就会抛出OutOfMemoryError异常。

③、本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈的作用是一样的,只不过虚拟机栈是服务Java方法的,而本地方法栈是为虚拟机调用Native方法服务的。

在Java虚拟机规范中对于本地方法栈没有特殊的要求,虚拟机可以自由的实现它,因此在Sun HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一了。

特性和异常: 同虚拟机栈,请参考上面知识点。

④、Java堆

Java堆(Java Heap)是Java虚拟机中内存最大的一块,是被所有线程共享的,在虚拟机启动时候创建,Java堆唯一的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,随着JIT编译器的发展和逃逸分析技术的逐渐成熟,栈上分配、标量替换优化的技术将会导致一些微妙的变化,所有的对象都分配在堆上渐渐变得不那么“绝对”了。

特性:内存共享

异常规定:OutOfMemoryError

如果在堆中没有内存完成实例分配,并且堆不可以再扩展时,将会抛出OutOfMemoryError。

Java虚拟机规范规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上连续即可,就像我们的磁盘空间一样。在实现上也可以是固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是可扩展的,通过-Xmx和-Xms控制。

⑤、方法区

方法区(Methed Area)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

误区:方法区不等于永生代

很多人原因把方法区称作“永久代”(Permanent Generation),本质上两者并不等价,只是HotSpot虚拟机垃圾回收器团队把GC分代收集扩展到了方法区,或者说是用来永久代来实现方法区而已,这样能省去专门为方法区编写内存管理的代码,但是在Jdk8也移除了“永久代”,使用Native Memory来实现方法区。

特性:内存共享

异常规定:OutOfMemoryError

当方法无法满足内存分配需求时会抛出OutOfMemoryError异常。

 

216.说一下堆栈的区别?

1.栈内存存储的是局部变量而堆内存存储的是实体;

2.栈内存的更新速度要快于堆内存,因为局部变量的生命周期很短;

3.栈内存存放的变量生命周期一旦结束就会被释放,而堆内存存放的实体会被垃圾回收机制不定时的回收。

 

217.队列和栈是什么?有什么区别?

队列先进先出,在队头做删除操作,在队尾做插入操作。

栈先进后出,在栈顶做插入和删除操作。

堆和它们不同,不存在是先进后出还是先进先出。

 

218.什么是双亲委派模型?

当需要加载一个类的时候,子类加载器并不会马上去加载,而是依次去请求父类加载器加载,一直往上请求到最高类加载器:启动类加载器。当启动类加载器加载不了的时候,依次往下让子类加载器进行加载。当达到最底下的时候,如果还是加载不到该类,就会出现ClassNotFound的情况。

好处:保证了程序的安全性。例子:比如我们重新写了一个String类,加载的时候并不会去加载到我们自己写的String类,因为当请求上到最高层的时候,启动类加载器发现自己能够加载String类,因此就不会加载到我们自己写的String类了。

 

219.说一下类加载的执行过程?

加载:将java源代码编译后的.class字节码文件以二进制流的方式加载进内存连接  

验证:验证加载进来的二进制流是否符合虚拟机的规范,不会危害的虚拟机自身的安全

准备:给类变量(静态变量)赋予初始值,基本数据/引用类型数据 

解析:将字符串引用转换为直接引用

初始化:变量赋予初始值、执行静态语句块、执行构造函数等等。

 

220.怎么判断对象是否可以被回收?

jvm要做垃圾回收时,首先要判断一个对象是否还有可能被使用。那么如何判断一个对象是否还有可能被用到?

如果我们的程序无法再引用到该对象,那么这个对象就肯定可以被回收,这个状态称为不可达。当对象不可达,该对象就可以作为回收对象被垃圾回收器回收。

 

那么这个可达还是不可达如何判断呢?

答案就是GC roots ,也就是根对象,如果从一个对象没有到达根对象的路径,或者说从根对象开始无法引用到该对象,该对象就是不可达的。

 

以下三类对象在jvm中作为GC roots,来判断一个对象是否可以被回收 (通常来说我们只要知道虚拟机栈和静态引用就够了)

虚拟机栈(JVM stack)中引用的对象(准确的说是虚拟机栈中的栈帧(frames))

我们知道,每个方法执行的时候,jvm都会创建一个相应的栈帧(栈帧中包括操作数栈、局部变量表、运行时常量池的引用),栈帧中包含这在方法内部使用的所有对象的引用(当然还有其他的基本类型数据),当方法执行完后,该栈帧会从虚拟机栈中弹出,这样一来,临时创建的对象的引用也就不存在了,或者说没有任何gc roots指向这些临时对象,这些对象在下一次GC时便会被回收掉

方法区中类静态属性引用的对象

静态属性是该类型(class)的属性,不单独属于任何实例,因此该属性自然会作为gc roots。只要这个class存在,该引用指向的对象也会一直存在。class 也是会被回收的,在面后说明

本地方法栈(Native Stack)引用的对象

一个class要被回收准确的说应该是卸载,必须同时满足以下三个条件

堆中不存在该类的任何实例

加载该类的classloader已经被回收

该类的java.lang.Class对象没有在任何地方被引用,也就是说无法通过反射再带访问该类的信息

这篇内容太少了,在说几句java中的四种引用类型

其实这四类引用的区别就在于GC时是否回收该对象

强引用(Strong) 就是我们平时使用的方式 A a = new A();强引用的对象是不会被回收的

软引用(Soft) 在jvm要内存溢出(OOM)时,会回收软引用的对象,释放更多内存

弱引用(Weak) 在下次GC时,弱引用的对象是一定会被回收的

虚引用(Phantom) 对对象的存在时间没有任何影响,也无法引用对象实力,唯一的作用就是在该对象被回收时收到一个系统通知

 

221.java 中都有哪些引用类型?

从JDK1.2开始,Java中的引用类型分为四种,分别是:

①强引用(StrongReference)

②软引用(SoftRefernce)

③弱引用(WeakReference)

④虚引用(PhantomReference)

 

222.说一下 jvm 有哪些垃圾回收算法?

主要有复制、标记清除、标记压缩三种实现算法。

1. 标记 - 清除算法

标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。

它的主要缺点:①.标记和清除过程效率不高 ;②.标记清除之后会产生大量不连续的内存碎片。

2. 复制算法

它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。

主要缺点:内存缩小为原来的一半。           

3. 标记  - 整理算法

标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。

主要缺点:在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。

 

223.说一下 jvm 有哪些垃圾回收器?

Serial收集器、ParNew收集器、Parallel Scavenge收集器、CMS收集器、G1收集器

 

224.详细介绍一下 CMS 垃圾回收器?

CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC ,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是:标记—清除。  

CMS过程:

初始标记 :在这个阶段,需要虚拟机停顿正在执行的任务,官方的叫法STW(Stop The Word)。这个过程从垃圾回收的”根对象”开始,只扫描到能够和“根对象”直接关联的对象,并作标记。所以这个过程虽然暂停了整个JVM,但是很快就完成了。

并发标记 :这个阶段紧随初始标记阶段,在初始标记的基础上继续向下追溯标记。并发标记阶段,应用程序的线程和并发标记的线程并发执行,所以用户不会感受到停顿。

并发预清理 :并发预清理阶段仍然是并发的。在这个阶段,虚拟机查找在执行并发标记阶段新进入老年代的对象(可能会有一些对象从新生代晋升到老年代, 或者有一些对象被分配到老年代)。通过重新扫描,减少下一个阶段”重新标记”的工作,因为下一个阶段会Stop The World。

重新标记 :这个阶段会暂停虚拟机,收集器线程扫描在CMS堆中剩余的对象。扫描从”跟对象”开始向下追溯,并处理对象关联。

并发清理 :清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行。

并发重置 :这个阶段,重置CMS收集器的数据结构,等待下一次垃圾回收。

 

225.新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?

新生代垃圾收集器

Serial

Serial收集器是单线程的一个收集器,但它的单线程的意义是它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集的时候,必须暂停其他所有的工作线程,直到它收集结束。

分代收集算法:新生代单线程采用复制算法,并暂停所有用户线程;老年代单线程采用标记-整理算法,并暂停所有用户线程。

ParNew

ParNew收集器是Serial收集器的多线程版。其基本操作和Serial算法基本一致。该收集器一般搭配CMS收集器进行工作。‘

分代收集算法:新生代采用复制算法,并暂停所有用户线程;老年代采用标记-整理算法,并暂停所有用户线程。

Parallel Scavenge

Parallel Scavenge收集器是也与ParNew算法十分相似,但是与其他收集器的关注点大多是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器目的是达到一个可控制的吞吐量。吞吐量就是CPU用于运行用户代码的时间与CPU总消耗的时间的比值。即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),举个例子,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。

GC自适应调节策略:JVM会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大吞吐量。Parallel Scavenge收集器可以搭配自适应调节策略。

分代收集算法:新生代采用复制算法,并暂停所有用户线程;老年代采用标记-整理算法,并暂停所有用户线程。

老年代垃圾收集器

Serial Old

Serial Old是Serial算法的老年代版本,同样是一个单线程收集器。该收集器主要是给Client模式下的虚拟机使用的。

分代收集算法:新生代单线程采用复制算法,并暂停所有用户线程;老年代单线程采用标记-整理算法,并暂停所有用户线程。

Parallnel Old

Parallnel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

CMS

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。该收集器是基于”标记-清除“算法实现的。

CMS收集器的收集过程分为以下4个步骤:

1、初始标记(Stop the World,标记GC Roots能直接关联到的对象)

2、并发标记(进行GC Roots Tracing的过程)

3、重新标记(Stop the World,休整并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录)

4、并发清除(并发清除无用的对象)

缺点:

a、CMS收集器对CPU资源非常敏感,并发阶段占用的线程资源较多。

b、CMS收集器无法处理浮动垃圾。因为CMS并发清理阶段用户线程还在运行着,所以也会有相应的垃圾产生,这部分垃圾CMS无法在此次的收集中处理掉它们。

c、CMS收集器由于是基于“标记-清除”算法,故会产生较多的内存空间碎片。

分类 所属分代 使用线程 使用算法

Serial 新生代 单线程 复制(新)、标记-整理(老)

ParNew 新生代 多线程 复制(新)、标记-整理(老)

Parallel Scavenge 新生代 多线程 吞吐量优先算法

Serial Old 老生代 单线程 复制(新)、标记-整理(老)

Parallel Old 老生代 多线程 复制(新)、标记-整理(老)

CMS 老生代 多线程 标记-清除算法(初始标记、并发标记、重新标记、并发清除)

G1 新生代&&老生代 多线程 标记-整理算法(初始标记、并发标记、最终标记、筛选回收)

 

226.简述分代垃圾回收器是怎么工作的?

年轻代:

  所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

  年老代:

  在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

  持久代:

  用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置

 

227.说一下 jvm 调优的工具?

jvm监控分析工具一般分为两类,一种是jdk自带的工具,一种是第三方的分析工具。jdk自带工具一般在jdk bin目录下面,以exe的形式直接点击就可以使用,其中包含分析工具已经很强大,几乎涉及了方方面面,但是我们最常使用的只有两款:jconsole.exe和jvisualvm.exe;第三方的分析工具有很多,各自的侧重点不同,比较有代表性的:MAT(Memory Analyzer Tool)、GChisto等。

 

228.常用的 jvm 调优的参数都有哪些?

内存调优

**设置heap大小

这个非常重要,通常只要这里设置够了,就不会出现溢出。

”-Xms4096m -Xmx4096m

or

”-Xms4g -Xmx4g

这两个值的大小应该一样,这样减少转换时初始化时间

64位一般可支持的最大内存是多少?如果物理内存为32G(一般为物理内存的90%,也就是27\~28G)

新生代和老年代的大小

这个参数相当于设置等值的最小、最大新生代,NewRatio这个设置新、老代比例,NewRatio=1代表,两个相等大小

”-Xmn1g

这个值如果不设的话,默认只有6M  

**设置持久代大小

同理,这两个值应该是一样的(jdk7)

”-XX:PermSize=512m -XX:MaxPermSize=512m

JDK8起用这两个参数

”-XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m

**设置code cache

”-XX:ReservedCodeCacheSize=1024m -XX:+UseCompressedOops

GC调优

并发GC线程设置

服务器总的线程数/jvm实例数就行了,如果一台服务器上有4个jvm。比如2颗6核超线程(超线程后1个核相当于2个线程)就是24/4=6调,置ParallelGCThreads时最大不要超过6就行了。

Mac笔记本4核8线程,一个jvm,ParallelGCThreads=CPU 数量/JVM 数量

”-XX:ParallelGCThreads=8

你可能感兴趣的:(面试集锦200道)