Java常见问题记录

前言

本篇记录一些常见问题

1:常见的OOM场景有哪些?

A:java.lang.OutOfMemoryError: Java heap space

堆空间不足以给进入老年代的对象分配空间,可以通过Xmx调整大小解决。
新生对象在Eden区分配内存,Eden区满了之后YGC会把Eden区存活的对象放到Survivor区,Survivor区分为From和To,每次GC都会检查Eden和From把存活的复制到To然后From和To换名称。当Eden和Survivor都无法分配或者对象已经成功躲过几轮YGC的时候对象会被处理到老年代,下面的例子一直创建对象,占满新生代之后请求在老年代分配对象空间,如果老年代空间也不够了就会抛出这个错误!

public class HeapOOM {
    
    static class OOMObject {
    }
    
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        
        while (true) {
            list.add(new OOMObject());
        }
    }
}

B:java.lang.OutOfMemoryError: PermGen space

堆空间无法给新的类的Meta信息和类的Class对象分配空间,可通过调整-XX:MaxPermSize大小解决。
一般Spring动态代理生成过多的字节码类或者有大量的类被加载进入内存的时候会出现这个错误,JSP预编译的时候也会出现这个错误!错误的主要原因是YGC和FGC都不会堆永久代进行垃圾清理。

C:java.lang.*Error

线程请求的栈深度大于虚拟机分配给线程的栈空间和方法栈的大小大于线程栈空间的时候会出现这个错误!
虚拟机栈和本地方法栈是线程独立的内存,外加一个程序计数器,每当新建线程的时候JVM都会给线程分配这三个内存作为栈空间。
无限递归就会出现这个问题

public class *ErrorMain {
    int stackLength = 0;

    public *ErrorMain() {
    }

    public void addStackLength(){
        stackLength++;
        addStackLength();
    }

    public static void main(String[] args){
        *ErrorMain sofem = new *ErrorMain();
        try {
            sofem.addStackLength();
        }catch (Throwable e){
            System.out.println(sofem.stackLength);
            e.printStackTrace();
        }
    }
}

D:OutOfMemoryError: unable to create new native thread

无法给新的线程分配栈空间就会出现这个问题!
一直创建线程可以复现。

E:OutOfMemoryError: GC overhead limit exceeded

通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生,可以通过调整Xmx来解决问题。
GC的对象过多的时候JVM预测GC的时间会很长,98%的时间用来回收2%内存,这个时候就会抛出异常。

F:OutOfMemoryError:Requested array size exceeds VM limit

数组过大,请求分配空间的时候堆内存不足以满足,检查是不是数组size过大或者提高Xmx堆内存大小解决这个问题

G:OutOfMemoryErr java.io.FileInputStream.readBytes(Native Method)

堆内存分配的过多,占用了总内存的80%以上导致本地内存没有空间了

H:OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?

其他进程耗尽内存的时候会出现这个错误。

2、MinorGC和FullGC的触发条件

A:MinorGC触发条件

  • Eden区满的时候

B:FullGC触发条件

  • System.gc方法,建议但不一定触发
  • 老年代空间不足的时候会触发
  • 方法区空间不足的时候
  • 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
  • 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

3、Java有内存泄漏吗?什么情况内存泄漏?

A:Java中有内存泄漏

内存泄漏是指无用对象得不到GC的及时回收导致长时间占用内存,从而造成内存空间的浪费。
一般场景是一个长生命周期的对象持有短生命周期对象的引用

  • 静态集合类引起,如HashMap等,这些静态变量生命周期于应用程序一致,他们引用的对象不能被释放
  • 当集合里面的对象属性被修改后,remove()方法不起作用
  • 各种链接没有显示关闭,如数据库和网络,IO链接等,不关闭GC不回收
  • 单例模式对象持有外部对象的引用,外部对象无法被回收
  • 内部类和外部模块等的引用
  • 监听器,释放对象的时候忘记删除监听器,增加了内存泄漏的机会

B:可达性分析:

对象如果到GCRoot没有任何引用连相连的时候,说明这个对象可回收,回收过程分为两次,第一次筛选出覆盖finalize方法的对象放到F-Queue中,虚拟机会调用线程执行这个方法,如果F-Queue中第二次被标记,那么第二次垃圾回收的时候就会回收掉。可以作为GCRoot的对象有

  • 虚拟机栈中引用的对象
  • 方法类静态属性引用的对象
  • 方法区常量池引用的对象(final等)
  • 本地方法栈JNI引用的对象

4、TCP三次握手过程

A:首先了解为什么要进行三次握手?

为了客户端和服务器确保自己有接收信息和发送信息的能力

B:三次握手过程

客户端C发送链接请求到服务端,此时C不知道自己有没有收发能力
服务端S接收到C的连接请求并返回一个消息,此时S知道自己有接收消息的能力,不知道自己发送消息的能力
客户端接收到服务端的响应,此时客户端知道自己有发送消息和接收消息的能力,客户端发送确认指令给服务端
服务端接收到客户端响应信息,确认了自己发送信息的能力,至此三次握手成功确认了双方收发信息的能力。

C:三次握手重要概念

  • 序号:seq序号32位,标识从TCP源端向目的端发送的字节流
  • 确认序号:ack序号32位,只有ACK标志位为1的时候确认序号字段才有效,ack=seq+1
    标志位:URG紧急指针有效、ACK确认序号有效、PSH接收方应尽快把这个报文交给应用层、RST重置连接、SYN发起一个新连接、FIN释放一个连接


    Java常见问题记录
    TCP请求报文结构

D:图解TCP三次握手

第一次主动打开连接的是客户端,所以SYN标志位为1,ACK为0(不是响应),字节流序号seq=x
第二次服务器接收到链接请求,ACK标志位为1(是一个响应),SYN=1(重新打开一个连接),字节流序号为y,ack=x+1(C发来的字节流序号基础上加一位表示是刚刚客户端你传过来的流数据)
第三次,客户端收到SYN=1和ACK=1之后知道这是一个响应和一个新连接,并通过ack=x+1知道这个响应是基于自己刚刚的字节流的;客户端要对这个连接进行确认ACK=1,seq=x+1(表示是第一次握手之后的下一个数据),ack=y+1(表示回复的信息是刚刚你传过来的信息).握手完成


Java常见问题记录
TCP三次握手过程

E:TCP四次挥手

第一次,客户端请求断开连接,FIN=1,seq=u
第二次,服务端接收到FIN标志知道要断开连接,ACK=1,seq=v,ack=u+1,确认收到了断开请求
第三次,服务端准备断开准备就绪,FIN=1,ACK=1,seq=w ack=u+1,告知客户端自己已经就绪了,你也可以准备断开了
第四次,客户端收到FIN=1,被告知断开连接,通过ACK和ack=u+1知道这是自己第一条消息的恢复,ACK=1,seq=u+1,ack=w+1,响应服务端,自己已经OK。四次挥手完成。
客户端最后又等待2MSL,为了确保最后一个ACK能够到达服务器
服务端发送两次,因为FIN表示自己不再发送数据了,但是可能接受数据,此时服务端数据可能没发送完呢,第一次告诉客户端我这还有数据,别着急,第二次就是请求断开了


Java常见问题记录
image.png

5、死锁的四个必要条件

  • 互斥条件,一个资源同时只能被一个线程占有
  • 不可剥夺条件,在被线程占有的时候只能等待该线程执行完成,占有权利不可剥夺
  • 请求与保持条件,占有资源的线程请求其他资源的时候被阻塞,占着资源不释放
  • 循环等待条件,等待队列里面形成一个等待环路

A:如何预防

  • 破坏请求和保持条件
  • 破坏不可剥夺条件
  • 破坏循环等待条件

6、StringBuffer和StringBuilder的区别

  • StringBuffer是线程安全的,里面各种synchronized关键字标注的方法,每次线程都要获取监视器,因此速率较慢
  • StringBuilder不是线程安全的,没有锁机制,所以比较快

7、HashMap原理

8、事务的隔离级别

A:四种隔离级别

9、类加载器工作机制

  • 加载、此过程把编译好的class文件加载到内存并在Perm区生成一个java.lang.class对象作为方法区这个类的各种数据的入口,这个过程既可以在zip包中读取(jar war)也可以在运行时生成,也可以由其他文件生成
  • 验证、这个阶段要确保Class文件字节流中包含的信息符合当前虚拟机的要求比如魔数验证等
  • 准备、正式为类变量分配内存并设置类变量的初始值,即在方法区中分配这些变量使用的内存空间
public static int value=1000;//赋值为0
public static final int value=1000;//赋值为1000,字面量
  • 解析、将常量池中的符号引用替换为直接引用,符号引用指的是
CONSTANT_Class_info
CONSTANT_Field_info
CONSTANT_Method_info

等引用的目标可能不在内存里;直接引用指的是指向目标的指针或者相对偏移量等能够定位到目标的句柄,引用的目标已经在内存里

  • 初始化、执行类构造器方法<client>,这个不是构造方法,而是类变量的赋值操作和静态语句块中的语句合并而成的方法,执行该方法之前会先执行父类中的<Client>方法,如果一个类没有静态变量赋值也没有静态语句块,那么编译器可以不生成<client>方法。一下几个情况不会执行类的初始化

1、通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
2、定义对象数组,不会触发该类的初始化。
3、常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
4、通过类名获取Class对象,不会触发类的初始化。
5、通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
6、通过ClassLoader默认的loadClass方法,也不会触发初始化动作。

Java常见问题记录
类加载过程

Java常见问题记录
双亲委派机制

10、Java锁的原理

synchronized关键字,在JVM中编译的时候会在同步块前后形成监视进入和监视退出两个字节码,这两个字节码都需要一个引用类型的参数来指定要锁定和解锁的对象,如果指定的话就会使用指定的对象,如果不指定就看是类方法还是对象方法。在执行监视进入的指令的时候,会判断能否进入,进入成功后锁计数器+1,不成功则继续等待和其他线程竞争,执行退出指令的时候会把锁计数器变为0。

11、SpringAOP的原理

12、SpringMVC的主要流程

web.xml中配置一个DispatcherServlet负责分派转发,请求到来的时候直接打到DispatcherServlet上的doService方法,doService调用doDispatch方法根据request找到相应的Handler,Handler处理完成之后把ModelAndView对象返回给前端渲染


Java常见问题记录
image.png

13、创建线程有几种方式

  • 继承Thread
  • 实现Runnable接口重写run方法
  • 实现Callable接口并用FutureTask来接收结果
  • 使用线程池来创建
package com.ccfdod.juc;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 一、创建执行线程的方式三:实现Callable接口。相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
 * 二、执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
 */
public class TestCallable {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        // 1.执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
        FutureTask<Integer> result = new FutureTask<>(td);
        new Thread(result).start();
        // 2.接收线程运算后的结果
        Integer sum;
        try {
            //等所有线程执行完,获取值,因此FutureTask 可用于 闭锁
            sum = result.get();
            System.out.println("-----------------------------");
            System.out.println(sum);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class ThreadDemo implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            System.out.println(i);
            sum += i;
        }
        return sum;
    }
}

14、事务的隔离级别

Java常见问题记录
事务隔离级别

A:每种隔离级别的实现方式

  • 共享锁:悲观锁,S锁,是一种读锁,当一个事务获得了一条数据的共享锁,其他事务也可以获得共享锁,但是不能获得排它锁
  • 排它锁:悲观锁,X锁,当一个事务对临界区加上排他锁之后,其他事务不能获得该临界区的任何锁,共享锁保证大家可以一起读,但只能一个人写,排它锁保证只能一个人处理数据,其他人不能读写
  • 未提交读:事务在读取的时候没有加锁,数据更新的时候对其加行级共享锁,导致其他事务不能更改数据,可以读取数据导致脏读
  • 提交读:事务在写数据的时候增加了排它锁,写过程其他事务是不能读取的。事务在读数据时增加了共享锁,这样读的时候数据无法更改,但读完一行就会释放行级共享锁,会出现丢失更新和不可重复读问题。
  • 可重复读:事务写的时候增加行级排他锁,事务读的时候增加行级共享锁,直到事务结束之后才释放,解决了不可重复读问题,但是可能出现幻读问题
  • 序列化:读数据加表级共享锁,写数据增加表级排它锁,幻读问题得到解决

B:InnoDB锁机制

  • 当索引具有唯一性的时候,如果检索某一条记录加锁,那么加的就是这行的行锁
  • 当索引不具有唯一性的时候,如果检索某一条记录加了锁,那么加的是这行前后的Gap锁,间隙锁,开闭区间
  • InnoDB扫描索引记录的时候首先加行锁,然后对索引记录两边加间隙锁
  • 使用范围条件更新记录的时候,无论提交读还是可重复读都会使用间隙锁,不存在读数据0-100 如果更新102那么102后面所有行都加锁

C、MVCC是啥?

  • 除了加锁之外的另一种快照技术,不加锁,对当时的表进行快照,事务基于快照数据进行操作

D、InnoDB中的锁种类

  • 共享锁S,多个事务同时拥有,其他事务不能拥有排它锁
  • 排它锁X,事务独有
  • 意向共享锁IS、表锁,要获取共享锁必须先获得该表的意向共享锁,多个事务可以同时占有
  • 意向排它锁IX、表锁,要获得排它锁必须获得该表的意向排它锁,可以多个事务同时占有


    Java常见问题记录
    image.png

E、为什么要有意向锁?

  • 事务A锁住了表中的一行,让这一行只能读,不能写。
    之后,事务B申请整个表的写锁。
    如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
    数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。

F、什么时候会加锁?

  • UPDATE、DELETE、INSERT语句,会自动给涉及的数据集加排它锁
  • SELECT LOCK IN Share mode或者select for update会分别加共享锁和排锁
  • 普通SELECT语句不加任何锁,只有在一个事务获得共享锁的时候才能进行锁升级,有其他事务公有共享锁想要升级的话需要等待

G:INNODB只有通过索引条件检索数据的时候才会使用行级锁,否则使用表锁!!!!注意是整条记录都没有索引的时候

15、Redis常见问题

A:memcached和Redis的区别

  • memcached所有值都是字符串,redis数据类型有string、list、set、sorted set、hash,redis
  • redis可以持久化数据
  • redis是单线程的,速度更快

B:Redis常见性能问题和解决方案

  • Master写内存快照,调用rdbSave会阻塞主线程工作,所以Master最好不要写内存快照
  • AOF文件过大会影响master重启的恢复速度,Master最好不要做任何持久化工作
  • Master调用BGREWRITEAOF重写AOF文件会占用大量的CPU和内存资源导致服务load过高,出现短暂服务暂停现象
  • Redis主从复制的性能问题,Slave和Master最好处于同一局域网内
  • mysql里有2000W数据,redis只存20w数据,如何保证redis的数据都是热点数据?Redis内存数据集大小上升到一定大小的时候就会实行淘汰策略,淘汰策略如下

volatile-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(驱逐):禁止驱逐数据

C、Redis事务

  • MULTI命令开启事务
  • 事务执行期间不会为其他客户端提供服务
  • EXEC/DISCARD命令提交/回滚事务内的所有操作
  • 事务开启之前,如果CS网络断开,事务不会执行,EXEC之后事务都会执行

D、WATCH命令实现CAS乐观锁

  • WATCH命令在事务执行之前监控了多个keys,如果WATCH之后有任何key发生了变化,EXEC命令执行的事务都会被放弃,同时返回Null multi-bulk应答通知Client事务执行失败。
    下面的代码保证了只有一个线程能做到val++
WATCH mykey
  val = GET mykey
  val = val + 1
  MULTI
  SET mykey $val
  EXEC

16、Zookeeper常见问题

  • zookeeper如何保证事务的顺序一致性?
    zookeeper采用递增的事务id--zxid来标识proposal,zxid是一个64位数字,高32位epoch用来表示leader是否改变,如果有新的leader产生epoch自增;低32位用来事务递增技术,当新的proposal产生,首先会向其他的server发出事务执行请求,如果超过半数能执行并且能执行成功,就会执行。
  • zookeeper对节点的watch监听通知是永久的吗?
    不是,当数据改变的时候,一个Watch事件会产生并且被发送到客户端中,但客户端只会接收到一次这样的通知,如果以后这个数据再次发生改变,之前设置Watch的客户端将不会再次接收到改变的通知。

17、有关基本类型float 、int、double、short

  • 3*0.1==0.3返回false,浮点数不能完全精确的表示出来
  • float f= 3.4是不正确的,Java里没小数点的默认是int,有小数点的默认是double
  • short a=4;a=a+5;会报错,a+=5;不会报错+=会自动向高精度进行数据类型转换
  • String a= new String(“1”+“2”)创建了几个对象?这样说更精确,创建了一个对象,一个常量池常量,一个对对象的指针。

18、HashMap在JDK1.7和JDK1.8有什么区别

  • hash桶链表长度超过8的时候会使用红黑树构建
  • key实现了compare接口才能真正利用红黑树特性
  • 为什么用红黑树,传统方法无法应对构建大量的Hash相同的对象的攻击,而使用红黑树之后不会造成崩溃。

HashMap为什么不是线程安全的?

多个线程同时put的时候可能造成某个key值Entry Key List的死循环,进而导致cpu100%,当另外一个线程get这个EntryList死循环的key的时候也会一直执行,导致线程越来越多,终极原因就是导致了死循环而不是死锁。
多个线程同时put可能导致两个线程同时做rehash当调用到transfer方法的时候可能出现闭环

19、数据库索引及其实现方式

A:索引有哪些优缺点?

  • 加快数据检索速度
  • 唯一索引保证数据库表中每行数据的唯一性
  • 加速表和表之间的连接
  • 额外占用物理存储空间
  • 创建索引和维护索引要花费一定时间
  • 对表进行更新操作时,索引需要被重建

B:索引类型

  • 唯一索引:Unique,create unique index stusNo on student,表明每条索引只对应唯一的数据记录,又包含单列唯一索引和多列唯一索引
  • 主键索引:primary key,唯一索引的特定类型,每个主键都会被创建索引
  • 聚集索引:cluster,表示行的物理顺序与键值的逻辑顺序相同

C:索引的实现方式

  • B+树:多个节点的多叉树,矮胖,Oracle默认索引就是B+树,如果经常需要同时对两个字段进行AND查询,建立一个复合索引是比较有效的。
  • 散列:本质上就是根据键值key进行hash运算
  • 位图索引:男 女 男 男 女对应两个位图,对应男的的10110和对应女的 01001,and或者or查询的时候可以使用按位与和按位或来直接获取结果

20、幻读的理解

  • 幻读不是读到了别人插入的数据,而是T1第一次读到123,T2插入4并提交,T1插入4,报主键冲突!这叫幻读,还有就是T1读123,T2删除了2,T1过来改2,报主键不存在。

21、可重复读是怎么实现不管其他事务是否提交自己读取到的数据都不变的?

A:MVCC机制

  • INNODB的MVCC是通过每行记录后面的两个隐藏的列实现的,这两个列分别保存了事务创建时间和事务删除时间,本质上是事务ID,即版本号。创建时间指的是事务创建操作后记录的事务ID,删除时间指的是事务执行删除操作之后的事务ID。
  • Innodb只会查找版本号小于等于当前版本号的数据行,即读取到的数据要么是开启事务之前存在的,要么是在当前事务插入或者修改得到的
  • 行的删除版本号要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行在事务开始之前未被删除;如果删除版本号小于当前版本号呢?证明事务开始之前已经有别的事务处理了这一行。
    参见https://blog.csdn.net/whoamiyang/article/details/51901888

22、Serializable是给表加表锁吗?

Serializable是对同一行记录不管是增删改查都不能同时进行,加的是行锁并不是表锁!

  • 不是这样的,t1如果按照索引只查询某一行,那么就给该行加上共享锁。t2此时可以更新此行以外的其他行,也可以插入一行!t2做操作之前会对相关行加排它锁。
  • 对于检索条件不是索引的查询和更新等操作,会对整个表进行加锁


    Java常见问题记录
    image.png

    Java常见问题记录
    image.png

23、synchronized的实现原理

同步代码块是通过使用monitorenter和monitorexit指令实现的,同步方法依靠的是方法修饰符上的ACC_SYNCHRONIZED实现的

24、Zookeeper选举流程

A:几个重要指标

  • 服务器ID,编号越大算法中权重越大
  • 数据ID,编号越大说明越新,算法中权重越大
  • 逻辑时钟,同一轮投票过程中每个节点逻辑时钟相同,偷完一轮数据会增加
  • 选举状态,Looking(竞选) FOLLOWING(随从状态,同步leader状态,参与投票),OBSERVING(观察状态,同步Leader状态,不参与投票),LEADING

B:选举消息内容

投票完成之后,需要把投票信息发送给集群中所有服务器,内容为
服务器ID,数据ID,逻辑时钟,选举状态

C:初始启动选举

  • 服务器1启动,给自己投票,广播投票信息,其他服务器没启动,所以状态处于LOOKING
  • 服务器2启动,给自己投票,接收服务器1广播的投票信息,比较之后发现自己的编号答,所以自己胜出,但此时投票没有大于半数,两个服务器处于LOOKING
  • 服务器3启动,给自己投票,与1,2交换信息,3大3胜出,投票结果1,2,3都选了3作为leader,因为大雨半数了
  • 服务器4启动,给自己投票,与1,2,3交换信息,尽管4大,但是服务器3已经胜出,所以4服从当小弟,把自己投票结果改为3是leader
  • 服务器5启动,同样认同3位leader

D:崩溃恢复选举

  • 服务器3作为leader 崩溃,进入重新选举流程
  • 服务器1发送投票,选举自己为leader,自己的事务ID为200_1000,投票轮次为第1轮
  • 服务器2收到1的投票后由于自己的zxid比较大,所以选自己为leader,服务器1收到之后把leader改为2
  • 服务器4收到1的投票和2的投票由于自己的zxid比较大,所以选自己为leader,发回给1,2。两个服务器确认之后选4位leader
  • 服务器5接收到了,但是投票已经超过半数了,所以服从4领导

E:3台挂1台能工作吗?挂2台呢?

  • 记住一条原则,过半即可用,只要大于3/2即可用

F:集群支持动态添加机器码?

  • 全部重启或者挨个重启,不支持动态扩容

25、mybatis原理

26、Https原理

  • C端发送请求到S端发现需要第三方证书
  • C端从第三方下载证书
  • C端携带证书里的公钥再次请求S端
  • S端通过私钥解密公钥内容,把对称加密的秘钥通过私钥进行加密返回给C端
  • C端通过公钥解密S端返回的私钥加密数据获取对称加密秘钥
  • CS通过对称加密秘钥进行数据通信

27、Tomcat原理

28、偏向锁,锁膨胀,锁升级

29、设计模式代码实现

30、redis底层有序集合的实现方式

31、数据结构与算法,二叉树的种类

32、怎么保证切面编程切入的顺序

33、mybatis一二级缓存与sqlsession有什么关系?什么时候开启,什么时候关闭?

34、IOC怎么解决循环依赖?

35、JDK1.8有哪些新特性?