2022面试大全

2 篇文章 0 订阅
2 篇文章 0 订阅

目录

一: 基础篇?

1:java语言有哪些特点?

简单性、面向对象、分布性、编译和解释性、稳健性、安全性、可移植性、高性能、多线索性、动态性

2:面向对象和面向过程的区别?

  1. 面向过程:面向过程性能比面向对象高,因为类的调用需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机,嵌入式开发,Linux/Unix等一般采用面向过程开发,但是面向过程没有面向对象易维护,易扩展,易复用.
  2. 面向对象:面向对象易维护,易扩展,易复用.因为面向对象有封装,集成,多态的特性.索引可以设计出低耦合的系统,使系统更加灵活,更易与维护,但是面向对象比面向过程性能低.

3:八种基本数据类型的大小,以及他们的封装类?

  1. 八种数据结构:byte,char,short,int),long,float,double,boolean
  2. 大小(字节):1bit,2bit,2bit,4bit,8bit,4bit,8bit,–
  3. 默认值:(byte)0,\u0000(null),(short)0,0,0L,0.0f,0.0d,false
  4. 封装类:Byte,Character,Short,Integer,Float,Double,Boolean

4:标识符的命名规则?

凡是自己可以起名字的地方都叫标识符。比如:类名、变量名、方法名、接口名、包名…
包名:多单词组成是所有字母都小写:xxxyyyzzz。
类名、接口名:多单词组成时,所有单词首字母大写:XxxYyyZzz。
变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz。
常量名:所有字母都大写。多单词时每个单词用下划线来连接:XXX_YYY_ZZZ。
标识符的规范如果不遵守的话仍然可以编译,建议遵守
标识符的注意事项
1、在起名时,为了提高阅读性,要尽量有意义,“见名知意”。
2、java采用unicode字符集,因此标识符也可以使用汉字声明,但是不建议使用。

5:instanceof关键字的作用?

是Java中的一个双目运算符,用于测试一个对象是否为一个类的实例,

6:java自动装箱与拆箱?

装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。

7:重载和重写的区别?

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求,不能根据返回类型进行区分。

8:equals 与 == 的区别?

==操作符既可以用于比较基本数据类型(boolean,char, int,float, double等),又可以在对象之间进行比较。 equals方法则只能用于对象之间的比较

9:hashCode的作用?

1、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;

2、如果两个对象相同,就是适用于equals(java.lang.Object) 方法,那么这两个对象的hashCode一定要相同;

3、如果对象的equals方法被重写,那么对象的hashCode也尽量重写,并且产生hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第2点;

4、两个对象的hashCode相同,并不一定表示两个对象就相同,也就是不一定适用于equals(java.lang.Object) 方法,只能够说明这两个对象在散列存储结构中,如Hashtable,他们“存放在同一个篮子里”。

10:String,StringBuffer 和StringBuilder 区别?

  1. String类是不可变类,即一旦一个String对象被创建以后,包含在这个对象中的字符序列是不可改变的,直至这个对象被销毁。
  2. StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer被创建以后,通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()等方法可以改变这个字符串对象的字符序列。一旦通过StringBuffer生成了最终想要的字符串,就可以调用它的toString()方法将其转换为一个String对象。
  3. StringBuffer多线程安全的,StringBuilder多线程不安全
    StringBuffer从JDK1.0就有了,StringBuilder是JDK5.0才出现
    StringBuffer比StringBuilder多了一个toStringCache字段,用来在toString方法中进行缓存,每次append操作之前都先把toStringCache设置为null,若多次连续调用toString方法,可避免每次Arrays.copyOfRange(value, 0, count)操作,节省性能。
    由于StringBuilder没有考虑同步,在单线程情况下,StringBuilder的性能要优于StringBuffer
  4. StringBuffer 多线程是线程安全的,因为加了同步锁.StringBuilder 多线程是不安全的,但是执行效率高.

11:ArrayList和LinkedList区别?

  1. LinkedList和ArrayList的差别主要来自于ArrayList和LinkedList数据结构的不同。ArrayList是基于数组实现的,LinkedList是基于双链表实现的。另外LinkedList类不仅是List接口的实现类,可以根据索引来随机访问集合中的元素,除此之外,LinkedList还实现了Deque接口,Deque接口是Queue接口的子接口,它代表一个双向队列,因此LinkedList可以作为双向队列 ,栈(可以参见Deque提供的接口方法)和List集合使用,功能强大。
  2. 因为ArrayList是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的,可以直接返回数组中index位置的元素,因此在随机访问集合元素上有较好的性能。ArrayList获取数据的时间复杂度是O(1),但是要插入、删除数据却是开销很大的,因为这需要移动数组中插入位置之后的的所有元素。
  3. 相对于ArrayList,LinkedList的随机访问集合元素时性能较差,因为需要在双向列表中找到要index的位置,再返回;但在插入,删除操作是更快的。因为LinkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是ArrayList最坏的一种情况,时间复杂度是O(n),而LinkedList中插入或删除的时间复杂度仅为O(1)。ArrayList在插入数据时还需要更新索引(除了插入数组的尾部)。
  4. LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的是实际的数据和前后节点的位置。

使用场景:

(1)如果应用程序对数据有较多的随机访问,ArrayList对象要优于LinkedList对象;

( 2 ) 如果应用程序有更多的插入或者删除操作,较少的随机访问,LinkedList对象要优于ArrayList对象;

(3)不过ArrayList的插入,删除操作也不一定比LinkedList慢,如果在List靠近末尾的地方插入,那么ArrayList只需要移动较少的数据,而LinkedList则需要一直查找到列表尾部,反而耗费较多时间,这时ArrayList就比LinkedList要快。

12:Collection包结构,与Collections的区别?

Collection
是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector、Stack、Set;

Collections
是集合类的一个帮助类, 它包含有各种有关集合操作的静态多态方法,用于实现对各种集
合的搜索、排序、线程安全化等操作。此类不能实例化,就像一个工具类,服务于Java的Collection框
架。

13:HashMap与HashTable的区别?

  1. HashTable是线程安全的,HashMap是线程不安全的。
  2. HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
  3. HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
  4. 哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
  5. HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
  6. HashTable中hash数组默认大小是11,增加的方式是 old2+1。HashMap中hash数组默认大小是16,增加的方式是 old2。

14:Java的四种引用,强弱软虚?

  1. 强引用就是程序中一般使用的引用类型,强引用的对象是可触及的,可以直接访问目标对象,不会被回收,相对的,软引用,弱引用和虚引用的对象是软可触及的,弱可触及和虚可触及的,在一定的条件下,都是可以被回收的.
    强引用示例:StringBuffer sb = new StringBuffer(“321”);
    假设以上代码是在函数内部运行的, 那么局部变量sb则会被分配在栈上,而对象StringBuffer示例则会被分配在堆上,局部变量sb会指向StringBuffer示例所在的堆空间,通过sb则就可以操作该实例,那sb就是StringBuffer的强引用.

  2. 软引用----可被回收的引用,软引用是比强引用弱一点的引用类型,一个对象只持有软引用,那么当堆空间不足的时候, 就会被回收,软引用使用java.lang.ref.SoftReference类型.

  3. 弱引用----发现即回收.

  4. 虚引用----对象回收跟踪.一个持有虚引用的对象,和没有引用几乎一样,他的作用主要是跟踪垃圾回收过程.

15:泛型常用特点?

泛型意味着编写的代码可以被不同类型的对象锁重用.使用泛型的好处在于,不必因为类型不用而定义不同的类或者集合等.

16:Java创建对象有几种方式?

  1. 使用new 关键字.
  2. 使用class类的newInstance方法.
  3. 使用Constructor类的newInstance方法.
  4. 使用clone方法.
  5. 使用反序列化

17:有没有可能两个不相等的对象有相同的hashCode?

可能,所以才会存在HashMap的key值hash冲突.

18:深拷贝和浅拷贝的区别?

如何区分深拷贝和浅拷贝,简单来说,假设b复制了a,当a发生变化的时候b也跟着变化了,那么久是浅拷贝,反正如果a发生变化b没有跟着变化那么就是深拷贝.

19:final有哪些用法?

  1. 被final修饰的类不可以被继承.
  2. 被final修饰的方法不可以被重写.
  3. 被final修饰的变量不可以被改变,如果修饰引用,那么表示引用不可变,引用指向的内容可变
  4. 被final修饰的方法,JVM会尝试将其内联,来提供运行效率.
  5. 被final修饰的常量,在编译阶段会存入常量池中

20:static都有哪些方法?

使用与静态方法和静态变量

21:3*0.1==0.3返回值是什么?

false

22:a=a+b与a+=b有什么区别吗?

a=a+b的含义:变量a开辟一片内存单元,把某个数字存入到内存单元中,然后把这个数取出到CUP中进行计算(加b),然后存回到原内存单元中。
+=操作符会进行隐式自动类型转换,此处 a+=b隐式的将加操作的结果类型强制转换为持有结果的类型,而a=a+b则不会自动进行类型转换,如: byte b1 = 2;byte b2 = 2;
这个时候就会出现一个问题,当你输入
b1 = b1+b2的时候,你会发现编译都不能通过

23:try catch finally, try 里有return, finally还执行么?

执行,无论try中有没有return,finally里面的代码块都会执行

24:Exception和Error包结构?

  1. Exception和Error都是继承Throwable类,在Java中只要Throwable类型的实例才可以被抛出(throw)或者捕获(catch),他是异常处理机制的基本组成类型.
  2. Exception异常是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应的处理.
  3. Error是指正常情况下,不大可能出现的情况,觉大部分的Error都是导致程序(比如JVM自身)处于非正常状态,不可恢复状态.既然是非正常情况,所以不便于也不需要捕获,常见的比如OutOfMemoryError之类,都是Error的子类.
  4. Exception又分为可检查(cecked)异常和不检查(unchecked)异常,可检查异常在源码里必须显示的进行捕获处理,这里是编译期检查的一部分.而Error是Throwable不是Exception
  5. 不检查异常就是所谓的运行时异常,类型NullPoninterException,ArrayIndexOutOfBoundsException之类,通常是可以编码避免的逻辑错误,集体根据需要来判断是否需要捕获,并不会在编译期强制要求.

25:OOM你遇到过哪些情况?SOF你遇到过哪些情况?

(1):OOM

  1. 产生堆溢出 将new出的对象放到List中可防止GC回收。
  2. 产生虚拟机栈或本地方法栈StackOverFlow 当请求的栈深度超过JVM允许最大深度即可,用Xss设置
  3. 产生虚拟机栈或方法栈OutOfMemory 不断创建线程
  4. 运行时常量池溢出 使用String.interm()填充常量池。intern的左右是如果该常量不再常量池中,则添加到常量池,否则返回该常量引用。常量池是方法区一部分,运行时可限制方法区PermSize和最大方法区MaxPermSize大小
  5. 方法区溢出 通过CGLib将大量信息放到方法区

(2):SOF

stackoverflow定义:当应用程序递归太深而发生栈溢出时,则抛出改错误.栈默认的内存空间是1-2M,一旦出现大量的递归和死循环,在不断压栈的过程中导致数据的容量大于1-2M时则会导致溢出
栈溢出的原因:递归调用,大量循环或者死循环,全局变量是否过多,数据,List,Map数据过大.

26:简述线程,程序,进程的基本概念,以及他们之间的关系是什么?

  1. 线程与进程相似
    但线程是一个比进程更小的执行单位,一个进程再其执行的过程中可以产生多个线程,与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或者是在各个线程之间做切换工作时,负担要比进程小得多,也正因为如此,线程也被成为轻量级的进程.

  2. 程序是含有指令和数据的文件
    被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码.

  3. 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的

27:Java序列化中如果有些字段不想序列化,怎么办?

transient关键字,用对于不想被序列化的变量,用transient关键字的作用就是:阻止用该关键字修饰的变量序列化,他只能修饰变量,不能修饰类和方法.

28:说说java中IO流?

  1. 二进制流可以适用任何文件的操作(字符文件、视频文件、图像文件、音频文件)

    1.1 InputStream/OutputStream用于二进制操作流
    1.2 为解决内存占用效率不高的情况,引入二进制缓存流BufferedInputStream/BufferedOutputStream
    
  2. 为解决字符文件源文件和写入时的编码格式问题引入字符流处理操作
    2.1 InputStreamReader/OutputStreamWriter用于字符流的文件操作,构造方法中可以设置编码格式,解决乱码问题
    2.2 对应的也有缓存流机制 BUfferedReader/BufferedWriter
    2.3 为了将这一系列字符流操作简化,引入FileReader/FileWriter,相当于既设置了缓存流,也设置了字符流,并且默认编码方式为utf8格式

29:JAVA 中IO与NIO的区别?

标准IO, 基于字节流和字符流进行操作,阻塞IO。
NIO基于通道channel和缓冲区Buffer进行操作,支持非阻塞IO,提供选择器

30:Java反射的作用的原理是什么?

通过反射java可以动态的加载未知的外部配置对象,临时生成字节码进行加载使用,使代码更灵活,极大地提高应用的扩展性。

31:说说List,Set,Map三者的区别?

  1. List接口存储一组不唯一的数据,且是有序的.
  2. Set 接口存储一组唯一的数据,且是无序的.
  3. Map用来存储键值对的数据,两个key可以引用相同的对象,但key值不能重复.key和value都可以是任何类型.

32:Object 有哪些常用方法?大致说一下每个方法的含义?

1:clone 方法

保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出CloneNotSupportedException 异常,深拷贝也需要实现 Cloneable,同时其成员变量为引用类型的也需要实现 Cloneable,然后重写 clone 方法。

2:finalize 方法

该方法和垃圾收集器有关系,判断一个对象是否可以被回收的最后一步就是判断是否重写了此方法。

3:equals 方法

该方法使用频率非常高。一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。子类一般都要重写这个方法。

4:hashCode 方法

该方法用于哈希查找,重写了 equals 方法一般都要重写 hashCode 方法,这个方法在一些具有哈希功能的 Collection 中用到。
一般必须满足 obj1.equals(obj2)==true 。可以推出 obj1.hashCode()==obj2.hashCode() ,但是hashCode 相等不一定就满足 equals。不过为了提高效率,应该尽量使上面两个条件接近等价。
JDK 1.6、1.7 默认是返回随机数;
JDK 1.8 默认是通过和当前线程有关的一个随机数 + 三个确定值,运用 Marsaglia’s xorshift scheme 随机数算法得到的一个随机数。v

5:wait 方法

配合 synchronized 使用,wait 方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait() 方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生。
其他线程调用了该对象的 notify 方法;
其他线程调用了该对象的 notifyAll 方法;
其他线程调用了 interrupt 中断该线程;
时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个 InterruptedException 异常。

6:notify 方法

配合 synchronized 使用,该方法唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占 CPU 的线程,等待队列中的线程指的是等待唤醒的线程)

7:notifyAll 方法

配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。

33:Java创建对象有几种方式?

  1. 使用new 关键字.
  2. 使用class类的newInstance方法.
  3. 使用Constructor类的newInstance方法.
  4. 使用clone方法.
  5. 使用反序列化

34:获取一个类Class对象的方式有哪些?

  1. 通过对象获得,已知某个类的实例,则可以调用该实例的getClass()方法获得Class对象.
  2. forName获得,已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()方法获取,但需要抛出异常
  3. 类型.class获得,已知集体的类,通过类的class属性获取,改方法最为安全可能,程序性能最高

35:用过ArrayList吗?说一些他有什么特点?

优点

  1. 根据下标遍历元素效率较高.
  2. 根据下标访问元素效率较高.
  3. 在数组的基础上封装了对元素操作的方法.
  4. 可以自动扩容

缺点

  1. 插入和删除的效率比较低.
  2. 根据内容查找元素的效率比较低

扩容规则

每次扩容现有容量的50%

36:有数组了为什么还要搞个ArrayList呢?

数组的长度在定义的时候就已经设定了,容量固定难以动态扩展,通常情况下存储的是同类型对象,类如int[] ,String [],当然Object[]除外,并且不能后随意添加项和删除其中的项.
数组可以动态增长容量,可以在任意位置插入和删除项,

37:说说什么是fail-fast?

fail-fast机制,及快速失败机制,是Java集合中的一种错误检测机制.当在迭代集合的过程中,改集合再机构上发生改变的时候,就有可能发生fail-fast,即抛出ConcurrentModificationException异常.fail-fast并不保证在不同步的修改下抛出异常,他只是尽最大努力去抛出,所以这种机制一般仅用于检测BUG,
使用java.util.concurrent包下的集合来替代.比如使用CopyOnWriteArraylist代替ArrayList,CopyOnWriteArrayList在使用上和ArrayList是一样的,但是他在进行add或remove操作时,并不是在原数组上修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将旧数组的引用指向新数组.所以对于CopyOnWriteArrayList来说,在迭代的过程中并不发生fail-fast现象。

38:HashMap 中的key我们可以使用任何类作为Key吗?

HashMap中的key可以是null,基本数据类型或引用数据类型,为了HashMap的正常使用,key一般是不可变对象,至少该对象中用于计算hash值的属性要不可变,方可保证HashMap的正常使用

39:HashMap的长度为什么是2的N次方呢?

HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;
这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1),
hash%length==hash&(length-1)的前提是length是2的n次方;
为什么这样能均匀分布减少碰撞呢?2的n次方实际就是1后面n个0,2的n次方-1 实际就是n个1;
例如长度为9时候,3&(9-1)=0 2&(9-1)=0 ,都在0上,碰撞了;
例如长度为8时候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞;

40:HashMap与ConCurrentHashMap的异同?

1:HashMap

  1. 实现了Map接口,实现了将唯一键映射到特定值上.允许一个NULL键和多个NULL值,非线程安全.
  2. 初始化数组长度是16,是2 的幂次方
  3. jdk8之后,如果链表的长度大于8,这个单向链表就会转换为红黑树;如果链表的长度小于6位,就会从红黑树转换为链表.
  4. 数组扩容时,扩容的大小是原有数组长度的2倍
  5. 键可以是null,但是只能有一个

2:ConcurrentHashMap

ConcurrentHashMap允许多个修改操作的并发进行,其关键在于使用了锁分离技术,它使用了多个锁来控制对hash表的不同部分进行的修改.ConcurrentHashMap内部使用段(segment)来表示这些不同的部分,每个段其实就是一个小的HashTable,他们有自己的锁.只要多个修改操作发生在不通的段上,他们就可以并发进行.

41:红黑树有那几个特征?

  1. 每个节点颜色不是黑素,就是红色,
  2. 根节点是黑色的
  3. 如果一个节点是红色,那么它的两个子节点就是黑色的
  4. 对于每个节点,从该节点到其后代叶节点的简单路径上,均包含相同数目的黑色节点

42:说说你平时是怎么处理Java异常的?

统一处理异常的方式,捕获到一直异常,并抛出到前端

二: JVM篇?

1:知识点汇总?

链接: jvm知识点汇总.

2:知识点详解?

链接: jvm知识点汇总.

3:说说类加载与卸载?

  1. 类加载:查找并加载类的二进制数据.生成Class的实例
    在加载类时,Java虚拟机必须完成以下3件事情:
    1.通过类的全名,获取类的二进制数据流
    2.解析类的二进制数据流为方法区内的数据结构(Java类模型)
    3.在堆中创建该类的Class类的对象.表示该类型.作为方法区这个类的各种数据的访问入口.

  2. 类卸载:如果程序运行过程中,堆中类的class对象不再被引用,方法区中的二进制数据会被卸载,称为类卸载.

4:简述一下JVM的内存模型?

JVM内存共分为堆,方法区,本地方法区,程序计数器,虚拟机栈
在这里插入图片描述

  • 堆: 线程共享主要存放的是对象的实例和数组,更加关注数据的存储,被所有线程所共享的一块内存区域在虚拟机启动的时候创建,用于存储对象的实例,堆可以按照可扩展来实现,通过-Xms,-Xmx来控制
  • 方法区: 线程共享,被所有方法线程共享的一块内存区域,用来存储已经被虚拟机加载的类信息,常量,静态变量等.
  • 本地方法区: 线程私有,和虚拟机栈类似,主要为虚拟机使用native方法服务.
  • 程序计数器: 线程私有,是当前线程所执行的字节码行号指示器.
  • 虚拟机栈: 主要存放局部变量,操作数栈,返回结果,更加关注程序方法的执行,线程私有,一个线程每调用一个方法就创建一个栈针,存储了局部变量,操作数,动态链接,方法返回地址等数据,每个方法从调用到执行完毕,对应虚拟机栈中的入栈和出栈,如果线程强求的栈深度大于虚拟机的栈深度,则报错:StackOverflowError,如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存时,则抛出:OutOfMemoryError

5:说说堆和栈的区别?

  • 栈内存: 栈内存首先是一片内存区域,存储的都是局部变量,凡是定义在方法中的都是局部变量(方法外的是全局变量),for循环内部定义的也是局部变量,是先加载函数才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用于,一旦离开作用域,变量就会释放.栈内存的更新速度很快,因为局部变量的生命周期都很短.
  • 堆内存: 堆内存存储的是数组和对象.(其实数组就是对象).凡是new建立的都是再堆中存放着,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失了.这个数据也没有消息,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了,堆里的实体虽然不会被释放,但是会被当成是垃圾, Java有垃圾回收机制不定时的收取.

描述:类如String[] str = new String[5];
主函数先进栈,在栈用定义了一个str变量,然后为str赋值,但是new String [5]不是一个具体的值,他是一个实体,存在堆中的,在堆中它首先通过new 关键字开辟出一个空间,然后给这个空间赋了一个内存地址,当str想和new String[5]建立联系的时候,就会通过这个内存地址来进行联系,所以当str没有任何引用的时候,str就会立即被释放,但是new String[] 不会立即被释放,而是被当做一个垃圾,在不定时的时间内被回收.

6:什么时候会触发FullGC?

FullGC:完全垃圾回收
如果永久代满了或者是超过了临界值后触发FullGC

7:什么是Java虚拟机?为什么Java被称作是"平台无关的编程语言"?

Java虚拟机是一个可执行Java字节码的虚拟机进程.Java源文件被编译成能被Java虚拟机执行的字节码文件.
Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译.Java虚拟机让这个变为可能.因为它知道底层硬件平台的指令长度和其他特性.

9:说说对象分配规则?

Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题,:给对象分配内存以及回收分配给对象的内存.
对象的内存分配,往大方向上来讲,就是在堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先再TLAB上分配,少数情况下回直接分配再老年代中,分配的规则并不是百分百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关参数的设置.

(1) 对象优先在Eden分配

 大多数情况下,对象在新生代Eden区中分配,当Eden区中没有足够的空间进行分配的时候,虚拟机将进行一次Minor GC.虚拟机提供-XX:+PrintFCDetails这个收集器日志参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况.在实例应用中,内存回收日志一般是打印到文件后通过日志工具进行分析的.
如果对象全部无法放入Survivor空间,那么就只好通过分配担保机制提前转移到老年代中.
新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC 非常频繁,一般回收速度也比较快.
老年代GC(Major gc/Full GC) : 指发生在老年代的GC,出现Major GC ,经常会伴随至少一次的Minor GC.

(2) 大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组.大对象对虚拟机的内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有不少的空间时就提前触发垃圾收集以获取足够的连续空间来'安置'他们.
虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配.这样做的目的是避免在Eden区及两个Survivor区之间发生大量的内存复制(新生代采用复制算法收集内存)

(3) 长期存活的对象将进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别到那些对象应当在新生代,那些对象应当在老年代中.为了做到这点,虚拟机给每个对象定义了一个对象年龄(age)计数器,如果对象在Eden出生并经过第一次Minor GC后任然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设定为1,对象在Survivor区中每’熬过’一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认是15岁),就将会晋升到老年代中,对象晋升老年代的年龄阈值,可以通过-XX:MaxtenuringThreshold设置.

(4)动态对象年龄判定

为了能更好的适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Sirvivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,那么年龄大于或者等于改年龄的对象就可以直接进入老年代,无须等到MaxTenuringthreshold中要求的年龄

(5)空间分配担保

在发生Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果大于,那么Minor GC 可确保是安全的,如果不成立,则虚拟机会查兰HandlePromotionFailure设置值是否允许担保失败.如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则将尝试着进行一次Minor GC ,尽管这些Minor GC 是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这是也要改为进行一次Full GC;

10:描述一下JVM加载class文件的原理机制?

JVM将类加载过程分为三个步骤:装载(load),链接(link)和初始化(Initialize)链接又分为三个步骤:
在这里插入图片描述

  • 装载: 查找并加载类的二进制数据;(加载文件)
  • 链接:
    1.验证:确保被加载类的正确性
    2.准备:为类的静态变量分配内存,并将其初始化为默认值;(静态先走)
    3.解析:吧类中的符号引用转换为直接引用;(再走引用)
  • 初始化:为类的静态变量赋予正确的初始值;(赋初值)

11:说说Java对象创建过程?

1.类加载检查
当虚拟机遇到一条new 指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载
2.分配内存
类加载通过后,接下来分配内存.若Java堆中内存时绝得规整的,使用=="指针碰撞"方式分配内存;如果不是规整的,就从空闲列表==中分配,叫做"空闲列表"方式. 在这里插入图片描述
Java堆内存是否规整,取决于GC收集器算法.
1.“标记-清楚"是不规整的
2.“标记-整理”(也称作"标记-压缩”).复制算法内存时规整的
划分内存时还需要考虑一个问题并发,虚拟机采用两种方式来保证线程安全
1.CAS+失败重试: 虚拟机采用乐观锁CAS配上失败重试的方式保证更新操作的原子性.
2.本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),TLAB为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行分配内存.

12:知道类的生命周期吗?

当我们编写一个Java的源文件后,进过编译会生成一个后缀名为class的文件,这种文件叫做字节码文件,只有 这种字节码文件才能够在java虚拟机中运行,Java类的生命周期就是指一个class文件从加载到卸载的全过程.
在这里插入图片描述
加载

  • 类加载器:

    把一个.class字节码文件加载到内存中(虚拟机内存).JVM中所有的class都是被类加载器(ClassLoader)加载到内存中的.默认采用双亲委派模式,其目的主要是出于安全考虑.

  • 双亲委派

    当某一个特定的类加载器他在接到需要加载类的请求时,这个类会首先查看自己已加载完的类中是否包含这个类,如果有就返回,没有的话就会把加载的任务交给父类加载器加载,依次递归,父类加载器如果完成类加载任务,就返回它,当父类加载器无法完成这个加载任务时,才会不得已自己去加载,这中机制就叫做双亲委派机制.

    类加载过程:
    当一个.class文件要被加载时.如果有自定义类加载器,会尝试先从自定义加载器中检查是否加载过,如果有那就无需加载了,如果没有,就去委托父加载器AppClassLoader中检查是否加载过,如果有那就无需再加载了,如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法.父类中同理会先检查自己是否已经加载过,如果没有再往上.注意这个过程,知道Bootstrap classLoader 之前,都是没有那个加载器自己选择.加载的,如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassOutFoundException.
    链接

  • 验证
    验证.class文件是否符合JVM规范,这个阶段的目的就是保证加载的类是能够被jvm所运行.

  • 准备
    静态成员变量赋值默认值

  • 解析
    将类,方法,属性等符号引用解析为直接引用;常量池中的各种符号引用解析为指针,偏移量等内存地址的直接引用.
    初始化

调用类初始化代码,给静态成员变量赋初始值

  • 通过new关键字实例化对象,访问final变量除外
  • java.lang.reflect对类进行反射调用时
  • 初始化子类的是否,父类首先初始化.
  • 作为程序入口直接运行时(也就是直接调用main方法)
    卸载

在类使用完之后,如果满足下面的情况,类就会被卸载:

  1. 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例.
  2. 加载该类的Classloader已经被回收.
  3. 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法.

如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,Java类的整个生命周期也就结束了.

13:简述Java的对象结构?

Java对象由三个部分组成:对象头,实例数据,对齐填充.
对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码,GC分代年龄,锁标识状态,线程持有的锁,偏向线程ID(一般是占32/64bit).第二部分是指针类型,指向对象的类元数据类型(即对象代表那个类).如果是数组对象,则对象头中还有一部分用来记录数组长度.
实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)

14:如何判断对象可以被回收?

垃圾收集器再做垃圾回收的时候,首先需要判定的就是那些内存时需要被回收的,那些对象是存活的,是不可以被回收的,那些对象是已经死掉了,需要被回收.
一般有两种方法可以来判断:

  • 引用计数器法:为每个对象创建一个计数器,当有对象引用的时候计数器+1,当引用被释放的时候计数器-1,当计数器为0的时候就可以被回收,它有一个缺点就是不能解决循环引用的问题.
  • 可打性分析算法:从GC ROOTS 开始向下搜索,搜索所走过的路径称为引用链.当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的.
  • 当对象对当前使用这个对象的应用程序变得不可触发的时候.这个对象就可以被回收.

15:JVM的永久代中会发生垃圾回收么?

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC ).如果你仔细的查看垃圾收集器的输出信息,就会发现永久代也是被回收的.这就是为什么正确的永久代大小对避免Full GC 是非常重要的原因.java8中永久代已经移除,新加了一个叫做元数据区的native内存区

16:你知道哪些垃圾回收算法?

  • 标记-清除算法:标记无用的对象,然后进行清除回收.缺点:效率不高,无法清除垃圾碎片.
  • 复制算法: 按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉.缺点:内存使用率不高,只有原来的一半.
  • 标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉边界意外的内存.
  • 分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法.

17:调优命令有哪些?

  • jps: 查询正在运行的JVM进程

  • jstat: jstat -gcutil 80801(java进程号) 2000 实时显示本地或远程JVM进程中类装载,内存,垃圾收集,JIT编译等数据

  • jinfo: jinfo 42493 查询当前运行着的JVM属性和参数的值

  • jmap:用于显示当前Java堆和永久代的详细信息

  • jstck: 用于生成当前JVM的所有线程快照,线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的原因.

  • top: 通过top -Hp 23344 可以查看该进程下各个线程的CPU使用情况

  • pidstat:实时查看一个进程的CPU使用情况及上下文切换情况.

  • vmstat:查看总体的CPU使用情况
    sudo vmstat 2 3
    参数2 表示每一个2秒显示下一个结果,3表示显示结果的数目
    cs列表示每秒上下文切换次数,us表示用户CPU时间.

  • jmap -heap 29532 查询某一实例jvm配置

18:常见的调优工具有哪些?

JDK自带了一些监控工具,都位于JDK的bin目录下,其中最常用的是jconsole和jvisualvm 这两个视图监控工具

  • jconsole:用于对JVM中的内存,线程和类进行监控
  • jvisualvm: JDK自带的全能分析工具
    可以分析:内存快照,线程快照,程序死锁,监控内存的变化,gc变化等.

19:Minor GC 与Full GC分别在什么时候发生?

新生代内存不够时发生Minor GC 也称 YGC,JVM内存不足时发生Fulle GC 也称 F GC.
导致Full GC 原因:

  1. 年老代被写满
    调用时尽量让对象在新生代GC时被回收,让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象.

  2. 持久代 Pemanet Generation 空间不足
    增加Perm Gen 空间,避免太多静态对象,控制好新生代和旧生代的比例

  3. System.gc() 被显示调用
    垃圾回收不要手动触发,尽量依靠JVM自身的机制来触发.

20:你知道哪些JVM性能调优参数?

-Xms2g:初始化推大小为 2g;
-Xmx2g:堆最大内存为 2g;
-XX:NewRatio=4:设置年轻的和老年代的内存比例为 1:4;
-XX:SurvivorRatio=8:设置新生代 Eden 和 Survivor 比例为 8:2;
–XX:+UseParNewGC:指定使用 ParNew + Serial Old 垃圾回收器组合;
-XX:+UseParallelOldGC:指定使用 ParNew + ParNew Old 垃圾回收器组合;
-XX:+UseConcMarkSweepGC:指定使用 CMS + Serial Old 垃圾回收器组合;
-XX:+PrintGC:开启打印 gc 信息;
-XX:+PrintGCDetails:打印 gc 详细信息。

21:对象一定分配在堆中吗?有没有了解逃逸分析技术?

逃逸分析:是一种可以 有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法.(是一种算法).通过逃逸分析,Java Hotspot 编译期能够分析出一个新的对象的引用的使用范围,从而决定是否要将这个对象分配到堆上.逃逸通俗来讲就是如果一个对象的指针被多个方法或者线程引用.我们就说这个对象的指针发生了逃逸
```

 public class EscapeAnalysisTest {
    //StringBuilder可能被其他方法改变,逃逸到了方法外部。
    public StringBuilder  escape(String a, String b) {
        StringBuilder str = new StringBuilder();
        str.append(a);
        str.append(b);
        return str;
    }
 
    //不直接返回StringBuffer,不发生逃逸
    public String notEscape(String a, String b) {
        StringBuilder str = new StringBuilder();
        str.append(a);
        str.append(b);
        return str.toString();
    }
}
```

因为StringBuilder 可能会被其他方法调用,因此他可以逃逸到方法的外部,也就是escape方法中定义的str 可以逃逸 ,而方法notEscape中其他人无法调用StringBuilder的对象.

逃逸分析的好处

  1. 栈上分配,可以降低垃圾收集器运行的频率.
  2. 同步消除,如果发现某个对象只能通过一个线程访问,那么在这个对象上的操作可以不需要同步.
  3. 标量替换.把对象分解成一个个基本类型.并且内存分配不在分配到堆上,而是分配在 栈上.这样的好处有:可以减少内存使用,因为不用生成对象头,程序内存回收率高,并且GC频率也会减少.

22:虚拟机为什么使用元空间替换了永久代?

在之前的版本中,字符串常量池存在于永久代中,在大量使用字符串的情况下,非常容易出现OOM的异常.此外,JVM加载的class的总数,方法大小等都很难确定,因此对永久代大小的指定难以确定.太小的永久代容易导致永久代内存溢出,太大的永久代则容易导致虚拟机内存紧张.
元空间不再与堆连续,而是直接存在于本地内存中,也就是机器的内存.机器的内存有多大, 元空间的野心就会有多大.

23:什么是Stop the World? 什么是OopMap? 什么是安全点?

进行垃圾回收的过程中,会涉及对象的移动.为了保证对象应用引用更新的正确性,必须暂停所有的用户线程,像这样的停顿,虚拟机设计者形象描述为 Stop The World.
在HotSpot中,有个数据结构(映射表)称为OopMap.一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,记录到OopMap中.在即时编译过程中,也会在特定的位置生成OopMap,记录下栈上和寄存器里那些位置是引用.

这些特定的位置主要在:

  1. 循环的末尾(非counted循环)
  2. 方法临返回前/调用方法的call指令后
  3. 可能抛异常的位置
    这些位置就叫做安全点(safepoint) , 用户程序执行时并非在代码指令流的任意位置都能够在停顿下来开始垃圾收集,而是必须是执行到安全点才能够暂停.

24:说一下JVM的主要组成部分以及作用?

JVM包括类加载子系统,堆,方法区,栈,本地方法栈,程序计数器,直接内存,垃圾回收器,执行引擎.

1:类加载子系统

类加载子系统负责加载class信息,加载的类信息存放于方法区中

2:直接内存

直接内存是在Java堆外的,直接向系统申请的内存空间.访问直接内存的速度会由于Java堆.处于性能的考虑,读写频繁的场合可能考虑直接使用内存

3:垃圾回收器

垃圾回收器可以对堆,方法区,直接内存进行回收

4:执行引擎

执行引擎负责执行虚拟机的字节码,虚拟机会使用即时编译技术将方法编译成机器码后再执行

25:什么是指针碰撞?

一般情况下,JVM的对象都放在堆内存中(发生逃逸分析的除外),当类加载检查通过后,Java 虚拟机开始为新生对象分配内存.如果Java堆中内存时绝对规整的,所有被使用过的内存都被放到一边,空闲的内存放到另外一边,中间放着一个指针作为分界点的指示器.所分配内存仅仅是吧那个指针向空闲空间方向挪动一段与对象大小相等的实例,这种分配方式就是指针碰撞.

26:什么是空闲列表?

如果Java堆内存中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,不可以进行指针碰撞啦,虚拟机必须维护一个列表,记录那些内存是可用的,在分配的时候从列表找到一块大的空间分配给对象实例,并更新列表上的记录,这种分配方式就是空闲列表

对象创建在虚拟机中是非常频繁的行为,可能存在线性安全问题.如果一个线程正在给A对象分配内存,指针还没有来的及修改,同时另一个为B对象分配内存的线程,仍引用这之前的指针方向,这样就会出问题.

27:什么是TLAB?

可以把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java堆中预先分配一小块内存,这就是TLAB(Thread Local Allocation Buffer,本地线程分配缓存).虚拟机通过-XX:UseTLAB设定它的.

28:对象头具体都包含哪些内容?

HosSopt虚拟机的对象头(Object Header)包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode),GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等等,这部分数据在32位和64位的虚拟机中分别为32个和64个Bits,官方称"Mark Word".
对象头的另外一部分是类型指针,即时对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例.并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说查找对象的元数据信息并不一定要经过对象本身,另外,如果对象是一个java数组,那么在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小.
锁的状态位与是否偏向锁对应到唯一的锁状态
锁的状态总共有四种:无所状态,偏向锁,轻量级锁和重量级锁.
偏向锁: (启动参数-XX:+UseBiasedLocking, 这是JDK1.6的默认值)为了让线程获得锁的代价更低而引入了偏向锁,那么,“锁总是同一个线程的持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程.那么只需要在锁第一次被拥有的时候,记录下偏向线程ID,这样偏向线程就一直持有着锁,直到竞争发生才释放锁.以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步,退出同步也,无需每次加锁解锁都去CAS更新对象头,如果不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候需要锁膨胀为轻量级锁,才能保证线程间公平竞争锁.乐观锁.

==轻量级锁:==还没有涉及到内核.获取轻量锁的过程与偏向锁不同,竞争锁的线程首先需要拷贝对象头中的Mark Word 到帧栈的锁记录中,拷贝成功后使用CAS操作尝试将对象的Mark Word 更新为指向当前线程的指针,如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,如果更新失败了,那么意味着有多个线程在竞争.

  • 轻量级锁每次同步推出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁
  • 每次进入退出同步块都需要CAS更新对象头
  • 争夺轻量级锁失败时,自旋尝试抢占锁

==重量级锁:==有涉及到内核.消耗的资源比较高.重量级锁的加锁.解锁过程和轻量级锁差不多,区别是:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不适用自旋锁,不会那么消耗CPU,所以轻量级锁适合用在同步块执行时间长的情况下,随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)JDK1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁.

29:说一下JVM有哪些垃圾回收器?

1:串行垃圾回收器 Serial串行

整体过程比较简单,就像踢足球一样,需要GC时,直接暂停,GC完了再继续,这个垃圾回收器是比较早起的回收器,只有一个线程执行GC.在多CPU架构下,性能就会下降严重.只适用与几十兆的内存空间.

2:并行垃圾回收器 Parallel并行

在串行基础上增加多线程GC,PS(Parallel Scavenge)+PO(Parallel Old) 这种组合是JDK1.8默认是垃圾回收器.在多CPU架构下性能会比Serial高很多.随着内存越来越大,多个GC线程还是不够忙活的,

3:CMS收集器 Concurrent Mark Sweep

核心思想就是将STW打散,让一部分GC线程与用户线程并发执行.整个GC过程分为4个阶段:

  1. 初始标记阶段:STW只标记出跟对象直接引用的对象.
  2. 并发标记阶段: 继续标记其他对象,与应用程序是并发执行的.
  3. 重新标记阶段: STW对并发执行阶段的对象进行重新标记.
  4. 并发清除阶段:并行.将产生的垃圾清除.清除过程中,应用程序又会不断产生新的垃圾.叫浮动垃圾,这些垃圾就要留到下一个GC过程中清除.
    CMS垃圾回收器是分代算法向不分代算法过度的一个过度阶段

4:G1垃圾收集器

它的内存模型是实际上不分代逻辑上还是分代的,对于堆内存就不再分新生代和老年代,而是划分为一个一个的小内存块,叫做Region,每个Region隶属于不同的年代.
GC分为4个阶段:

  1. 初始标记: 标记处GC Roots 直接引用的对象.STW
  2. 标记Region: 通过RSet标记出上一个阶段标记的Region引用到的Old区的Region.并发的,没有STW.
  3. 并发标记:跟CMS的步骤差不多的.只是遍历的范围不再是整个Old区,而只需要遍历第二步标记出来的Region
  4. 重新标记:跟CMS中的重新标记过程上差不多.
  5. 垃圾清理:与CMS不同的是,G1可以采用拷贝算法,直接将整个Region中的对象拷贝到另一个Region.而这个阶段,G1只选择垃圾较多的Region来清理,并不是完全清理.

30:如何选择垃圾收集器?

  • 单CPU或内存较小,使用Serial收集器.
  • 多CPU,关注吞吐量,后台运算而不需要太多交互的分析任务,使用Paraller Scavenge 收集器
  • 多CPU,关注服务的响应速度,希望系统停顿时间尽可能短,使用ParNew + CMS 收集器组合
  • 服务器内存较大,可以使用G1收集器.

31:什么是类加载器?

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析,和初始化,最终形成被虚拟机直接使用的java模型,这就是虚拟机的类加载机制.

在这里插入图片描述
加载过程阶段
在加载阶段,虚拟机需要完成以下三件事:

  1. 通过类的全限定名称来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结果转化为方法区运行时的数据结构.
  3. 在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问接口

知道哪些类加载器?

  1. 启动类加载器(Bootstrap ClassLoader): 最顶层的加载类,这个类加载器是由C++语言实现的,是虚拟机自身的一部分.负责将存在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的类库加载到虚拟机内存中.
  2. 扩展类加载器(Extension ClassLoader):它负责加载<JAVA_HONE>\lib\ext目录中的,或者被java.util.dirs系统变量所指定的路径中所有类库,开发者可以直接使用扩展类加载器.
  3. 应用程序类加载器(Application ClassLoader):面向我们用户的加载器,负责加载当前应用classpath下的所有的jar包和类

双亲委派模型
工作过程: 每个类都有自己对应的类加载器,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(他的搜索范围没有找到所需的类)时,子加载器才会尝试自己去加载.

双亲委派的好处:保证了java程序的稳定性,可以避免类的重复加载,也保证了java核心的API不被篡改

连接阶段

验证
为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,验证阶段为:文件格式验证,元数据验证,字节码验证,符合引用验证.

准备
为类变量分配内存并设置改类变量的默认初始化,即零值.
这个不包含用final修饰的staic,因为final在编译的时候就会分配了,准备阶段就会显示初始化.
这个不会为实例变量进行初始化,类变量会分配再方法区,实例变量就会随着对象一起分配到java的堆中
解析 初始化
在准备阶段,变量已经赋过一次系统要求的初始化,初始化执行构造器的()方法过程.

32:什么是tomcat类加载机制?

Tomcat的类加载机制是违反了双亲委派原则的,对于一些未加载的非基础类(Object,String)等,各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托.

对于JVM来说:
因此 ,按照这个过程可以想到,如果同样在Classpath指定的目录中和自己工作目录中存放相同的class,会优先加载CLASSPATH目录中的文件.

三:多线程& 并发篇?

1:说说JAVA实现多线程有几种方法?

  1. 集成Thread类
  2. 实现Runable 接口,重写run方法
  3. 实现Callable 接口,重写call方法(有返回值)
  4. 使用线程池(有返回值)

2:如何停止一个正在运行的线程?

  1. 使用退出标志,使线程正常退出.也就是当run方法完成后线程终止
  2. 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法.
  3. 使用interrupt方法中断线程/

3:notify()和notifyAll()有什么区别?

先解释两个概念.

  • 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到该对象的等待池,等待池中的线程不会去竞争该对象的锁.
  • 锁池:只有获取了对象的锁,线程才能执行对象的synchronized代码,对象的锁每次只有一个线程可以获得,其他线程只能再锁池中等待.

notify()随机唤醒对象的等待池中的一个线程,进入锁池, notifyAll()唤醒对象的等待池中的所有线程,进入锁池

4:sleep()和wait()有什么区别?

  • sleep是Thread的方法,导致此线程暂停执行指定时间,把执行机会给其他的线程,但是监控状态依然保持,到时候就会自动恢复.
  • 调用sleep方法不会释放对象锁.
  • wait是Object类中的方法,对此对象调用wait方法导致本地线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify或notifyAll方法后本线程才会进入对象锁定池准备获得对象锁进入运行状态
  • wait会释放锁.

5:volatile 是什么?可以保证有序性吗?

volatile可以保证有序性,多线程通过抢占时间片来执行自己的代码片,所有我们会感觉到线程是同时执行完的,除了引入了时间片以外,由于处理器优化和指定重排等,CPU还可能对输入代码进行乱序执行,比如我们拿到数据要执行写库,查询,删除这三个操作,这就会可能要设计到有序性的问题了,
volatile可以禁止指令重排序,这就保证了代码的程序会严格按照代码的先后顺序执行,这就保证了有序性.被volatile修饰的变量的操作,会严格按照代码顺序执行,就是说当代码执行到volatile修饰的变量时,其前面的代码一定执行完毕,后面的代码一定没有执行.

volatile关键字禁止指定重排序有两层意思

  1. 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行
  2. 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行.

6:Thread 类中的start()和run()方法有什么区别?

  • 通过start()方法来启动一个线程,此时线程处于就绪状态,可以被jvm来调度执行,在调度过程中,jvm通过调用线程类的run()方法来完成实际的业务逻辑,当run()方法结束后,此线程就会终止,所以通过start()方法可以达到多线程的目的.
  • 如果直接调用线程类的run()方法,会被当做一个普通的函数调用,程序中任然只有主线程这一个线程,即start()方法呢能够异步的调用run().法,但是直接调用run()方法却是同步的,无法达到多线程的目的.

7:为什么wait,notify和notifyAll这些方法不再thread类里面?

  1. wait和notify不仅仅是普通方法或同步工具,更重要的是他们是Java中两个线程之间的通信机制.对语言设计者而言,如果不能通过Java关键字(例如synchronized)实现通信此机制,同时又要确保这个机制对每个对象可用,那么Object类则是合理的声明位置.记住同步和等待通知是两个不同的领域,不要把他们看成是相同的或相关的.同步是提供互斥并确保Java类的线程安全,而wait和notify是两个线程之间的通信机制.
  2. 每个对象都可上锁,这是在Object类而不是Thread类中生命wait和notify的另一个原因.
  3. 在Java 中,为了进入代码的临界区,线程需要锁定并等待锁,他们不知道那些线程持有锁,而只是知道锁被某个线程持有,并且需要等待以取得锁,而不是去了解那个线程在同步块内,并请求他们释放锁,
  4. Java是基于Hoare的监视器的思想,在Java中,所有对象都有一个监视器.

8:为什么wait和notify方法要在同步块中调用?

  1. 只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法.
  2. 如果不这么做,代码会抛出lllegaMonitorStateException异常,
  3. 为了避免wait和notify之前产生竞态条件

wait()方法强制当前线程释放对象锁,这意味着在调用某对象的wait方法之前,当前线程必须已经获得该对象的锁,因此,线程必须在某一个对象的同步方法或同步代码块中才能调用改对象的wait()方法.

在调用对象的notify()和notifyAll()方法之前,调用线程必须已经得到该对象的锁,因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法.

调用wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行,调用notify()或notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程,“特殊状态已经被设置”.这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量).

9:Java中的interrupted和isinterrupted方法的区别?

interrupt方法用于中断线程.调用该方法的线程的状态为将被置为"中断"状态.
注意: 线程中断仅仅是置线程的中断状态位,不会停止线程.需要用户自己去监视线程的状态位并做处理.支持线程中断的方法(也就是线程中断后会抛出interruptedException 方法)就是在监视线程的中断状态,一旦线程的中断状态被置为"中断状态",就会抛出中断异常.
interrupted查询当前线程的中断状态,并且清楚原状态.如果一个线程被中断了,第一次调用interrupted则返回true,第二次和后面的就返回false了.
isInterrupted仅仅是查询当前线程的中断状态.

10:Java中的synchronized和ReentrantLock有什么不同?

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现.
  2. sunchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁.
  3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
  4. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到
  5. Lock可以提高多个线程进行读操作的效率.
    ReentrantLock相比synchronized,增加了一些高级的功能.但也有一定缺陷:在ReentantLock类中定义了很多方法,比如:
  6. isFair() 判断锁是否是公平锁
  7. isLocked() 判断锁是否被任何线程获取了
  8. isHeldByCurrentThread() 判断锁是否被当前线程获取了
  9. hasQueuedThreads() 判断是否有线程在等待该锁

1.两者在锁的相关概念上区别?

(1): 可中断锁

顾名思义就是可以相应中断的锁,在Java中,synchronized就不是可中断锁,而Lock就是可中断锁,如果某一个线程A正在执行锁中的代码,另一个线程B正在等待获取该锁,可能由于等待时间过长,线程C就不想等待了,像先处理其他的事,我们就可以让它中断自己或者是在别的线程中中断它,这就是可中断锁,lockInterruptibly()体现了Lock的中断性.

(2): 公平锁

公平锁就是尽量以请求锁的顺序来获取锁,比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁(并不是绝对的)这种就是公平锁.非公平锁即无法保证锁的获取是按照请求锁的顺序进行的,这样就导致了一些线程可能永远无法获得锁.synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序,ReentrantLock可以设置成公平锁.

(3): 读写锁

读写锁将对一个资源的访问分文2个阶段,一个读锁一个写锁.正因为有了读写锁,才使得多个线程之前的读操作可以并发进行.不需要同步,而写操作需要同步进行,提高了效率,ReadWriteLock就是读写锁,他是接口,ReentrantReadWriteLock实现了这个接口.

(4): 绑定多个条件

一个ReentrantLock对象可以同时绑定多个Condition对象,而在synchronized中,锁的wait()和nitofy()和nitifyAll()可以实现一个隐含的条件,如果要和多个条件关联的时候,就不得不额外的添加锁,而ReentrantLock则无须这么做,只需要多次调用newCondition()方法即可.

11:有三个线程T1,T2,T3如何保证顺序执行?

使用线程类的join()方法

public class JoinTest {
 
    // 1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
 
    public static void main(String[] args) {
 
        final Thread t1 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                System.out.println("t1");
            }
        });
        final Thread t2 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                try {
                    // 引用t1线程,等待t1线程执行完
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {
 
            @Override
            public void run() {
                try {
                    // 引用t2线程,等待t2线程执行完
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t3.start();//这里三个线程的启动顺序可以任意,大家可以试下!
        t2.start();
        t1.start();
    }
}

12:SynchronizedMap和ConcurrentHashMap有什么区别?

SynchronizedMap实现上在调用Map的所有方法,对Mao进行了同步

ConcurrentHashMap是对要操作的桶加锁,而不是整个加锁,所以ConcurrentHashMap在性能以及安全性方面更加有优势.

13:什么是线程安全?

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的.
当多个线程访问某个方法时,不算通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的,如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的.

14:Thread类中的yield方法有什么作用?

使当前线程从执行状(运行状态)变为可执行态(就绪状态).
当前线程到了就绪状态,那么接下来哪个线程会从就绪状态编程执行状态呢?可能是当前线程,也可能是其他线程,看系统的分配了.

15:Java线程中submit()和execute()方法有什么区别?

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中,而submit()方法返回有计算结构的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其他线程类向ThreadPoolExecutor和ScheduledThreadPollExecutor都有这些方法.

16:说说对synchronized关键字的了解?

synchronized是用来加锁的,可以对一个对象,类去加锁,锁之间会有互斥,有一个人加了锁,另一个人就必须等待锁释放了才能进来
在这里插入图片描述
synchronized锁的原理是什么?
首先synchronized加锁,他会执行一个monitorenter指令,释放锁的时候会执行一个monitorexit指令.每个对象都有一个关联的monitor,如果一要对一个对象加锁,就必须先获取这个对象管理的monitor的lock锁.如果一个线程运行到synchronized代码的话,就会获取这个对象的monitor锁,然后计数器就会加1,然后第二次运行到synchronized代码的话,会再次获取这个对象的monitor锁,这个就是重入锁了,然后计数器会加1.变成2.那么这个时候,其他的线程运行到第一次synchronized代码的话,会发现这个对象的monitor锁的计数器是大于0的,然后就会进入block阻塞状态,什么都干不了,等待获取锁,如果第一个线程出了synchronized修饰的代码块的话,就会有一个monitorexit指令,此时,在底层获取锁的线程就会对这个对象的monitor计数器减1,如果有多次重入加锁,就会对应多次减1,知道最后,计数器是0.然后后面block阻塞的线程,会再次尝试获取锁,但是只有一个线程可以获取锁.这就是synchronized锁的底层实现原理.

17:说说自己是怎么使用synchronized关键字?

代码块加synchronized

18:Vector是一个线程安全类吗?

单线程下是线程安全的, 多线程下则可能是不安全的.

19:volatile关键字的作用?

  1. volatile 关键字是防止在共享的空间发生读取的错误.只保证其可见性,不保证原子性;使用volatile指每次从内存中读取数据,而不是从编辑器优化后的缓存中读取数据,简单来讲就是防止编译器优化.
  2. 在单任务环境中,如果在两次读取变量之间不改变变量的值,编译期就会发生优化,会将RAM中的值赋值到寄存器中;由于访问寄存器的效率要高于RAM,所以在需要读取变量时,直接从寄存器中获取变量的值,而不是从RAM中.
  3. 在多任务的环境中,虽然在两次读取变量之前不变该变量的值,在一些情况下变量的值还是会发生变化,比如在发生中断程序或者有其他的线程.这时候如果编译期优化,依旧从寄存器中获取变量的值,修改的值就得不到及时的响应.
  4. 要想防止编译期优化.就需要在声明变量时加volatile关键字,加关键字后,加载RAM中读取变量的值,而不是直接在寄存器中取值.

20:常用的线程池有哪些?

  1. newSingleThreadExecutor: 创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行.
  2. newFixedThreadPool: 创建固定大小的线程池,每次提交一个任务就创建一个线程,知道线程达到线程池的最大大小.
  3. newCachedThreadPool: 创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小.
  4. newScheduledThreadPool: 创建一个大小无线的线程池,此线程池支持定时以及周期性进行任务的需求.
  5. newSingleThreadExecutor: 创建一个单线程的线程池.此线程池支持定时以及周期性执行任务的需求.

21:简述一个你对线程池的理解?

在没有线程池概念之前,我们要使用线程必须先通过创一个Thread类完成线程的创建,并调用start()方法开启,在线程执行完会将线程销毁,而线程资源是很宝贵的,创建和销毁线程会造成资源的浪费.而线程池是将创建的线程存储到一个池中,在需要使用时从池中去拿,使用完之后再将线程归还到池中,下一次接着使用.

22:Java程序是如何执行的?

先把Java代码编译成字节码,也就是把.java类型的文件编译成.class类型的文件.这个过程的大致执行 流程:Java源代码->词法分析器->语法分析器->语义分析器->字节码生成器->最终生成字节码,其中任何一个节点执行失败就会导致编译失败:吧class文件放置到java虚拟机,这个虚拟机通常指的是Oracle官方自带的Hostpot JVM;Java 虚拟机使用类加载器(Class Loader ) 装载class文件,类加载完成之后,会进行字节码校验,字节码校验通过之后JVM解释器会把字节码翻译成机器码交由操作系统执行.但不是所有代码都是解释执行的,JVM对此作了优化,比如,以Hostpot虚拟机来说,它本身提供了JIT也就是我们通常所说的动态编译期,它能够在运行时将热点代码编译为机器码,这个时候字节码就变成了编译执行.

23:锁的优化机制了解吗?

解决并发问题

24:说说线程和进程的区别?

  1. 进程是资源分配的最小单位,线程是程序执行的最小单位.
  2. 进程有自己的独立地址空间,没启动一个进行,系统就会为它分配地址空间,建立数据表来维护代码段,堆栈段,和数据段.这种操作是非常昂贵的,而线程是共享进程中的数据,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多.
  3. 线程之间的通信更方便,同一个进程下的线程共享全局变量,静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行,不过如何处理好同步与互斥是编写多线程程序的难点?
  4. 但是多线程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因此进程有自己独立的地址空间.

25:产生死锁的四个必要条件?

  1. 互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,知道该进程访问结束
  2. 占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源.
  3. 不可抢占: 别人已经占有了某项资源,你不能因为自己也需要资源该资源,就去吧别人的资源抢过来
  4. 循环等待:存在一个进程链,使得每个进程占有下一个进程所需的至少一种资源

当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,他们所持有的资源也无法释放.这样会导致CPU的吞吐量下降,所以索索情况是会浪费系统资源和影响计算机的使用性能的.那么,解决死锁问题就是相当有必要的了.

26:如何避免死锁?

  1. 加锁顺序(线程按照一定的顺序加锁)
    当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生.如果能够确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生
  2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
    在尝试获取锁的时候加一个超时时间,当一个线程在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁的请求,若一个线程没有在给定的时限内成功获得所需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试.这段随机的等待时间让其他线程有机会尝试获取相同的这些锁.并且让该应用在没有获得锁的时候可以继续运行
  3. 死锁检测
    死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景.
    每当一个线程获得了锁,会在线程和锁相关的数据结构中(map,graph等等)将其记下,除此之外,每当有线程请求锁,也需要记录在这个数据结构中,
    当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生(例如:线程A请求锁2,但是锁2这个时候被线程B持有,这时线程B就可以检查线程A当前锁持有的锁.如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁2;线程B拥有锁2,请求锁1))
    当然,死锁一般要比两个线程互相持有对方的锁这种情况要复杂的多.线程A等待线程B,线程B等待线程C,线程C等待线程A.线程A为了检测死锁,他需要递进地检测所有被B请求的锁.从线程B锁请求的锁开始,线程A先找到线程C,然后又找到线程A.发生请求的锁是被自己持有的,这是他就知道发生了死锁.

27:线程池核心线程数怎么设置呢?

线程的平均工作时间所占比例越高,就需要越少的线程;
线程的平均等待时间所占比例越高,就需要越多的线程;

28:Java线程池中队列常用类型有哪些?

1:linkedBlockingDeque(链表同步阻塞对列)
LinkedBlockingDeque是基于链表的双端阻塞对列.线程安全,元素不用允许为null.一般多用于生产者消费者模式.
2:ArrayBlockingQueue(数组同步阻塞对列)
当我们使用ArrayBlockingQueue时,让runable的数据小于等于线程池的大小与对列的和时,会先将部分runable存到线程池中立刻执行,将剩余的runable存入阻塞对列中.当runable的数量大于线程池的数量与阻塞对列之和时会将线程池+阻塞对列数量的runable执行,并抛出RejectedExecutionException,使用LinkedBlockingDeque时同理
3:SynchronousQueue(同步阻塞对列)
当我们使用同步阻塞队列时,同步阻塞对列没有大小,当runable的数量小于等于线程池的大小时每执行一次threadPoolExecutor.execute(runable);线程池的大小会加1,当超过线程池的大小时不再执行多余的任务并抛出RejectedExecutionExecption异常.

29:线程安全需要保证几个基本特征?

  1. ==原子性: == 简单来说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现.
  2. 可见性: 是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到朱内存上,volatile就是负责保证可见性的.
  3. 有序性: 是保证线程内串行语义,避免指令重排等.

30:说一下线程之间是如何通信的?

1:通过共享变量,线程之间通过该变量进行协作通信. 通过wait(),notify(),notifyAll()方法
2:通过对列(本质上也是线程间共享同一块内存) 来实现消费者和生产者的模式来进行通信.

31:CAS的原理是什么?

Compare-And-Swap 比较并交换,是一条CPU并发原语
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的,CAS在Java中的提现是sun.misc.Unsafe类中的哥哥方法,这是一种完全依赖于硬件的功能,通过它实现原子操作.不会造成数据不一致问题.

32:CAS有什么缺点吗?

  1. ABA问题
    它只能拿着当前结果值和预期的值进行比较, 那么如果初始结果为100, 但被A改成了200,又被B改成了100, 这是它并不能检测出来初试结果已经被修改.
  2. 自旋时间过长
    由于单次CAS不一定能执行成功,所以CAS往往是配合着循环来实现的,有的时候甚至是死循环,不停的进行重试,直到线程竞争不激烈的时候,才能修改成功,但是我们实际 的场景中,高并发是很正常的,在这种场景下,就可能导致CAS一直都操作不成功,这样的话,循环时间就会越来越长.而且在此期间,CPU资源也是一直在被消耗的.这会对性能产生很大的影响.
  3. 范围不能灵活控制
    通常我们在执行CAS的时候,是针对某一个,而不是多个共享变量的,这个变量可能是Integer 类型,也可能是Long类型,也可能是对象类型等等,但是我们不能针对多个共享变量同时进行CAS操作,因为这多个变量之间是独立的,简单的吧原子操作组合到一起,并不具备原子性.因此如果想对多个对象同时进行CAS操作并想保证线程安全的话,是比较困难的.相比之下,使用线程安全技术,调整线程安全的范围就变得非常容易,比如可以用synchronized关键字,如果想把更多的代码加锁,那么只需要把更多的代码放到同步代码块中即可.

34:说说ThreadLocal原理?

ThreadLoacl底层是通过ThreadLocalMap这个静态内部类来存储数据的,ThreadLocalMap就是一个键值对的map,它的底层是Entry对象数组,Entry对象中存放的键是ThreadLocal对象,值是Object类型的具体存储内容.

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
  public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

35:线程池原理知道吗?以及核心参数?

核心参数

  1. corePoolSize 核心线程数
  2. maximumPoolSIze 最大线程数
  3. keepAliveTime 空闲线程存活时间
  4. unit 对应keepAliveTime 的计量单位
  5. workQueue 工作对列
  6. threadFactory 线程工厂,用户创建线程可以指定线程名,是否为daemon 线程等
  7. handler 拒绝策略

原理

为了避免线程频繁的创建.销毁所带来的资源开销,就有了线程池,当有任务提交到线程池后具体的任务执行流程如下
1:判断下当前线程池存活的线程数是否低于corePollSize,如果低于corePoolSize就创建线程来执行;如果达到corePoolSize就把任务放入到工作对列里等待任务调度执行.
2:当工作对列存满任务,且存活线程数大于corePoolSize但是低于maximumPoolSize时,也通过创建线程来执行.如果线程数达到maximumPoolSize了,继续提交任务就交由RejectedExecutionHandler来执行拒绝操作.
3:如果线程池中线程数超过corePoolSize,且线程空闲下来时,超过空闲keepAliveTime就会被销毁,知道线程数达到corePoolSize.如果设置allowCoreThreadTimeOut(true)那么超过keepAliveTime时,低于corePoolSize数量的线程空闲时间达到keepAliveTime也会销毁.

在这里插入图片描述
在这里插入图片描述

36:线程池的拒绝策略有哪些?

  1. ==AbortPolicy == 这种拒绝策略在拒绝任务时,会直接抛出一个类型为RejectedExecutionException的RuntimeException,让你感知到任务被拒绝了,然后就可以根据业务逻辑选择重试或者放弃提交等策略.
  2. DiscardPolicy 当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为在提交的时候根本不知道该任务会被丢弃,可能在成数据丢失.
  3. DiscardOldestPolicy 如果线程池没有被关闭且没有能力执行,则会丢弃任务对列中的头结点,通常是存活时间最长的任务,这中拒绝策略丢弃的不是最新提交的,而是最后提交的也就是存活时间最长的任务,这样就可以腾出空间给新提交的任务,但是也存在一定的数据丢失风险.
  4. CallerRunsPolicy 当有新任务提交后,如果线程池没被关闭且没有能力执行,则吧这个任务交于提交任务的线程执行,也就是谁提交的任务,谁就负责去执行任务.

37:说说你对JMM内存模型的理解?为什么需要JMM?

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了改线程中用到的变量的朱内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存.不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和朱内存之间进行数据同步进行.而JMM就作用于工作内存和朱内存之间数据同步过程.他规定了如何做数据同步以及什么时候做数据同步.

JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致.编译器会对代码指令重排序,处理器会对代码乱序执行等带来问题
Java 中提供了一系列和并发处理相关的关键字,比如synchronized,valatile,final,concurren包等,其实这些就是Java内存模型封装了底层的实现后提供出来的一些关键字.

38:多线程有什么用?

提高程序的执行效率.

39:说说CycliBarrier和CountDownlatch的区别?

  • CyclicBarrier: 一种同步辅助工具,允许一组线程都等待彼此到达一个共同的障碍点,cyclicbarrier在涉及固定大小的线程组的程序中很有用,这些线程偶尔必须等待对方,这个屏障被称为循环的,因为它可以在等待的线程被释放后重新使用
  • CountDownLatch: 一种同步辅助工具,允许一个或这个线程等待一组在其他线程中执行的操作完成,倒计时锁存器用给定的计数初始化.同于调用倒计时方法,await方法阻塞直到当前技术达到零,之后释放所有等待线程,并立即返回任何后续的await调用.这是一种一次性的现象-计数无法重置.如果需要重置计数的版本,请考虑cyclicBarrier

40:什么是AQS?

AQS是AbustactQueuedSynchronized的简称,它是一个Java提供的底层同步工具类,用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态.
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,ynchronousQueue,Future Task 等等皆是基于AQS的.
AQS支持两种同步方式:
1:独占式
2:共享式

这样方便使用者实现不同类型的同步组件,独占式如ReentrantLock, 共享式如Semaphore, CoutDownLatch, 组合式的如ReentrantReadWriteLock. 总之,AQS为使用提供了底层支撑,如何组装实现,使用者可以自由发挥,

41:了解Semaphore吗?

semaphore可以用来实现服务的并发限流,主要是用来保护共享资源的,使资源在同一时刻已有一个线程所拥有.

public class SpringbootApplication {
    //定义一个信号量,初始参数为6,代表只允许6个线程同时访问
    final static Semaphore semaphore = new Semaphore(6);
    //定义一个线程任务
    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                //获取许可
                semaphore.acquire();
                System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "获取许可证");
                //假设处理业务逻辑要3秒
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "释放许可证");
                //释放许可
                semaphore.release();
            }
        }
    }

42:什么是Callable和Future?

Callable接口类似于Runable,从名字就可以看出来了,但是Runable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到 异步执行任务的返回值,可以认为是带有回调的Runable.
Future接口表示异步任务,是还没有完成的任务给出的未来结果,所以说Callable用于产生结果,Future用于获取结果.

43:什么是阻塞对列?阻塞对列的实现原理是什么?如何使用阻塞对列来实现生产者,消费者模型?

什么是阻塞对列?

阻塞对列是一个支持阻塞的插入和移除的对列.

  • 支持阻塞的插入方法: 意思是方对列满时,对列会阻塞插入元素的线程,知道对列不满
  • 支出阻塞的移除方法: 意思是对列为空时,获取元素(同时移除元素)的线程会被阻塞,等到对列变为非空

原理?

如果对列是空的,消费者会一直等待,当生产者添加元素时,消费者是如何知道当前对列有元素的呢?
使用通知模式实现.所谓通知模式,就是当生产者往满的对列添加元素时会阻塞住生产者,当消费者消费了一个对列中的元素后,会通知生产者当前对列可用.

如何实现?

生产者是往对列里添加元素的线程.消费者是从对列里拿元素的线程.阻塞对列就是生产者存放元素的容器,而消费者也只从容器里拿元素.

44:什么是多线程中的上下文切换?

即使是单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制.时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时间是同时执行的,时间片一般是几十毫秒(ms)上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行,CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片会切换到下一个任务.但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载就是一次上下文切换.

45:什么是Daemon线程?它有什么意义?

所谓后台(daemon)线程,是指在程序运行的时候在后台提佛那个一种通用服务的线程,并且这个线程并不属于程序中不可或缺的部分.因此,当所有的非后台进行结束时,程序也就终止了,同时会杀死进程中的所有后台进程.反过来说,只要有任何非后台进行还在运行,程序就不会终止.必须在线程启动之前调用setDaemon()方法,才能把它设置为后台进程.注意:后台进程在不执行finallly字句的情况下就会终止其run()方法.
比如:JVM的垃圾回收线程就是Daemon()线程,Finalizer也是守护线程.

46:乐观锁和悲观锁的理解以及如何实现,有哪些实现方式?

乐观锁

顾名思义就是很乐观,每次去拿数据的时候都会任务别人不会修改,所以不会上锁,但是在更新的时候会判断一个在此期间别人有没有去更新这个数据,可以使用版本号等机制.乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁,

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里面就用到了很多这种锁的机制,比如行锁,表锁,读锁,写锁等,都是在做操作之前先上锁,再比如Java里面的同步原语synchronized关键字的实现也是悲观锁

四:Spring篇?

1:什么是Spring?

Spring是一个分层的JavaSE/EE full-stack (一站式)轻量级开元框架

2:你们项目中为什么使用Spring框架?

Spring是一个开源框架,它被创建出来的初衷就是解决企业级应用开发的复杂性.Spring不仅仅局限于服务端开发,任何的Java应用都能借助于Spring 变得更加简单,可测试性更强,松耦合性更好.
为了降低Java开发的复杂性,Spring采取了4中关键策略:
基于POJO的轻量级和最小侵入性编程.
通过依赖注入和面向接口实现松耦合.
通过切面和模板减少样版式代码.
通过切面和惯例进行声明氏编码.

3:Autowired和Resource关键字的区别?

@Autowired: 先根据类型查找再根据名称查找
@Resource: 先根据名称查找再根据类型查找

4:依赖注入的方式有几种?各是什么?

  1. 构造器注入
  2. setter方法注入
  3. 接口注入

5:说说你对Spring MVC 的理解?

Spring mvc 是一种基于Java,使用了MVC架构模式的思想,将web层进行指责解耦,简化我们日常web开发.Spring MVC 核心类是DispatcherServlet,它是一个Servlet,顶层是实现Servlet接口.

工作原理
在这里插入图片描述
上面的流程图很清楚的展示了Spring MVC的工作原理,以下就是文字描述的工作原理:

  1. 客户端发起请求,直接请求到DispatcherServlet,DispatcherServlet收到请求后,不会自己处理,而是委托给其他的解析器处理,进行进行全局的流程控制.
  2. 首先,DispatcherServlet请求HandlerMapping进行处理,HandlerMapping会获取HandlerExecutionChain对象(包含一个Handler处理器对象,多个HandlerInterceptor拦截器)
  3. 解析到Handler处理器后,会使用HandlerAdaper对Handler处理器进行包装
  4. 然后,调用拦截器的perHandle方法进行拦截,返回true后,调用目标Handler的目标方法处理业务逻辑,得到ModelAndView对象,再调用拦截器的postHandler方法.
  5. ViewResolver 会根据ModelAndView查找实际的View
  6. DispatcherServlet会将Model模型数据传进来交给View进行渲染视图
  7. 调用拦截器的afterCompletion方法后,DispacherServlet将渲染视图返回给浏览器

6:Spring MVC常用的注解有哪些?

@RequestMapping: 用于处理请求url映射的注解,可用于类或者方法上,用于类上,则表示类中的所有响应请求的方法都是以该地址改为父路径
@RequestBody: 实现接收Http请求的json数据,将json转换为Java对象
@ResponseBody: 实现将Controller方法返回对象转化为json对象响应给客户

7:谈谈你对Spring AOP的理解?

AOP(==Aspect Orient Programming ==), 一般称为面向切面编程,作为面向对象的一种补充,与OOP相比,面向切面,传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,比如事务管理,日志,缓存等等.这些横切性问题和我们的主业务逻辑关系不大,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护.AOP是处理一些横切性问题.AOP的编程思想就是把这些问题和主业务逻辑分开,达到与业务逻辑解耦的目的,使代码的重用性和开发效率更高.
AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP 为代表.静态代理是编译期实现,动态代理是运行期实现,可想而知前者拥有更好的性能.

8:Spring AOP和AspectJ AOP有什么区别?

Spring AOP 属于运行时增强,而AspectJ 是编译时增强.Spring AOP基于代理(Proxying),而AspectJ基于字节码操作(Bytecode Manipulation)
Spring AOP 已经集成了AspectJ, AspectJ 应该算的上是Java生态中最完整的AOP 框架了,AspectJ 相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单,如果我们的切面比较少,那么两者性能差异不大,但是,当切面太多的话,最好选择AspectJ ,它比Spring AOP快很多.

9:在Spring AOP中关注点和横切关注的区别是什么?什么是通知呢?有哪些类型呢?

区别?

关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能.
横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响这个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能.因此这些都属于横切关注点.

什么是通知?

指拦截到连接点之后需要执行的代码,通知分为前置,后置,异常,环绕,最终五种

通知的类型?

  1. 前置通知(Before): 在目标方法被调用之前调用通知功能
  2. 后置通知(After): 在目标方法完成之后调用通知,此时不会关心方法的输出是什么
  3. 最终通知(After-Returning): 在目标方法成功执行之后调用通知
  4. 异常通知(After-Throwing): 在目标方法抛出异常后调用通知
  5. 环绕通知(Around): 通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为.

10:说说你对Spring的IOC是怎么理解的?

IOC即Inversion of Control 即"控制反转",不是什么技术,而是一种设计思想.
传统开发中,我们在对象内部通过new 关键字,创建依赖的对象,我们主动控制依赖的对象;而IOC有专门一个容器来创建依赖对象.对象内部只是被动的接收这些依赖的对象.依赖对象的获取给反转了,之前依赖对象有对象内部主动创建获取,现在,依赖对象由容器创建,对象从容器中获取依赖对象.
IOC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象.这一点是通过DI(Dependency Injection,依赖注入)来实现的.当A对象需要一个B对象的依赖,以前我们需要编写代码手动构建B对象,有了DI(依赖注入),我们不需要构建B对象,何时构建,Spring会在合适的时候注入B对象,完成各个对象之间的控制.

11:解释一个Spring bean的生命周期?

Spring bean 生命周期主要是refresh()方法.

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			//为容器初始化做准备
			prepareRefresh();

			// 解析xml和注解
			//该方法会解析所有 Spring 配置文件(通常我们会放在 resources 目录下),将所有 Spring 配置文件中的 bean 定义封装成 BeanDefinition,加载到 BeanFactory 中。
      //加载 bean 定义,由 XmlWebApplicationContext 实现
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			//前面已经把配置类的定义已经注册到容器中,还在进行对配置类进行全面分析和处理之前,Spring还要再做一些预处理,比如设置类加载器,设置后置处理器ApplicationContextAwareProcessor,将environment  systemProperties systemEnvironment三个对象添加到容器当中,设置Spring框架不需要被自动注入的接口等操作。
			prepareBeanFactory(beanFactory);

			try {
				      
        //这里面代码是空的
        // 由子类实现对BeanFacoty的一些后置处理
				postProcessBeanFactory(beanFactory);

				/* 本方法会实例化和调用所有 BeanFactoryPostProcessor
        BeanFactoryPostProcessors:bean工厂的bean属性处理容器,说通俗一些就是可以管理我们的bean工厂内所有的beandefinition(未实例化)数据,可以随心所欲的修改属性。*/
				invokeBeanFactoryPostProcessors(beanFactory);

				/*本方法会注册所有的 BeanPostProcessor,将所有实现了 BeanPostProcessor 接口的类加载到 BeanFactory 中。
        BeanPostProcessor 接口是 Spring 初始化 bean 时对外暴露的扩展点,Spring IoC 容器允许 BeanPostProcessor 在容器初始化 bean 的前后,添加自己的逻辑处理。在 registerBeanPostProcessors 方法只是注册到 BeanFactory 中,具体调用是在 bean 初始化的时候。
				具体的:在所有 bean 实例化时,执行初始化方法前会调用所有 BeanPostProcessor 的 postProcessBeforeInitialization 方法,在执行初始化方法后会调用所有 BeanPostProcessor 的 postProcessAfterInitialization 方法。*/
				registerBeanPostProcessors(beanFactory);

				//初始化ApplicationContext的MessageSource
				initMessageSource();

				//初始化ApplicationContext事件广播器
				initApplicationEventMulticaster();

				// 初始化子类特殊bean(钩子方法)
				onRefresh();

				// 注册事件监听器
				registerListeners();

				/*
				* 1、bean实例化过程
				* 2、依赖注入
				* 3、解析@PostConstruct,@PreDestroy,@Resource, @Autowired,@Value等注解
				* 4、BeanPostProcessor的执行
				* 5、Aop的入口
				*
				* */
				finishBeanFactoryInitialization(beanFactory);

				// 广播事件,ApplicationContext初始化完成
				finishRefresh();
			}

			catch (BeansException ex) {
				...
			}

			finally {
				...
			}
		}
	}

finishBeanFactoryInitialization(beanFactory):
在这里插入图片描述
1:首先在preInstantiateSingletons 方法中判断: 单例,非懒加载,非抽象,满足这三个条件才会调用getBean(Bean实例化都是通过调用该方法实现的)实例化
2:getBean中会调用doGetBean,doGetBean中从缓存中拿实例,没有的话则通过scope类型去创建对应的Bean实例.在创建对象之前如果scope是protytype类型的首先会通过.
3:有的话调用getSingleton(beanName),经过一系列的方法调用,调到doCreateBean,进行Bean的实例化,和对象中属性注入,检查Aware相关接口,BeanPostProcessor的前置处理,BeanPostProcessor的后置处理,初始化操作.

Spring bean 完整生命周期

  1. 实例化Bean:
    对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化.对于ApplicationContent容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean.
  2. 设置对象属性(依赖注入)
    实例化的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入
  3. 处理Aware接口
    接着,Spring 会检测该对象是否实现了xxxAware接口,并将先关额xxxAware实例注入给Bean;
    ①:如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanld) 方法,此处传递的就是Spring 配置文件中Bean的ID值;
    ②:如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身.
    ③:如果这个Bean实现了ApplicationContentAware接口,会调用setApplicationContent(ApplicationContent)方法,传入Spring上下文;
  4. BeanPostProcessor:
    如果想对Bean进行一些自定义的处理,那么可以让Bean实现BeanPostProcessor接口,那将会调用postProcessBeforeInitiaIization(Object obj,String s)方法.
  5. InitializingBean 与init-method:
    如果Bean在Spring配置文件中配置了init-method属性,则会自动调用其配置的初始化方法.
  6. 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj,String s)方法;由于这个方法时再Bean初始化结束时调用的,所以可以被应用于内存或缓存技术.
  7. DisposableBean:
    当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean 这个接口,会调用其实现的destory()方法.
  8. destroy-method:
    最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法.

进阶版Spring完整的生命周期?

  1. 我们先在我们需要获取bean对象的方法中调用ApplicationContext容器,将XML文件或者JAVA文件作为参数传到对应的容器中.
  2. 启动时,容器会帮我们设置配置文件路径,调用AbstractApplicationContext类的refresh()方法,实现的ConfigurableApplicationContext接口中的refresh()方法(refresh方法的作用就是:在创建IOC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的IOC容器,refresh的作用类型于对IOC容器的重启,在新建立好的容器中对容器进行初始化,对Bean定义资源进行载入)
  3. 在refresh()方法中调用obtainFreshBeanFactory方法进行解析XML和注解,并加载Bean定义,注册到IOC容器中.
  4. 然后调用prepareBeanFactory方法做一些预处理,比如设置类加载器
  5. 之后调用invokeBeanFactoryPostProcessors方法管理我们的Bean工厂内所有的beandefinition(未实例化)数据,可以再次对数据进行修改操作
  6. 在调用registerBeanPostProcessors方法注册所有的BeanPostProcessor,将所有实现了BeanPostProcessor接口的类加载到BeanFactory中,Spring IOC 容器允许BeanPostProcessor在容器初始化bran的前后,添加自己的逻辑处理.
  7. 之后会做一些初始化容器的一些工作和注册事件监听器.
  8. 接下来就是调动finishBeanFactoryIniyislization 方法进行bean对象的处理
  9. 首先判断:单例,非懒加载,非抽象,满足这个三个条件才会调用getBean(Bean实例化都是通过调用该方法实现的)实例化
  10. getBean中调用doGetBean,doGetBean中从缓存中拿实例,没有的话则通过scope类型去创建对应的Bean实例,在创建对象之前如果scope是prototype类型的首先会通过isPrototypeCurrentlyInCreation检验是否存在循环依赖.
  11. 有的话调用getSingleton(beanName),经过一系列的方法调用,调到doCreateBean,在此方法中调用createBeanInstance方法进行Bean实例化,通过获取BeanDefinition对象中的信息,实例化所有的bean.
  12. 在调用populateBean,完成对对象属性的设置(依赖注入),实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入.
  13. 在通过调用initializebean方法中的invokeAwareMethods 方法,检测对象是否实现了xxxAware接口,并将相关的xxxAware实例出入给这个bean
    ①:如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanld) 方法,此处传递的就是Spring 配置文件中Bean的ID值;
    ②:如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身.
    ③:如果这个Bean实现了ApplicationContentAware接口,会调用setApplicationContent(ApplicationContent)方法,传入Spring上下文;
  14. 如果想对Bean进行一些自定义的处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialiaztion()方法.可以在初始化前对bean进行操作.
  15. 如果Bean在Spring配置文件中配置了init-method属性,则会自动调用其配置的初始化方法.
  16. 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization()方法,可以初始化后对bean进行操作
  17. 接下来就可以使用bean了
  18. 当Bean不在需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destory()方法
  19. 如果这个Bean的Spring配置中配置了destory-method属性,会自动调用其配置的销毁方法.

12:解释一下Spring支持的几种bean 的作用域?

Spring容器中的bean可以分成5个范围:

  1. singleton: 默认,每个容器中只有一个bean的实例,单例的模式有BeanFactory自身来维护
  2. prototype: 为每一个bean请求提供一个实例
  3. request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收.
  4. session: 与request范围类型,确保每个session中有一个bean的实例,在session过期后,bean会随之失效.
  5. global-session:全局作用域,global-session和Portlet应用相关.当你的应用部署在Portlet容器中工作时,它包含很多portlet.如果你想要声明让所有的porttal公用全局的存储变量的话,那么这全局变脸需要存储在global-session中.全局作用域与Servlet中的session作用于效果相同.

13:Spring 基于XML注入bean的几种方式?

  1. set方法注入
  2. 构造器逐日: ①通过index设置参数的位置;②通过type设置参数类型
  3. 静态工厂注入
  4. 实例工厂

14:Spring框架中都用到了那些设计模式?

  1. 工厂模式: BeanFactory就是简单工厂模式的体现,用来创建对象的实例
  2. 单例模式: Bean默认为单例模式
  3. 代理模式:Spring 的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
  4. 模板方法:用来解决代码重复的问题.如果RestTemplate, JmsTemplate,JpaTemplate
  5. 观察者模式: 定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现-ApplicationListener

15:说说Spring 中ApplicationContent和BeanFactory的区别?

ApplicationContext: 它构建核心容器时,创建对象采取的策略是采用立即加载的方式,也就是说,只要一读取完配置文件马上就创建配置文件中的配置对象
BeanFactory: 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式,也就是说,什么是否根据id获取对象了,什么时候才真正创建对象.

16:Spring框架中的单例bean是线程安全的么?

不是

17:Spring是怎么解决循环依赖的?

Spring 使用三级缓存来解决循环依赖的问题,三级缓存分别是:
singletonObjects: 一级缓存,存储单例对象,Bean已经实例化,初始化完成.
earlySingletonObjects: 二级缓存,存储singletonObejct, 这个Bean实例化了,还没有初始化.
singletonFactories: 三级缓存,存储singletonFactory.
在这里插入图片描述

18:说说事务的隔离级别?

当两个事务对同一个数据库的记录进行操作时,那么,他们之前的影响是怎么样的呢?这就出现了事务隔离级别的概念.数据库的隔离性与并发控制有很大关系.数据库的隔离级别是数据库的事务特性ACID的一部分,ACID即原子性(atom icity),一致性(consistency),隔离性(isolation)持久性(durability)

Spring的事务隔离级别有四个: READ_UNCOMMITTED,READ_COMMITTED,REPEATABLE_READ和SERILIZABLE,还有一个数据库默认的隔离级别DEFAULT,Mysql默认是REPEATABLE_READ.

1:脏读: 读到了其他事务还没有提交的数据
2:不可重复读:对某数据进行读取,发现两次读取的结果不同,也就是说没有读到相同的内容.这是因为有其他事务对这个数据同时进行了修改或删除
3:幻读: 事务A根据条件查询得到了N条数据,但此时事务B更改或者增加了M条符合事务A查询条件的数据,这样当事务A再次进行查询的时候发现会有N+M条数据,产生了幻读

READ_UNCOMMITTED

一个事务可以读取到另一个事务未提交的事务记录.换句话说,如果事务A开启,写入了一条记录,这时候事务B来读取数据是可以直接读取到的,但是当事务A回滚了之后,事务B读取到的数据就不是有效的数据了,这中情况被称为脏读==(drity read)==.除了脏读的问题,READ_UNCOMMITTED还可能出险non-repeatable read ==(不可重复读)==和phantom read ==(幻读)==的问题.

READ_COMMITTED

一个事务只能读取到已经提交的记录,不能读取到未提交的记录
如果事务A先读取到一条数据,事务B再对这条数据进行修改,那么事务A这个时候再去读取这条数据的时候就会发生前后两次读取的记录不一致,这个问题被称为non-repeatable read (不可重复读).
除了non-repeatable read (不可重复读) 的问题,READ_COMMITTED还可能发生phantom read (幻读) 的问题。

REPEATABLE_READ

一个事务可以多次从数据库读取某条记录,而且多次读取的那条记录都是一致的,相同的,这个隔离级别可以避免dirty read ==(脏读)==和non-repeatable read ==(不可重复读)==的问题,但可能发生phantom read ==(幻读)==的问题.
事务A两次从数据库读取一系列记录,期间,事务B插入了某条记录并提交.当事务A第二次读取时,会读取到事务B刚刚插入的那条记录.在事务期间,事务A两次读取的一系列记录不一致,这个问题成为phantom read.

SERIALIZABLE

SERIALIABLE是Spring 最强的隔离级别.事务执行时,会在所有级别上加锁,比如read和write时都会加锁,仿佛事务是以串行的方式进行的,而不是一起发生的.这会防止dirty read (脏读), non-repeatable read ==(不可重复读)==和 phantom read ==(幻读)==的出险,但是,会带来性能的下降.

DEFAULT

MySQL默认是REPEATABLE_READ

19:说说事务的传播级别?

  1. PROPAGATION_REQUIRED: 默认的Spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文不存在事务,则新建事务执行,所以这个级别的事务传播级别通常能满足大多数的业务场景.
  2. PROPAGATION_SUPPORTS: 该传播级别的特点是,如果上下文中已经存在事务,则支持事务加入,如果没有事务,则使用非事务的方式执行.所有并非所有的包在transactionTemplate.execute中的代码都会有事务支持,这个传播级别通常是用来处理那些并非原子性的非核心业务逻辑操作,应用场景较少.
  3. PROPAGATION_MANDATORY: 该级别的事务要求上下文中必须存在事务,否则就会抛出异常,配置该方式的传播级别是有效的控制上下文调用代码遗漏事务控制的保证手段.比如一段代码不能单独被执行调用,但是一旦被调用,就必须有事务包含的情况,就可以使用这个传播级别.
  4. PROPAGATION_REQUIRES_NEW: new每次都会创建一个新事务,该传播级别的特点是,每次都会新建事务,并且上下文的事务挂机,执行当前新建事务完成后,上下文事务回复再执行.
    比如:现在有一个发送1000个红包的操作,在发送之前,要做一些系统的初始化,验证,数据记录操作,然后发送1000封红包,然后再记录发送日志,发送日志要求100%准确,如果日志不准确,则整个父事务逻辑需要回滚.通过PROPATATION_REQUIRES_NEW级别的事务来进行就可以完成,发送红包的子事务不会直接影响到父事务的提交和回滚.
  5. PROPAGATION_NOT_SUPPORTED: 当前级别的特点就是上下文中存在事务,则挂起事务,执行当前逻辑,结束后回复上下文的事务,
  6. PROPAGATION_NEVER: 要求上下文中不能存在事务,一旦有事务,就抛出runtime异常,强制停止执行,这个事务传播级别可以说跟事务有仇.
  7. PROPAGATION_NESTED: 从字面可以知道,为nested嵌套级别事务,该传播级别特征是,如果上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务.

20:Spring 事务实现方式?

  1. 编程式事务管理,基于POJO的应用来说是唯一选择.需要在代码中调用beginTransaction() commit() rollback()等事务管理相关的方法.
  2. 基于TransactionProxyFactoryBean的声明式事务管理
  3. 基于@Transaction的声明式事务管理
  4. 基于AspectJ AOP配置事务

21:Spring框架的事务管理有哪些优点?

  1. 它为不同的事务API如JPA,JDBC,Hibernate,JPA和JDO提供一个不变的编程模式.
  2. 它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API
  3. 它支持声明式事务管理
  4. 它和Spring各种数据访问抽象层很好的集成.

22:事务注解的本质是什么?

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

五: Mybatis篇?

1:什么是Mybatis?

  1. 持久层框架
  2. 支持自定义SQL,存储过程以及高级映射
  3. Mybatis 免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作.
  4. 可以通过简单的XML或注解来配置和映射原始类型,接口和Java POJO 为数据库中的记录

2:说说Mybatis的优点和缺点?

优点:

  1. 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML中,解除sql与程序代码的耦合,便于统一管理,提供XML标签,支持编写动态SQL语句,并可重用
  2. 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量亢余的代码,不需要手动开关连接.
  3. 很好的与各种数据库兼容
  4. 能够与Spring 很好的集成
  5. 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护

缺点:

  1. SQL语句的编写工作量较大,尤其当字段多,关联表多时,对开发人员编写SQL语句的功能有一定要求.
  2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库.

3:#{}和${}的区别?

  1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如: order by #{name} 如果传入张三, 那么解析sql的时候机会解析成order by ‘张三’,
  2. $将传入的数据直接显示的生成在sql中,如: order by ${name} 如果传入张三, 那么解析sql的时候机会解析成order by 张三,
  3. #方式能够很大程度防止sql注入。
  4. $方式无法防止Sql注入。

4:当实体类中的属性名和表中的字段名不一样,怎么办?

在这里插入图片描述

5:Mybatis是如何进行分页的?分页插件的原理是什么?

Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页。可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

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

6:Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?

1: 使用标签,逐一定义数据库列名和对象属性名之间的映射关系
2: 使用sql列的别名功能,将列的别名书写为对象属性名

有了列名和属性名的映射关系后,Mybatis通过反射创建对象,通过使用反射给对象的属性逐一赋值并且返回,那些找不到映射关系的属性,是无法完成赋值的

7:如何执行批量插入?

Mybatis的Service 层提供批量保存,批量更新等方法,可以直接调用即可.

8:Xml映射文件中,除了常见的select|insert|update|delete标签外还有哪些标签?

、、、、
,加上动态 sql 的 9 个标签,其中 为 sql 片段标签,通过
标签引入 sql 片段, 为不支持自增的主键生成策略标
签。

9:Mybatis实现一对一有几种方式?具体是怎么操作的?

有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在
resultMap 里面配置 association 节点配置一对一的类就可以完成;

嵌套查询是先查一个表,根据这个表里面的结果的 外键 id,去再另外一个表里面查询数据,也是通过 association 配置,但另外一个表的查询通过 select 属性配置。

10:Mybatis是否支持延迟加载?如何支持,他的实现原理是什么?

1: mybatis 仅支持关联对象association和关联集合对象collection的延迟加载,association是一对一,collection指的是一对多查询,在mybatis配置文件中可以配置lacyloadingEnable=true/false.
2:原理: 使用CGLIB为目标对象建立代理对象,当调用目标对象的方法时进入拦截器方法.比如调用a.getb().getName(),拦截器方法invoke()发现a.getb()为null值,会单独发送事先保存好的查询关联b对象的sql语句,吧b查询上来然后调用a.setB(b),于是a的对象的属性b就有值了,然后接着调用a.getb().getName(),这就是延迟加载的原理.

11:说说Mybatis的缓存机制?

在MyBatis中,Cache是缓存接口,定义了一些基本的缓存操作,所有缓存类都应该实现该接口.MyBatis内部提供了丰富的缓存实现类,比如具有基本缓存功能的PerpetualCache,具有LRU策略的缓存LruCache,以及可保证线程安全的缓存SynchronizedCache和具备阻塞功能的缓存BlockingCache等.除此之外,还有很多缓存实现类.需要特别说明的是,MyBatis在实现缓存模块的过程中,使用了装饰模式,在以上几种缓存实现类中,PerpetualCache相当于装饰模式中ConCreteComponent.LruCache,SynchronizedCache和BlockingCache等相当于装饰模式中的ConcreteDecorator

一级缓存

在进行数据库查询之前,Mybatis首先会检查一级缓存中是否有相应的记录,如果有则直接返回即可,一级缓存是数据库的最后一道防线,若一级缓存未命中,查询请求将落到数据库上,一级缓存是在BaseExecutor被初始化的,一级缓存的类型为PerpetualCache.

二级缓存

二级缓存构建在一级缓存之上,在收到查询请求时,Mybatis首先会查询二级缓存,若二级缓存未命中,再去查询一级缓存,与一级缓存不同,二级缓存和具体的命名空间绑定,一级缓存则是和SqlSession绑定.在按照MyBatis规范使用SqlSession的情况下,一级缓存不存在并发问题.二级缓存则不然,二级缓存可在多个命名空间间共享.这种情况下,会存在并发问题,因此需要针对性去处理.除了并发问题,二级缓存还存在事务问题.

12:Mybatis中见过什么设计模式?

工厂模式,建造者模式(Builder),单例模式,适配器模式,代理模式,模板方法模式,装饰器模式.

13:Mybatis 中比如UserMapper.java 是接口,为什么没有实现类还能调用?

通过代理,反射找到xml文件中对应的方法

六:Spring boot 篇?

1:为什么要使用Spring boot ?

  1. 有良好的基因,他是伴随着spring4.0 而生的,他的作用就是帮助开发者快速的搭建spring 框架.
  2. 简化依赖
  3. 简化配置
  4. 简化部署 spring boot 内嵌了tomcat,我们只需要将项目打成java 包,然后执行 java -jar xxx即可.
  5. 简化监控

2:Spring boot 的核心注解是哪个?他主要有那几个注解组成的?

  1. 启动类上面的注解是@SpringBootApplication它是Spring Boot 的核心注解
  2. @SpringBootConfiguration: 组合了Conofiguration注解,实现配置文件的功能
  3. @EnableAutoConfiguration: 打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能:@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
  4. ComponentScan: Spring 组件扫描

3:运行Spring boot 有哪几种方式?

  1. 运行带有main方法类
  2. 通过命令行 java -jar 的方式
  3. 通过spring-boot-plugin的方式

4:如何理解Spring boot 中的Starters?

Starters可以理解为启动器,它包含了一系列可以集成到应用里面的依赖包.你可以一站式集成Spring及其他技术,而不需要到处找实例代码和依赖包,如你想使用Spring JPA访问数据库,只要加入springboot-starter-data-jpa启动器依赖就能使用了,Starters包含了许多项目中需要用到的依赖,他们能快速持续的运行.

5:如何在Spring boot 启动的时候运行一些特定的代码?

可以实现接口 ApplicationRunner 或者 CommandLineRunner,这两个接口实现方式一样,它们都只提供了一个 run 方法。

6:Spring boot 需要独立的容器运行吗?

可以不需要,
内置了 Tomcat/ Jetty 等容器。

7:Spring boot 的监视器是什么?

Spring boot actuator 是 Spring 启动框架中的重要功能之一,Spring Boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态,有几个指标必须在生产环境中进行检查和监控,即时一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息,监视器模块公开了一组可直接作为Http URL 访问的REST端点来检查状态.

8:如何使用Spring boot 实现异常处理?

Spring提供了一种使用ControllerAdvice处理异常的非常有用的方法。
我们通过实现一个ControlerAdvice类,来处理控制器类抛出的所有异常。

9:Spring boot 常见的starter有哪些?

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

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

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

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

mybatis-spring-boot-starter 第三方的mybatis集成starter

10:Spring boot 实现热部署有哪几种方式?

主要有两种方式:
1 Spring Loaded
2 Spring-boot-devtools

11:如何理解Spring boot 的配置加载顺序?

Spring Boot配置加载顺序优先级是:propertiese文件、YAML文件、系统环境变量、命令行参数

12:Spring boot 的核心配置文件有那几个?他们的区别是什么?

Spring boot 的核心配置文件是Application和Bootstrap 配置文件.Application配置文件这个容易理解,主要用于Spring Boot项目的自动化配置.bootstrap配置文件有以下几个应用场景,使用Spring Cloud Config配置中心时,这是需要在bootstrap配置文件中添加链接到配置中心的配置属性来加载外部配置中心的配置信息

13:如何继承Spring boot 和ActiveMQ?

对于集成 Spring Boot 和 ActiveMQ,
我们使用spring-boot-starter-activemq依赖关系

七: MYSQL

1:数据库的三范式是什么?

  1. 第一范式: 强调的是列的原子性,即列不能够再分成其他几列.
  2. 第二范式: 首先遵循第一范式,然后包含两部分内容,一是表必须有一个主键,二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分,
  3. 第三范式:首先是满足第二范式,另外非主键列必须直接依赖于主键,不能存在传递依赖.

2:Mysql数据库引擎有哪些?

Innodb

	支持事务,是事务安全的,提供行级锁与外键约束,有缓冲池,用于缓冲数据和索引
	适用场景:用于事务处理,具有ACID事务支持,应用于执行大量的insert和update操作的表

MyISAM

	不支持是,不支持外键约束,不支持行级锁,操作时需要锁定整张表,不过会保存表的行数,所有当执行select count(*) from table name 时执行特别快.
	适用场景: 用于管理非事务表,提高高速检索及全文检索能力,适用于大量的select 操作的表,如 日志等.

MEMORY

	使用存在于内存中的内容创建表,每一个memory只实际对应一个磁盘文件.因为是存在内存中的,所以memory访问速度非常快,而且该引擎使用hash索引,可以一次定位,不需要像B树一样从根节点查找到支节点,所以精确查询时访问速度特别快,但是非精确查找时,比如like,这中范围查找,hash就起不到作用了,另外一旦服务关闭,表中的数据就会丢失,因为没有存到磁盘中.
	适用场景:主要用于内容变化不频繁的表,或者作为中间的查找表.对表的更新要谨慎因为数据没有被写入到磁盘中,服务关闭前要考虑好数据的存储.

MERGE

	MERGE存储引擎把一组MyISAM数据表当做一个逻辑单元来对待,让我们可以同时对他们进行查询.构成一个MERGE数据表结构的各成员MyISAM数据表必须具有完全一样的结构,每一个成员数据表的数据列必须按照同样的顺序定义同样的名字和类型,索引也必须按照同样的顺序和同样的方式定义.便于同时引用多个数据表而无需发出多条查询

3:说说InnoDB和MyISAM的区别?

在这里插入图片描述

  1. MyISAM 不支持事务.
    InnoDB: Mysql 默认的事务型引擎,也是最重要和使用最广泛的存储引擎.它被设计成为大量的短期事务,短期事务大部分情况下是正常提交的,很少被回滚.InnoDB的性能与自动奔溃恢复的特性,使得它在非事务存储需求中也很流行,除非有非常也别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎.
    MyISAM: 在Mysql 5.1及之前的版本,MyISAM提供的大量的特性,包括全文索引,压缩,空间函数(GIS)等,但MyISAM并不支持事务以及行级锁,而且一个毫无疑问的缺陷是奔溃后无法完全恢复.
  2. MyISAM与InnoDB表锁和行锁的解释
    在Mysql中.表级锁有两种模式:表共享读锁,表独占写锁.也就是说对于MyISAM引擎的表,多个用户可以对同一个表发起读的请求,但是如果一个用户对表进行写操作,那么则会阻塞其他用户对这个表的读和写.
    InnoDB引擎的表,是通过索引项来加锁实现的,即只有通过索引条件检索数据的时候,InnoDB才会使用行级锁,否则也会使用表级锁.
  3. 在物理空间的存储
    所有数据库的文件都在data目录下,一个文件夹对应一个数据库,本质是文件的存储InnoDB在数据库中只存在一个*.frm文件,以及上级目录下的ibdata文件
    MyISAM在磁盘上存储成三个文件: ①*.frm文件(存储表定义)②MYD(MyData,数据文件)③MYI(MyIndex,索引文件)
  4. 是否保存数据库中表的具体行数
    InnnoDB不报存表的具体行数,也就是说,当时用count(*) 查询时,InnoDB需要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可.

4:数据库事务?

一组逻辑操作的单元,使数据从一种形态到另一个形态.当在一个事务中执行多个操作时,要么所有的事务都被提交,永久的保存下来,要么所有的事务全部回滚到最初事务开启之前的状态(事务要不全部成功,要不全部失败)

5:索引是什么?

索引本身是一个独立的存储单位,在该单位里边有记录着数据表某个字段和字段对应的物理空间.索引内部有算法支持,可以使查询速度非常快.有了索引,我们根据索引为条件进行数据查询,速度就非常快,索引本身有算法支持,可以快速定位我们要找到的关键字(字段),索引字段与物理地址有直接对应,帮助我们快速定位要找到的信息.

6:SQL优化手段有哪些?

  1. SQL语句中IN包含的值不应过多.
  2. SELECT语句务必指明字段名称
  3. 当只需要一条数据的时候,使用limit 1.
  4. 如果排序字段没有使用到索引的话就少用排序
  5. 如果限制条件中没有用到索引,尽量少用or
  6. 尽量用union all代替union
  7. 不适用ORDER BY RAND()
  8. 使用合理的分页方式以提高分页的效率
  9. 分段查询
  10. 避免在where 子句中对字段进行null值判断.对于null的判断会导致引擎放弃使用索引而进行全表扫描.
  11. 不建议使用%前缀模糊查询,这样查询会导致索引失效,但是可以使用name%,如果想使用类似于%name%的,可以为name建立全文索引.
  12. 避免在where子句中对字段进行表达式操作
  13. 避免隐式类型转换
  14. 对于联合索引,要遵循最左匹配原则
  15. 必要时候可以使用forceindex来强制查询走某个索引.
  16. 注意范围查询语句,对于联合索引来说.如果存在范围查询,比如between, < > 等条件时,会造成后面的索引字段失效.

7:简单说一说drop,delete和truncate区别?

  1. drop:直接删除表
  2. truncate: 删除表中数据.再插入时自增长id又从1开始.不记录日志
  3. delete: 删除表中数据,可以加where子句.记录日志,可以回滚,

8:什么是视图?

视图是从一个或者几个基本表(或视图)导出的表,它与基本表不同,是一个虚表,数据库中只存放视图的定义,而不存放视图对应的数据,这些数据任存放在原来的基本表中,所以一旦表中发生数据变化,从视图中查询出的数据也就随之改变了,从这个意义上来讲,视图就是一个窗口,通过实体可以看到数据库中自己想了解的数据变化.

创建视图的语句: create view 视图名 as 查询语句;

9:什么是内联结,左外连接,右外连接?

  1. 内联结: inner join 默认使用数据量最小的表作为主表
  2. 左外连接: left join 默认使用左表作为主表进行查询
  3. 右外连接: right join 默认使用右表作为主表进行查询

10:并发事务带来哪些问题?

mysql从5.5.8开始,InnoDB就是默认的存储引擎,InnoDB最大的特点就是支持事务,支持行级锁.既然支持事务,就会有处理并发事务带来的问题:更新丢失,脏读,不可重复读,幻读

11:事务隔离级别有哪些?MySQL的默认隔离级别是?

SQL 标准定义了四个隔离级别:
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。

READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。

REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

默认支持的隔离级别是 REPEATABLE-READ(可重读)

12:大表如何优化?

在这里插入图片描述

13:分库分表之后ID主键应该怎么处理?

UUID,使用REDIS生成唯一ID

14:说说MySQL中一条查询SQL是如何执行的?

在这里插入图片描述
①客户端请求连接到MYSQL数据库
②连接器负责跟客户端建立连接,获取权限以及维持和管理连接
③连接建立完成后,就会执行select语句,MYSQL拿到一个查询请求后,会先到查询缓存中看看,如果该条语句直接在缓存中找到,则将结果直接返回给客户端
④如果没有命中缓存,就要开始执行语句了,这个时候分析器就会对词法分析和语法分析,判断客户想要干什么
⑤知道了要干什么以后,就有优化器来确定怎么干,这个时候会对执行的sql语句选择一种最优的执行方法
⑥通过优化器知道了怎么做以后,执行器就开始执行语句,执行时会先看用户是否有执行权限,如果有权限,执行器就是根据表的引擎定义,去使用这个引擎提供的接口
⑦语句执行完了以后,就去引擎层获取数据返回(如果开启查询缓存的话就会去缓存查询的语句及结果)

15:索引有哪些优缺点?

优点
可以提高数据检索的效率,降低数据库的IO成本,通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗.
缺点
实际上索引也是一张表,改表保存了主键与索引字段,并执行实体表的记录,所以索引列也是要占空间的.虽然索引大大提高了查询效率,但是却会降低更新表的速度,因为在更新表的时候,MYSQL不仅要保存数据,还要保存一下索引文件,每次更新添加了索引列的字段,都会调整因为跟新所带来的键值变化后的索引信息.

16:MySQL中varchar,与char的区别?varchar(30)中的30代表的含义?

varchar与char的区别区别一,定长和变长
char 表示定长,长度固定,varchar表示变长,即长度可变。char如果插入的长度小于定义长度时,则用空格填充;varchar小于定义长度时,还是按实际长度存储,插入多长就存多长。 因为其长度固定,char的存取速度还是要比varchar要快得多,方便程序的存储与查找;但是char也为此付出的是空间的代价,因为其长度固定,所以会占据多余的空间,可谓是以空间换取时间效率。varchar则刚好相反,以时间换空间。区别之二,存储的容量不同
对 char 来说,最多能存放的字符个数 255,和编码无关。而 varchar 呢,最多能存放 65532 个字符。varchar的最大有效长度由最大行大小和使用的字符集确定。整体最大长度是 65,532字节。
varchar(30)中30的涵义 最多存放30个字符,varchar(30)和(200)存储hello所占空间一样,但后者在排序时会消耗更多内存,因为order by col采用fixed_length计算col长度(memory引擎也一样)。在早期 MySQL 版本中, 50 代表字节数,现在代表字符数。

17:int(11)中的11代表是什么含义?

显示字符的长度,不影响内部存储,只是影响带 zerofill 定义的 int 时,前面补多少个 0,易于报表展示

18:为什么SELECT COUNT(*) FROM table 在InnoDB中比MyISAM慢?

innoDB不记录表的行数,在执行count查询时会全表扫描, MyISAM会记录行数,执行count时直接取最大的行数值

20:Mysql索引类型有哪些?

主键索引,全文索引,普通索引,唯一索引,符合索引

21:什么时候不要使用索引?

  1. 表记录太少
  2. 经常插入.删除.修改的表
  3. 数据重复且分布平均的表字段
  4. 经常和主字段一块查询但主字段索引值比较多的表字段

22:说说什么是MVCC?

NVCC全称是多版本并发控制(Multi-Version Concurrency Control),只有在InnoDB引擎下存在.MVCC机制的作用其实就是避免同一个数据在不同事务之间的竞争,提高系统的并发性能.
它的特点如下:

- 允许多个版本同时存在,并发执行.
- 不依赖锁机制,性能高
- 只在读已提交和可重复读的事务隔离级别下工作
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值