Java面试常见问题

Java面试

一、基础

1.&&和&的区别? |和||的区别?

1.当符号左边是false时,&继续执行符号右边的运算。&&不再执行符号右边的运算。

2.当符号左边是true时,|继续执行符号右边的运算,而||不再执行符号右边的运算

2.final关键字的作用

fianl修饰类时,类不能被继承
fianl修饰方法时,方法不能被重写
fianl修饰变量时,变量不能被修改。他就变成常量了

2+final finally finalize区别

finally

是异常处理语句结构的一部分,表示总是执行,try{}catch(){}finally{}

finalize

是Object类的一个方法,在垃圾收集器执行的时候会调用被回收的对象的finalize方法,供垃圾收集时的其他资源回收,例如关闭文件等

finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作

3.static静态关键字的作用?

被static所修饰的是静态变量,静态变量由多个对象共享,如果修改了静态变量的值,那么其他对象的静态变量也会被修改
static修饰的方法称为静态方法,可以类名.方法名直接进行调用,不用依赖对象实例也可以进行调用。因为static方法独立于任何实例,因此static方法必须被实现,所以不能是抽象的abstract方法
被static修饰的代码块称为静态代码块,会随着java加载类的时候加载这些代码块。他们可以有多个。可以放在类中的任何位置,但是只能初始化一次

4.this关键字,super关键字的用法?

this表示当前对象的引用

  1. this.方法() 调用当前对象中的方法
  2. 形参成员重名,用this来区分
  3. 引用本类的构造方法

super表示父类对象的引用

作用:调用父类的方法、属性、构造器

5.面向对象编程有哪些特点

面向对象三大特征:封装、继承、多态

封装: 通常认为,封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口,封装就是隐藏一切可隐藏的东西,只向外界提供简单的接口

继承: 继承是从已有的类中继承信息创建新类的过程,提供继承信息的类是父类,得到继承信息的类是子类。

多态:多态是指允许不同子类型的对象对同一消息做出不同的响应。(同样的对象调用同样的方法做出了不同分响应)多态分为编译时多态和运行时多态。多态的前提是继承,方法的重写,向上造型

6.子类继承父类时父类的构造方法何时调用

在子类new对象的时候会先调用父类的构造方法,然后再调用子类的构造方法。

7.面向过程与面向对象有什么区别

面向过程就是分析出解决问题所需要的步骤,然后使用函数把这些步骤一步一步的实现,使用的时候一个一个的调用即可。

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。

8.什么是Java的跨平台性?简述其原理

所谓的跨平台性,是指Java语言编写的程序,一次编译之后可以在多个平台上运行。

原理:只需要在要运行Java代码的系统上安装一个JVM即可。由JVM负责Java程序在该系统中的运行。所以Java可以跨平台

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7cNRq3tE-1629779155626)(file:///F:/%E6%A1%8C%E9%9D%A2/Gaoven/%E8%AF%BE%E4%BB%B6/Java%E5%9F%BA%E7%A1%80/01-05.files/image006.jpg)]

9.break、continue、return的区别和作用

break:结束当前循环

continue:跳出本次循环,继续执行下次循环

return:程序返回,不再执行下面的代码

10.i++和++i的区别?

这两个都是自增的操作

i++ 是先使用后自增

++i 是先自增在使用

比如:

int i=1;

int j=i++;

return j;

j=1

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

int i=1;

int j=++i;

return j;

j=2

11.JDK、JRE、JVM有什么区别

JVM=java虚拟机

JRE=Java虚拟机+核心类库 ----------- 是运行Java程序的环境

JDK=Java虚拟机+核心类库 +编译工具

12.列出JAVA中所有基本数据类型,并说明这些数据类型占用的字节数?

Java中的基本数据类型:(八大基本类型)
整型 : byte、short、int、long
浮点型 : float、double
字符型:char
布尔型:boolean

所占字节数:
byte :1字节
short: 2字节
int : 4字节
long : 8字节
float : 4字节
double: 8字节
char: 2字节
boolean: 1字节

13.静态变量和实例变量的区别?

静态变量前要加static 关键字,而实例变量前则不加。

  1. 实例变量是某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。

  2. 静态变量不属于某个实例对象,而是属于类,所以也称为类变量。

  3. 实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。

14.abstract class和interface有什么区别?

abstract class称为抽象类。
什么是抽象类?
我们把用abstract修饰的方法,称为抽象方法。抽象方法没有方法体。而我们把含有抽象方法的类称为抽象类,必须用abstract修饰,这就是 abstract class
interface称为接口
什么是接口?
接口就是特殊的抽象类,里面所有的方法都是抽象方法,没有一般方法。但是在jdk1.8版本后推出了接口中也可以有一般方法,需要用default ,static修饰

二者的区别:

  • 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
  • 构造函数:抽象类可以有构造函数;接口不能有。
  • main 方法:抽象类可以有 main 方法,并且我们能运行它;接口不能有 main 方法。
  • 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
  • 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

14+抽象类能被实例化么?

抽象类其实可以被实例化,但是实例化的方式不是通过new方式来创建对象的,而是通过父类应用指向子类的实例间接地实现父类的实例化,因为在实例化子类之前,一定会实现他的父类。

14++抽象类为什么不能new

  1. 抽象类没有方法体,无法生成一个不具体的对象
  2. 对象实例化的时候,关键字new向JVM申请内存,这个类的成员(成员变量,成员方法)会被保存到内存中。而抽象类没有具体的成员(成员变量,成员方法),没办法准确分配内存
  3. 抽象类是为了实现多态,当某些类只希望作为父类使用,不希望被实例化。当我们进行设计的时候需要尽量依赖父类,越向上层的类越稳定,不容易被实例化
  4. 实例化和有没有构造方法没有关系,构造方法只是初始化对象的,new关键字向JVM申请内存来创建对象的。

15.重写和重载的区别

方法的重载和重写都是多态的实现方式,区别在于重载是编译时多态,重写是运行时多态

重载发生在同一个类中,同名的方法有着不同的参数列表被视为重载

重写发生在子类与父类之间,重写要求子类重写方法与父类的被重写 方法有相同的返回值类型,访问权限大于父类方法

区别:

1.参数列表

重写要求参数列表与被重写方法完全一致

重载要求参数列表中参数的顺序、类型、个数完全不同

2.访问权限,方法的修饰符

重写要求方法权限大于等于被重写方法

重载要求方法名相同即可,没有权限限制

3.异常

重写不能抛出父类没有的一般异常

只能抛出非运行时异常

4.前提

重写的前提是继承,有父子级关系

重载没有前提

16. ==和equals()的区别?

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型比较的是值,引用数据类型比较的是内存地址)。
equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
情况1:类没有覆盖写 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2:类override了 equals() 方法。一般,我们都覆盖 equals() 方法来比较两个对象的内容是否相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。

17.throw和throws的区别

throw 表示抛出一个异常类的对象,生成异常对象的过程。声明在方法体内。
throws 属于异常处理的一种方式,声明在方法的声明处。

18.collection 和collections

clloections是java.util下面的类,它的里面有各种相关集合操作的方法

collection是接口,是各种集合结构的父接口。

19.String类能被继承吗为什么?

不能被继承,因为String类是由fianl修饰的,final修饰的类不能被继承

20.为什么用fianl修饰String?

1.为了实现常量池,节约内存

String有专门的字符串常量池,只有用fianl修饰才能实现常量池。

常量池的存在,没有使用new关键字而产生的String对象,会存储到字符串常量池,当然如果有同样的内容的字符串产生时,将不会再新建对象,直接从常量池中取出来节省了堆内存空间。因为jvm里面,堆是用来存放对象实例的地方。

2.多线程安全

String被fianl修饰后是多线程安全的。同一字符串可以被多个线程共享,多个线程无法改变字符串内容。这就避免了当多个对象指向同一String时如果有一个对象改变了String会对其他的对象产生影响。所以安全。

3.实现String可以创建hashcode不可变性

因为字符串是不可变的,因为他在创建的时候hashcode就被缓存了,不需要重新计算,这就使得字符串很适合做map中的key,字符串的处理速度要快过其他键对象,所有hashmap中往往使用string作为键

21.new()和clone()的区别

A a=new A();

new对象的过程:程序执行到new操作符的时候会看new操作符后面的类型,因为知道了类型才知道分配多大的内存空间。分配完内存空间再调用构造函数填充对象的各个域进行对象的初始化。构造方法返回后,一个对象创建完毕,可以把她的引用地址发送到外部,外部使用这个引用操作对象。

A a1=(A)a.clone()

克隆的第一步与new相似都是分配内存空间。调用克隆方法时分配内存空间与原对象相同,然后使用原对象的各个域填充克隆对象的各个域。clone()返回,一个新的对象就被创建了,内容相同,地址值不同。

22.对象类中如何比较对象中某个值的大小进行排序?

重写Comparable接口和Comparator中的方法

23.Integer值超过127之后发生了什么?

Integer.class在装载(Java虚拟机启动)时,其内部类型IntegerCache的static块即开始执行,
实例化并暂存数值在-128到127之间的Integer类型对象。
当自动装箱int型值在-128到127之间时,即直接返回IntegerCache中暂存的Integer类型对象。
-128-127在缓存里取。否则就返回一个新对象。对象的地址值就不相同了,一个在静态区,一个在对象区

24.什么是浅拷贝和深拷贝?

浅拷贝(Shallow Copy):指向被复制的内存地址,如果地址发生改变,浅拷贝出来的对象也会相应改变。

深拷贝:为对象都开辟了新的内存空间地址,使这个增加的指针指向新的内存

总结:实则浅拷贝和深拷贝只是相对的,如果一个对象内部只有基本数据类型,那用 clone() 方法获取到的就是这个对象的深拷贝,而如果其内部还有引用数据类型,那用 clone() 方法就是一次浅拷贝的操作。

25…什么是序列化与反序列化?

作用:在网络上传输对象字节序列,把对象字节保存到硬盘上。减少内存消耗。或者在远程rpc调用对象的时候需要。

序列化就是把对象存储到硬盘上,反序列化就是获取对象文件中的数据,转成后台对象类的数据。

26.说一下JDBC链接数据库的过程

  1. 加载驱动

    Class.forName("com.mysql.jdbc.Driver");
    原来是DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    driver底层已经帮忙注册了
        public Driver()throws SQLException{}
    	static{
            try{
                DriverManager.registerDriver(new Driver());
            }catch(SQLException var){
                throw new RuntimeException("Can't register driver!")
            }
        }
    
  2. 获取连接

    Connection con=DriverManager.getConnection(url,name,pwd);
    
  3. 获取传输器

    Statement st=con.createStatement();
    String sql="insert usered('id','name','password') values(?,?,?)"
    preparedStatement ps=con.prepareStatement(sql);//预编译SQL
    //手动给参数赋值
    ps.setInt(1,6);
    ps.setString(2,"小棉袄");
    ps.setString(3,"123");
    
  4. 执行sql

    Result rs=st.executeQuery(sql);
    
  5. 获取结果集,处理结果集

    while(rs.next()){System.out.println(rs.getObject(""))}
    
  6. 关闭资源

DBCP-C3P0连接池

数据库连接-执行完毕-释放,连接释放十分浪费系统资源

池化技术:准备一些预先的资源,过来就连接预先准备好的

最小连接数:10

最大连接数:15

等待超时:100ms

编写连接池,实现一个接口DataSource

开源数据源实现

DBCP

导入jar包到lib

src下创建文件dbcpconfig.properties

C3P0

导入jar到lib

src下配置c3p0-config.xml文件

DBCP和C3P0的区别:

C3P0有自动回收空闲连接的功能,DBCP没有

结论:无论使用什么数据源,本质还是一样,DataSource接口不会变,方法就不会变

Druid:阿里巴巴

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

操作字符串的类有:String、StringBuffer、StringBuilder。

String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

28.String str="i"与 String str=new String(“i”)一样吗?

不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。

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

  • indexOf():返回指定字符的索引。
  • charAt():返回指定索引处的字符。
  • replace():字符串替换。
  • trim():去除字符串两端空白。
  • split():分割字符串,返回一个分割后的字符串数组。
  • getBytes():返回字符串的 byte 类型数组。
  • length():返回字符串长度。
  • toLowerCase():将字符串转成小写字母。
  • toUpperCase():将字符串转成大写字符。
  • substring():截取字符串。
  • equals():字符串比较

一Plus前端

1.浏览器发出一个请求到收到响应经历了哪些步骤?

  1. 浏览器解析用户输入的URL,生成一个HTTP格式的请求
  2. 先根据URL域名从本地hosts文件中查找是否有映射IP,如果没有就将域名发送给电脑所配置的DNS进行域名解析,得到IP地址
  3. 浏览器通过操作系统将请求通过四层网络协议发送出去
  4. 途中可能会经过各种路由器、交换机,最终到达服务器。
  5. 服务器收到请求后,根据请求所指定的端口,将请求传递给绑定了该端口的应用程序,比如8080被Tomcat占用了
  6. Tomcat接收到请求数据后,按照HTTP协议的格式进行解析,解析得到所要访问的servlet
  7. 然后servlet来处理这个请求,如果是springMVC中的DispatchServlet,那么会找到对应的Controller中的方法,并执行该方法得到结果
  8. Tomcat得到响应结果后封装成HTTP响应的格式,并再次通过网络发送给浏览器所在的服务器
  9. 浏览器所在的服务器拿到结果后再传递给浏览器,浏览器则负责解析并渲染

2.跨域请求是什么?有什么问题?如何解决?

跨域请求是指浏览器在发起网络请求时,会检查该请求所对应的的协议、域名、端口和当前网页是否一致,如果不一致则浏览器会进行限制,比如在www.baidu.com的某个网页中,如果使用ajax去访问www.jd.com是不行的,但如果是img,ifram,script等标签的src属性去访问则是可以的,之所以浏览器要设置这层限制,是为了用户信息安全,但是如果开发者想要绕过这层限制也是可以的:

  1. response添加header,被逼入resp.setHeader(“Access-Control-Allow-Origin”,"*")表示可以访问所有网站,不受是否同源的限制
  2. jsonp的方式,该技术底层就是基于script标签来实现的,因为script标签是可以跨域的
  3. 后台自己控制,先访问同域名下的接口,然后在接口中再去使用HTTPClient等工具去调用目标接口
  4. 网关,和第三种方式类似,都是交给后台服务来进行跨域访问

二、JVM

img

数据区

jvm中那些是线程共享区

堆区和方法区是所有线程共享的,栈,本地方法栈,程序计数器是每个线程独有的

1.Java程序运行机制详细说明

  • 首先利用IDE集成开发工具编写Java源代码,源文件的后缀为.java;
  • 再利用编译器(javac命令)将源代码编译成字节码文件,字节码文件的后缀名为.class;
  • 运行字节码的工作是由解释器(java命令)来完成的。

java文件通过编译器变成了.class文件,接下来类加载器又将这些.class文件加载到JVM中。
其实可以一句话来解释:类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class对象,用来封装类在方法区内的数据结构。

2.JVM运行时数据区

1、堆
JVM中最大的一块,主要用来存放对象实例和数组,几乎所有的对象实例都在这里分配内存。线程共享,内部会划分出多个线程私有的分配缓冲区(TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。

2、虚拟机栈
每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。线程私有,生命周期和线程一致。

3、方法区(非堆)
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

4、本地方法栈
本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。

5、程序计数器
程序计数器(Program CounterRegister)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域

3.说说堆和栈的区别

1)物理地址
堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,
所以有各种算法。比如,标记-消除,复制,标记-整理,分代(即新生代使用复制算法,老年
代使用标记——压缩)
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
2)内存分配
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大
于栈。
栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
3)存放的内容
堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。栈区更关注的是程序方法的执行。
4)程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。

4.引用类型

对象引用类型分:强引用、软引用、弱引用和虚引用。

强引用:不会被回收。就是我们一般声明对象是时虚拟机生成的引用,强引用环境下,垃圾回收时需要严格判断当前对象是否被强引用,如果被强引用,则不会被垃圾回收。

软引用:内存充足,则不回收;不足则回收。软引用一般被做为缓存来使用。与强引用的区别是,软引用在垃圾回收时,虚拟机会根据当前系统的剩余内存来决定是否对软引用进行回收。如果剩余内存比较紧张,则虚拟机会回收软引用所引用的空间;如果剩余内存相对富裕,则不会进行回收。换句话说,虚拟机在发生OutOfMemory时,肯定是没有软引用存在的。

弱引用:会被回收。弱引用与软引用类似,都是作为缓存来使用。但与软引用不同,弱引用在进行垃圾回收时,是一定会被回收掉的,因此其生命周期只存在于一个垃圾回收周期内。

GC

主流的有三种垃圾回收算法:复制算法,标记-清除算法、标记-整理算法

1.JVM中那些可以作为gc root

gc root,特征,就是她指挥引用其他对象,而不会被其他对象引用,例如栈中的本地变量,方法区中的静态变量,本地方法栈中的变量,正在运行的线程等可以作为gc root

2.项目中如何排查JVM问题

对于还在正常运行的系统:

  1. 可以使用jmap来查看JVM中各个区域的使用情况
  2. 可以通过jstack来查看线程的运行情况,比如那些线程阻塞,是否出现死锁
  3. 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc比较频繁,那就得进行调优了
  4. 通过各个命令的结果,或者jvisualvm等工具来进行分析
  5. 首先,初步猜测频繁发生fullgc的原因,如果频繁发生fullgc但是又一直没有出现内存溢出,那么表示fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收,避免这些对象进入到老年代,对于这种情况,就要考虑这些存货时间不长的对象是不是比较大,导致年轻代放不下,直接进入到老年代,尝试加大老年代的大小,如果改完后,fullgc减少,则证明修改有效。
  6. 同时,还可以找到占用CPU最多的线程,定位到具体的方法,优化这个方法的执行,看是否能避免某些对象的创建,从而节省内存

对于已经发生OOM的系统:

  1. 一般生产系统都会设置当系统发生OOM时,生成当时的dump文件
  2. 可以用jsisualvm等工具来分析dump文件
  3. 根据dump文件找到异常的实例对象,和异常的线程(占用CPU高),定位到具体的代码
  4. 然后在进行详细的分析和调试

总之调优不是一蹴而就的,需要分析、推理、实践、总结、再分析,最终定位到具体的问题

5.JVM有多种垃圾回收算法,其中目前在用最经典的就是分代收集算法。

永久代(Perm):主要保存class,method,field等对象,该空间大小,取决于系统启动加载类的数量,一般该区域内存溢出均是启动时溢出。java.lang.OutOfMemoryError: PermGen space
老年代(Old):一般是经过多次垃圾回收(GC)没有被回收掉的对象。
伊甸园(Eden):新创建的对象。
幸存区0(Survivor0):经过垃圾回收(GC)后,没有被回收掉的对象。
幸存区1(Survivor1):同Survivor0相同,大小空间也相同,同一时刻Survivor0和Survivor1只有一个在用,一个为空。

6.标记-清除算法

首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。缺点:标记和清除两个过程效率都不高;标记清除后会产生空间碎片,空间碎片导致分配较大对象时可能提前触发垃圾回收。

img

7.复制算法

将可用内存分为两个区域,每次只使用其中一块,当使用的那一块内存用完时,将还存活的对象复制到另外一块内存中,然后把已使用过的内存空间一次清理掉。优点:解决的空间碎片问题,实现简单。缺点:需要两倍空间,将内存缩小为两块,内存使用率不高。复制操作频繁效率变低。

img

8.标记-整理算法

此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。

img

9.垃圾收集器CMS G1

垃圾回收策略可以看作是内存回收的抽象策略,而垃圾收集器是内存回收的具体实现

垃圾收集器有很多种,常见的有:串行收集器、并行收集器、并发收集器、CMS收集器以及最新的G1收集器。重点为CMS收集器和G1收集器

10.CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器

基于 标记清除 算法实现。第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

特点:

针对老年代
基于"标记-清除"算法(不进行压缩操作,会产生内存碎片)
以获取最短回收停顿时间为目标
并发收集、低停顿
需要更多的内存
运作步骤:

初始标记: 暂停所有的其他线程,标记GC Roots能直接关联到的对象,速度很快;
并发标记:进行GC Roots Tracing的过程;
重新标记: 修正并发标记期间的变动部分,需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫,回收所有的垃圾对象。
缺点:

对 CPU 资源敏感;
无法收集浮动垃圾;
标记清除 算法带来的空间碎片。

11.G1收集器

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器。以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。

G1是将整个堆空间分成许多个大小不等的独立区域(Region),大约有2000块,每个Region从1M到32M大小不等,在JVM启动的时候就已经分割好了,Region可采用并行的垃圾回收或 NOT STW 方式。

运作步骤:

初始标记(Initial Marking)
并发标记(Concurrent Marking)
最终标记(Final Marking)
筛选回收(Live Data Counting and Evacuation)
特点:

1、并行与并发

G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

2、分代收集

虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。

能独立管理整个GC堆(新生代和老年代),而不需要与其他收集器搭配;
能够采用不同方式处理不同时期的对象;
虽然保留分代概念,但Java堆的内存布局有很大差别;
将整个堆划分为多个大小相等的独立区域(Region);
新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合。
3、空间整合

与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。是一种类似火车算法的实现,不会产生内存碎片,有利于长时间运行。

4、可预测停顿

这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型。可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒。在低停顿的同时实现高吞吐量。

为什么G1可以实现可预测停顿?

可以有计划地避免在Java堆的进行全区域的垃圾收集;
G1收集器将内存分大小相等的独立区域(Region),新生代和老年代概念保留,但是已经不再物理隔离。
G1跟踪各个Region获得其收集价值大小,在后台维护一个优先列表;
每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);

类加载器

12.类加载器,加载类的流程

类加载器实现的功能是:为加载阶段获取二进制字节流

1、加载
加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。

在加载阶段,JVM需要完成3件事:

通过类的全限定名获取该类的二进制字节流;
将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2、连接
2.1 验证

验证阶段,确保加载进来的字节流符合JVM规范。将完成以下4个阶段的检验动作:

文件格式验证
元数据验证(是否符合Java语言规范)
字节码验证(确定程序语义合法,符合逻辑)
符号引用验证(确保下一步的解析能正常执行)
2.2 准备

准备阶段,为静态变量在方法区分配内存,并设置默认初始值。

2.3 解析

解析阶段,是虚拟机将常量池内的符号引用替换为直接引用的过程。

3、初始化
初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。

(注:当有父类且父类为初始化的时候,先去初始化父类;再进行子类初始化语句)
img

类加载器之间的这种层次关系叫做双亲委派模型

13.双亲委派模型

双亲委派模型要求除了顶层的启动类加载器(Bootstrap ClassLoader)外,其余的类加载器都应当有自己的父类加载器。这里的类加载器之间的父子关系一般不是以继承关系实现的,而是用组合实现的。

JVM中存在三个默认的类加载器:

  1. BootstrapClassLoader
  2. ExtClassLoader
  3. AppClassLoader

AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父类加载器是BootStrapLoader

JVM在加载一个类时,会调用AppClassLoader方法加载这个类,不过在这个方法中,会先使用ExtClassLoader的LoadClass方法来加载类,同样ExtClassLoader的loadCLass方法中会先使用BootstrapClassLoader来加载类,如果BootstrapClassLoader加载到了就直接成功,如果BootStrapClassLoader没有加载到,那么ExtClassLoader就会自己尝试加载,如果没有加载到,那么就会申请AppClassLoader来加载这个类

所以双亲委派是指JVM在加载类时,会委派给Ext和Bootstrap进行加载,如果没有加载到才自己进行加载

14.双亲委派模型的工作过程

如果一个类接受到类加载请求,他自己不会去加载这个请求,而是将这个类加载请求委派给父类加载器,这样一层一层传送,直到到达启动类加载器(Bootstrap ClassLoader)。

只有当父类加载器无法加载这个请求时,子加载器才会尝试自己去加载。

15.为何要双亲委派机制(避免同一个类加载出不同的结果使得程序混乱)

为何要采用双亲委派机制呢?了解为何之前,我们先来说明一个知识点:判断两个类相同的前提是这两个类都是同一个加载器进行加载的,如果使用不同的类加载器进行加载同一个类,也会有不同的结果。

如果没有双亲委派机制,会出现什么样的结果呢?比如我们在rt.jar中随便找一个类,如java.util.HashMap,那么我们同样也可以写一个一样的类,也叫java.util.HashMap存放在我们自己的路径下(ClassPath).那样这两个相同的类采用的是不同的类加载器,系统中就会出现两个不同的HashMap类,这样引用程序就会出现一片混乱。

16.Tomcat中为什么要使用自定义类加载器

一个Tomcat可以部署多个应用,而每个应用中都存在很多类,并且各个应用中的类是独立的,全类名是可以相同的,一个Tomcat不管内部部署了多少个应用,Tomcat启动之后就是一个Java进程,也就是一个JVM,所以如果Tomcat中只存在一个类加载器,比如默认的AppClassLoader,那么久只能加载一个类,这是有问题的,而在Tomcat中,会为部署的每个应用都生成一个类加载器,名字叫做WebAppClassLoader,这样Tomcat中每个应用就可以使用自己的类加载器去加载自己的类,从而达到应用之间的类隔离,不出现冲突,另外Tomcat还利用自定义加载器实现了热加载功能。

17.Tomcat如何进行优化

对于Tomcat调优,可以从两个方面来进行调整:内存和线程

首先启动Tomcat,实际上就是启动了一个JVM,所以可以按JVM调优的方式来进行调整,从而达到Tomcat优化的目的

另外Tomcat中设计了一些缓存区,比如AppReadBufSize、BufferPoolSize等缓冲区来提高吞吐量。

还可以调整Tomcat的线程,比如调整MinSpaceThreads参数来改变Tomcat空闲时的线程数,调整MaxThreads参数来设置Tomcat处理连接的最大线程数

并且还可以调整IO模型,比如使用NIO、APP这种相比如BIO更加高效的IO模型

三、集合

1.数组和集合的区别

  1. 数组初始化之后长度就固定了,集合不固定
  2. 数组中只能存放同一种数据类型。集合中可以存放多种
  3. 数组中只能存放有序的元素,可以添加重复元素。集合可以无序,不可以出现无序的元素。

2.ArrayList 1.7版本和1.8版本底层扩容的实现原理

通过一个空参的构造器创建对象时1.7底层创建了长度是10的数组。当我们向集合中添加第11个元素时,底层会进行扩容,扩容为原来数组长度的1.5倍。同时将原数组中的内容复制新的数组中。 元素要求所在的类必须重写equals方法(饿汉式)

1.8中并没有直接就创建长度为10的数组,调用add的时候才创建。(懒汉式)

3.collection接口下有什么常用的子类集合,说说底层原理

有List 和 set

list下面有

​ arrayList :底层是数组,线程不安全,查找快增删慢

​ LinkedList:底层是双向链表。查找慢,增删插入快

​ Vector:底层是object数组,线程安全效率低

set下面有

​ HashSet :是Set的主要实现类,线程不安全,底层是hashMap。初始容量和加载因子都和hashMap一样。

​ treeSet:可以按照对象的指定属性进行排序

​ LinkedHashSet:是按照添加元素的顺序进行遍历,因为底层维护了一张链表用来记录添加的顺序

3+CopyOnWriteArrayList的底层原理是怎样的

  1. 首先CopyOnWriteArrayList内部也是用数组来实现的,在向CopyOnWriteArrayList添加元素时,会复制一个新的数组,写操作在新的数组上进行,读操作在原数组上进行
  2. 写操作会枷锁,防止出现并发写入丢失数据的问题。
  3. 写操作结束之后会把原数组指向新数组
  4. CopyOnWriteArrayList允许在写操作时来读取数据,大大提高了读的性能,因此适合读多写少的应用场景,但是CopyOnWriteArrayList会比较占用内存,同时可能独到的数据不是实时最新的数据,所以不适合实时性能要求很高的场景

4.说一下map接口中的常用子类。以及底层实现原理

Map分为hashmap和linkedhashmap,hashtable,treemap,

HashMap:是线程不安全的,可以存放null的key和vaule,如果是null默认从第0个查找。

LinkedHashMap:和Hashmap一样,只是可以按照元素添加的顺序进行遍历。底层用双向链表来记录元素添加顺序。

​ 存储数据保持进入的顺序与被取出的顺序一致

HashTable:是线程安全的。不可能存null。但是Hashtable是表级锁底层方法都是使用synchronized来保证线程安全,而ConcurrentHashMap是段级锁

TreeMap:添加的key-vaule对进行排序,可以考虑订制排序和自然排序,实现Comparator和 Comparable接口。

5.ArrayList 和 Vector 的区别

  1. Vector与ArrayList一样,也是通过数组实现的,Vector类的所有方法都是同步的。它也是线程安全的,而Arraylist是线程异步(ASynchronized)的,是不安全的。如果不考虑到线程的安全因素,一般用Arraylist效率比较高。
  2. 使用ArrayList时,如果不指定大小,会生成一个空的数组;使用Vector时,如果不指定大小,会默认生成一个10个元素大小的数组
  3. 扩容方面。Vector默认在大小为0的时候就会扩容,第一次扩容容量是2倍,如果还达不到要求,则直接扩容到指定容量

6.ArrayList和LinkedList 的区别和联系

LinkeList 和 ArrayList 都实现了List接口,但是LinkeList还额外实现了Deque接口。

LinkeList 底层是链表,因为实现的Deque接口,所以她还是一个双端队列。链表的结构支持她高效的插入和删除。对查找方法很低效。但是实现的Deque接口的双端队列会一直维护她的头指针和尾指针,所以她对于头部和尾部的查找还是很高效的。

  1. ArrayList和LinkedList都是线程不安全的

  2. 底层数据结构不同,ArrayList 是数组,LinkedList 是链表

  3. 插入和删除受影响。ArrayList 采用数组存储插入删除的时间复杂度受位置的影响近似为 O(n)。

    ​ LinkeList采用链表结构,插入删除不受位置影响,时间复杂度为O(1)

  4. ArrayList支持快速访问随机位置,LinkedList不支持高效访问

  5. 内存空间占用问题:ArrayList空间浪费,分配的内存空间即使用了一点点,剩下的空间也会占着。

7.Set不能存放重复元素,其底层是如何实现的

hashcode() 和 equals()…hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)。
当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相同的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象。如果equals方法返回为true,HashSet 就不会让其加入操作成功。如果返回false,就会重新散列到其他位置。

8.HashMap的数据结构

在Java1.8之前 HashMap底层是 数组+链表

在Java1.8之后 HashMap底层是 数组+链表+红黑树。链表长度超过8的时候链表转化成红黑树

8+HashMap的扩容机制原理

1.7版本

  1. 先生成新数组
  2. 遍历老数组中的每一个位置上的链表上的每个元素
  3. 取每个元素的key,并给予新数组长度,计算出每一个元素在新数组中的下标
  4. 将元素添加到新数组中去
  5. 所有元素转移完后,将新数组赋值给HashMap对象的table属性

1.8版本

  1. 先生成新数组

  2. 遍历老数组中的每个位置上的链表或红黑树

  3. 如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中去

  4. 如果是红黑树,则先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置

    a. 统计每个下标位置的元素个数

    b.如果该位置下标的元素个数超过了8,则生成一个新的红黑树,并将根节点添加到新数组的对应位置

  5. 所有元素转移完了之后,将新数组赋值给HashMap对象的table属性。

9.HashMap中put方法的过程?

  • 调用哈希函数获取Key对应的hash值,再计算其数组下标;
  • 如果没有出现哈希冲突,则直接放入数组;如果出现哈希冲突,则以链表的方式放在链表后面;
  • 如果链表长度超过阀值(TREEIFYTHRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表;
  • 如果结点的key已经存在,则替换其value即可;
  • 如果集合中的键值对大于12,调用resize方法进行数组扩容。

10-HashMap为什么不使用二叉查找树和平衡二叉树?

红黑树:是许多二叉查找树中的一种,他能保证在最坏的情况下,基本动态操作时间为O(lgn)

问题一:为什么不使用二叉排序树?

问题主要出现在二叉排序树在添加元素的时候极端情况下会出现线性结构。

举例说明:由于二叉排序树左子树所有节点的值均小于根节点的特点,如果我们添加的元素都比根节点小,会导致左子树线性增长,这样就失去了用树型结构替换链表的初衷,导致查询时间增长,所以这是不用二叉查找树的原因

问题二:为什么不使用平衡二叉树?

红黑树不追求“完全平衡”,他只要求部分达到平衡,但是提出了为节点增加颜色,红黑使用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,而AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同的情况,旋转得次数比红黑树要多。

红黑树读取略逊于AVL,维护强于AVL,空间开销与AVL类似,内容几多时略优于AVL,维护优于AVL

10.Hashmap为什么使用红黑树,红黑树是什么?

因为链表长了就会查询慢的问题,红黑树相当于排序数据。可以自动的使用二分法进行定位。性能较高。

红黑树是一种平衡二叉查找树,他的节点是红色或者黑色,根节点是黑色,每个叶子节点都是黑色的空节点,红色的节点他的子节点都是黑色,他可以用变色和旋转来调整平衡

11.hashmap如何解决hash冲突问题

1)开放定址法:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的 地址则表明表中无待查的关键字,即查找失败。

2) 再哈希法:同时构造多个不同的哈希函数。

3)链地址法:将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

4)建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。

12.拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?

之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持"平衡"是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

13.说说你对红黑树的见解?

  • 1、每个节点非红即黑
  • 2、根节点总是黑色的
  • 3、如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
  • 4、每个叶子节点都是黑色的空节点(NIL节点)
  • 5、从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

14.你知道hash的实现吗?为什么要这样实现?

JDK1.8中,是通过hashCode()的高16位异或低16位实现的:(h=k.hashCode())^(h>>>16),主要是从速度,功效和质量来考虑的,减少系统的开销,也不会造成因为高位没有参与下标的计算,从而引起的碰撞。

15.HashMap和HashTable有什么区别?

  • ①、HashMap是线程不安全的,HashTable是线程安全的;
  • ②、由于线程安全,所以HashTable的效率比不上HashMap;
  • ③、HashMap最多只允许一条记录的键为null,允许多条记录的值为null,而HashTable不允许;
  • ④、HashMap默认初始化数组的大小为16,HashTable为11,前者扩容时,扩大两倍,后者扩大两倍+1;
  • ⑤、HashMap需要重新计算hash值,而HashTable直接使用对象的hashCode;

16-ConcurrentHashMap的扩容机制

1.7版本

  1. ConcurrentHashMap是基于Segment分段实现的
  2. 每个Segment相当于一个小型的HashMap
  3. 每个Segment内部会进行扩容,和HashMap的扩容逻辑类似
  4. 先生成新数组,然后转移元素到新数组中
  5. 扩容的判断也是每个Segment内部单独判断的,判断是否超过阈值

1.8版本

  1. 不再基于Segment实现
  2. 当某个线程进行put时,如果发现ConcurrentHashMap正在进行扩容,那么该线程一起进行扩容
  3. 如果某个线程Put时,发现没有正在进行扩容,则将key-value添加到ConcurrentHashMap中,然后判断是否超过阈值,超过了则进行扩容
  4. ConcurrentHashMap是支持多个线程同时扩容的
  5. 扩容之前也先生成一个新数组
  6. 在转移元素时,先将元素分组,将每组分给不同的线程进行元素的转移,每一个线程负责一组或多组的元素转移工作

16.HashMap 和 ConcurrentHashMap 的区别

  1. ConcurrentHashMap对整个桶数组进行了分割分段(Segment),然后在每一个分段上都用lock锁进行保护,相对于HashTable的synchronized锁的粒度更精细了一些,并发性能更好,而HashMap没有锁机制,不是线程安全的。(JDK1.8之后ConcurrentHashMap启用了一种全新的方式实现,利用CAS算法。)
  2. HashMap的键值对允许有null,但是ConCurrentHashMap都不允许。

17.ConcurrentHashMap 和 Hashtable 的区别?

ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。

  • 底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
  • 实现线程安全的方式(重要): ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

img

img

18.在多线程环境下使用HashMap会有什么问题?在什么情况下使用get方法会产生无限循环?

比如,在一个线程例初始化了一个HashMap然后在多个其他线程里对其进行读取,这肯定没有任何问题。有个例子是使用HashMap来存储系统配置项,当有多于一个线程对HashMap进行修改操作的时候才会真正产生问题,比如增加、删除、更新键值对的时候。

因为put操作可以造成重新分配存储大小(re-sizeing)的动作,因此有可能造成无限循环的发生,所以这时需要使用HashTable或者ConcurrentHashMap,而后者更优。

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

img

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

Java中的Iterator功能比较简单,并且只能单向移动:

(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

(2) 使用next()获得序列中的下一个元素。

(3) 使用hasNext()检查序列中是否还有元素。

(4) 使用remove()将迭代器新返回的元素删除。

Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

20. Iterator 和 ListIterator 有什么区别?

  • Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
  • Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
  • ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

四、线程

1.什么是线程?

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。

2.线程和进程有什么区别?

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。

不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间

3.线程的五大状态?线程的生命周期

创建状态(New):用三种方式创建线程,还没有调用start。
就绪状态(Runnable):在创建了线程之后,调用Thread类的start()方法来启动一个线程,即表示线程进入就绪状态。
运行状态(Running):当线程获得CPU时间,线程才从就绪状态进入到运行状态
阻塞状态(Blocked):运行中的线程进入阻塞状态,如调用sleep()方法让线程睡眠,调用wait()方法让线程等待等。
终止状态(Dead):当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。

4.如何在Java中实现线程

  • 继承java.lang.Thread 类
  • 实现Runnable接口
  • 实现Callable<>接口
  • 通过线程池创建线程

5.用Runnable还是Thread?

大家都知道我们可以通过继承Thread类或者调用Runnable接口来实现线程,问题是,那个方法更好呢?什么情况下使 用它?这个问题很容易回答,如果你知道Java不支持类的多重继承,但允许你调用多个接口。所以如果你要继承其他类,当然是调用Runnable接口好了。

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

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启 动,start()方法才会启动新线程

7.Java中Runnable和Callable有什么不同?

Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在 JDK1.5增加的。它们的主要区别是Callable的 call() 方法可以返回值(Future对象)和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

  • Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

8.什么是线程池? 为什么要使用它?

概念:创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时 候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

**思路:**提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回线程池中。可以避免频繁创建销毁,实现重复利用

好处:

  1. 提高响应速度(减少了储藏间新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理

JDK1.5提供了线程池相关API:ExecutorService和Executors

  • ExecutorService:真正的线程池接口。常见的子类ThreadPoolExecutor
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。

8.1线程池的参数配置

第一个参数为corePoolSize 核心线程数

第二个参数为maximumPoolSize 最大线程数

第三个参数为keepAliveTime 线程的保存时间

第四个参数为queue:用来定义等待任务队列的属性

8.2线程池的底层工作原理

线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:

  1. 如果此时线程池中的线程数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务
  2. 如果此时线程池的数量等于corePoolSize,但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
  3. 如果此时线程池中的线程数量大于等于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建立新的线程来处理被添加的任务
  4. 如果此时线程池中的线程数大于coolSizePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过handler所指定的策略来处理此任务。
  5. 当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime(过期时间),线程将终止,这样,线程池可以动态的调整池中的线程数。

8+ThreadLocal的底层原理

  1. ThreadLocal是java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻,任意方法中获取缓存的数据
  2. ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
  3. 如果在线程池中使用ThreadLocal会造成内存溢出,因为当ThreadLocal对象使用完后,应该要把设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法就是在使用ThreadLocal对象之后,手动调用Thread的remove方法,手动清除Entry对象。
  4. ThreadLocal经典的应用场景就是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享同一个连接)

9.Java中notify 和 notifyAll有什么区别?

notify()方法不能唤醒某个具体的线程,所以只有一个线程在等 待的时候它才有用武之地。而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。

10.如何避免死锁?

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。这是一个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:

互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。

11.什么是线程安全?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量 的值也和预期的是一样的,就是线程安全的。

12.多线程是什么?

在介绍多线程的时候,我们首先要知道什么是线程,而要了解线程还要了解进程。
1.进程:一个正在执行中的程序,每个进程执行都有一个执行顺序
2.线程:进程中的一个独立控制单元,线程在控制进程的执行。一个进程中至少有一个线程。
3.多线程:一个进程中不只有一个线程。

13.我们为什么要用多线程呢?

  1. 为了更好地利用CPU的资源,如果只有一个线程,我们有多个任务的时候必须等着上一个任务完成才能进行。多线程则不用等待可在主线程执行的同时执行其他任务。(可同时执行多个线程效率高)
  2. 进程之间不可以共享数据,但是 线程可以。(可共享数据)
  3. 系统创建进程需要为该进程重新分配系统资源,创建线程代价少。
  4. Java语言内置了多线程功能支持,简化了Java多线程编程。

13+什么场景用到多线程

1.定期更新配置文件,任务调度,一些监控用于定期信息采集等

2.tomcat内部采用的就是多线程,上百个客户端访问同一个web应用,tomcat接入后都是把后续的处理交给一个新的线程来处理,这个新的线程最后调用到我们servlet程序,比如doGet或者doPost方法。如果不采用多线程机制,上百个人同时访问一个web应用的时候,tomcat就得排队串行处理,访问速度非常慢。

3.需要异步处理的时候,需要使用多线程,比如taskA和taskB,单线程只能串行处理,先taskA,然后再做taskB,如果想要多个task同时执行,就必须为每个task分配一个线程,然后通过java虚拟机的线程调度,来同时执行多个任务。比如你的CPU是多核的话,就可以让一个CPU执行一个线程。如果只有一个CPU的话,底层是按照分时复用原则,各个线程按照时间片来获得CPU资源。

4.特别好使的操作,如备份数据库,可以开多个线程执行备份,然后执行返回

项目中多线程的例子

1.后台任务,例如:定时向大量用户发送邮件

2.异步处理,例如:发微博,记录日志等

14.什么是悲观锁

总是假设最坏的情况,每次拿数据的时候都会认为别人会修改,所以在每次拿数据的时候都会上锁,这样别人想要拿这个数据的时候就会因为加锁了而阻塞,直到他拿到锁。

悲观锁核心思想:共享资源每次只给一个线程使用,其他进程阻塞,用完后再把资源转让给其他的进程。

关系型数据库很多都用到了悲观锁机制,Java中synchronized 和 ReetrontLock 是悲观锁

15.什么是乐观锁

总是假设最好的情况,每次拿数据的时候都会认为别人不会修改,所以不会上锁。但是在更新的时候回先判断一下

别人有没有更新这个数据,可以使用 版本号控制机制和CAS算法实现,乐观锁适用于多读的应用类型,这样可以提高吞吐量。

16.两种锁的使用场景

乐观锁适用于少写多读的情况,多读冲突少可以省去锁的开销提高吞吐量,多写的情况使用悲观锁比较安全

17.乐观锁的版本号机制(Redis中使用)

一般在数据表中加上一个数据版本号的字段version,表示数据被修改的次数,当数据被修改的时候version+1,当数据更新的时候,读取数据的同时也会读取版本号,更新数据时,若当前读到的version和数据库中存储的version一致的时候说明,在更新期间没有别的线程操作此数据,此时允许更新。如果version不一致,说明更新期间数据被其他线程修改了,此时不允许更新。

18sleep和wait的区别

1.sleep不会释放对象锁。Wait会释放锁

2.sleep会自动唤醒。wait需要其它线程唤醒。

3.sleep是Thread中的方法。wait是Object中的方法。

4.sleep可以在任何地方使用,而wait只能在同步方法或者同步块中使用。

18.5如何理解volatile关键字

在并发领域中,存在三大特性:原子性,有序性,可见性,volatile关键字修饰对象的属性,在并发环境下可以保证这个属性的可见性,对于加了volatile关键字的属性,在对这个属性进行修改时,会直接将CPU该机缓存中的数据写回到主内存,对这个变量的读取也会直接从主内存中读取,从而保证了可见性,底层是通过操作系统的内存屏障来实现的,由于使用了内存屏障,所以会禁止指令重排,所以同时也就保证了有序性,在很多并发场景下,如果用好volatile关键字可以很好的提高执行效率。

19.公平锁

定义:

公平锁是指多个线程按照申请锁的顺序来获得锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁

优点

等待锁的线程不会饿死。

缺点

整体吞吐效率比非公平锁要低,等待队列中除第一个以外的线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁要大。

公平锁排队的理解:如图,新来了一个D,D要进行排队,排到队列的最后面,最后来的最后获得锁

在这里插入图片描述

20.非公平锁

概念
非公平锁是多个线程加锁时直接尝试获得锁,获取不到才会进入等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获得锁的场景。

优点
可以减少唤醒线程的开销,整体吞吐效率高,因为线程有几率不阻塞直接获得锁。

缺点
处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

运行机制如下图:右上角此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁

​ 右下角获取不到会进入等待队列的队尾等待

在这里插入图片描述

20+ReentrantLock中的公平锁和非公平锁的底层实现

首先不管是公平锁和非公平锁,它们的底层实现都会使用AQS(AbstractQueuedSynchronizer抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架)来进行排队,区别在于:线程在使用lock()方法加锁时,如果是公平锁,会先检查AQS堆类中是否存在线程在排队,如果有线程在排队,则当前线程也进行排队,如果是非公平锁,则不会去检查是否有线程排队,而是直接竞争锁。

20.5ReentrantLock中tryLock()和Lock()方法的区别

tryLock()表示尝试加锁,可能加到,也可能加不到,该方法不会阻塞线程,如果加到锁则返回true,没有加到锁返回false

lock()表示阻塞加锁,线程会阻塞直到加到锁,方法也没有返回值。

20.75 CountDownLatch和Sempaphore的区别和底层原理

  • CountDownLatch表示计数器,可以给CountDownLatch设置一个数字,一个线程调用CountDownLatch的await()将会阻塞,其他线程可以调用CountDownLatch的countDown()方法来对CountDownLatch中的数字减一,当数字被减为0后,所有await()的线程都将被唤醒。对应的底层原理就是,调用await()方法的线程会利用AQS排队,一旦数字被减为0,则会将AQS中排队的线程依次唤醒。
  • Sempaphore表示信号量,可以设置许可的个数,表示同时允许最多多少个线程使用该信号量,通过acquire()来获取许可,如果没有许可,可用线程阻塞,并通过AQS来排队,可以通过release()方法来释放许可,当某个线程释放了许可后,会从AQS中正在排队的第一个线程开始一次唤醒,直到没有空闲许可。

21.锁的分类

锁一般的分类有:同步锁、互斥锁、悲观锁、乐观锁、公平锁、非公平锁。

java中,一把锁可能同时占有多个标准,符合多种分类

偏向锁/轻量级锁/重量级锁:这三种锁特指synchronized锁的状态

Synchronized的偏向锁、轻量级锁、重量级锁、自旋锁

  1. 偏向锁:在锁对象的对象头中记录一下当前获取到该锁的线程ID,该线程下次如果又来获取该锁就可以直接获取到
  2. 轻量级锁:由偏向锁升级而来,当一个线程获取到锁后,此时这把锁是偏向锁,此时如果有第二个线程来竞争锁,偏向锁就会升级为轻量级锁,之所以叫轻量级锁,是为了和重量级锁区分开来,轻量级锁底层是通过自旋来实现的,并不会阻塞线程。
  3. 重量级锁:如果自旋次数过多仍然没有获取到锁,则会升级为重量级锁,重量级锁会导致线程阻塞。
  4. 自旋锁:自旋锁就是线程在获取锁的过程中,不会去阻塞线程,也就是无所谓唤醒线程,阻塞和唤醒这两个步骤都是需要操作系统去进行的,比较消耗时间,自旋锁是线程通过CAS获取预期的一个标记,如果没有获取到,则继续循环获取,如果获取到了则表示获取到了锁,这个过程线程一直在运行中,相对而言没有使用太多的操作系统资源,比较轻量。

21.偏向锁

它的思想是如果自始至终,对于这把锁都不存在竞争,那么其实就没必要上锁,只要打个标记就行了。有点类似于乐观锁的机制,乐观锁就是默认不竞争,没必要上锁。但是他不是乐观锁。。

偏向锁概念:

一个对象在被初始化后,如果还没有任何线程来获取它的锁时,它就是可偏向的,当有第一个线程来访问它尝试获取锁的时候,它就记录下来这个线程,如果后面尝试获取锁的线程正是这个偏向锁的拥有者,就可以直接获取锁,开销很小。

22.轻量级锁(CAS)

JVM 的开发者发现在很多情况下,synchronized 中的代码块是被多个线程交替执行的,也就是说,并不存在实际的竞争,或者是只有短时间的锁竞争,用 CAS 就可以解决

偏向锁升级到轻量级锁

这种情况下,重量级锁是没必要的。轻量级锁指当锁原来是偏向锁的时候,被另一个线程所访问,说明存在竞争,那么偏向锁就会升级为轻量级锁,线程会通过自旋的方式尝试获取锁,不会阻塞。

23.重量级锁(阻塞)

会对拿不到锁的线程进行阻塞

这种锁利用操作系统的同步机制实现,所以开销比较大。当多个线程直接有实际竞争,并且锁竞争时间比较长的时候,
此时偏向锁和轻量级锁都不能满足需求,锁就会膨胀为重量级锁。重量级锁会让其他申请却拿不到锁的线程进入阻塞状态

24.synchronized 和 Lock 孰优孰劣?不同点

这篇文章很好

https://blog.csdn.net/qq_43542795/article/details/105970892?ops_request_misc=&request_id=&biz_id=102&utm_term=%E9%94%81%E8%86%A8%E6%B6%A8&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-.pc_search_result_control_group&spm=1018.2226.3001.4187

  1. 锁加在谁身上,用法区别
    synchronized 可以自定义monitor锁对象。(加解锁是隐式的)
    Lock接口必须用Lock锁对象来加锁和解锁。(显式的加锁,解锁,抛异常时也能保证释放锁)

  2. 加解锁顺序不同:
    对于 Lock 而言如果有多把 Lock 锁,Lock 可以不完全按照加锁的反序解锁:

​ 但是 synchronized 无法做到,synchronized 解锁的顺序和加锁的顺序必须完全相反

  1. synchronized 锁不够灵活
    synchronized 锁在被占有后,只能阻塞。(如果持有锁的线程很有才释放锁,等待时间长)
    Lock 类等锁的过程中,通过提供非阻塞尝试来获取锁( tryLock() ),尝试获取可被中断的锁( lockInterruptibly()) ,以及尝试获取可以超时( tryLock(long, TimeUnit) )
  2. synchronized 锁只能同时被一个线程拥有,但是 Lock 锁没有这个限制
    例如在读写锁中的读锁,是可以同时被多个线程持有的,可是 synchronized 做不到。
  3. 原理区别
    synchronized 是内置锁,由 JVM 实现获取锁和释放锁的原理,还分为偏向锁、轻量级锁、重量级锁。
    Lock 根据实现不同,有不同的原理,例如 ReentrantLock 内部是通过 AQS 来获取和释放锁的。
  4. 是否可以设置公平/非公平
    ReentrantLock 等 Lock 实现类可以根据自己的需要来设置公平或非公平,synchronized 则不能设置非公平锁
  5. 性能区别
    在 Java 5 以及之前,synchronized 的性能比较低,但是到了 Java 6 以后,发生了变化,因为 JDK 对 synchronized 进行了很多优化,比如自适应自旋、锁消除、锁粗化、轻量级锁、偏向锁等,所以后期的 Java 版本里的 synchronized 的性能并不比 Lock 差。

Synchronized和ReentrantLock的区别

  1. Synchronized是一个关键字,ReentrantLock是一个类
  2. Synchronized会自动加锁与释放锁,ReentrantLock需要手动加锁与释放锁
  3. Synchronized底层是JVM层面的锁,ReentrantLock是API层面的锁
  4. Synchronized是非公平锁,ReentrantLock可以选择公平锁和非公平锁
  5. Synchronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state表示来表示锁的状态
  6. Synchronized底层有一个锁升级的过程

Synchronized获取对象锁和类锁的用法

  1. 获取对象锁的两种方法:

    1. 同步代码块

      synchronized(this),synnchronized(类实例对象),锁的是小括号中的实例对象

    2. 同步非静态方法

      synchronized method,锁的是当前对象的实例

  2. 获取类锁的两种用法

    1. 同步代码块

      synchronized(类.class),锁的是小括号中的类对象,即Class对象

    2. 同步静态方法

      synchronized static method,锁的是当前对象的类对象(class对象)

五、IO

这里写图片描述

1.说一下BIO和NIO和AIO?

Java BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上 ,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

2.java 中 IO 流分为几种?

按功能来分:输入流(input)、输出流(output)。

按类型来分:字节流和字符流。

六、计算机网络

1.TCP的3次握手和四次挥手?为什么是四次挥手,而不是三次或是五次、六次?

三次握手:要建立连接时,1客户端发送一个SYN报文给服务器,2服务器收到请求后,向客户端响应一个SYN+ACK连接数据报文,3然后客户端收到报文,回应ACK报文,服务器收的到ACK完成3次握手。

四次挥手:双方关闭连接要经过双方都同意。所以,1首先是客服端给服务器发送FIN,要求关闭连接,2服务器收到后会发送一个ACK进行确认。3服务器关闭连接然后再发送一个FIN,4客户端收到FIN发送ACK确认,并进入TIME_WAIT状态。等待2MSL后自动关闭。服务器收到ACK就关闭连接了。

为什么是3次握手:因为没有收到服务器返回确认报文,这时会放弃连接,重新启动一条连接请求,因为他们之间要相互确认是否收到ACK。

2.TCP与UDP的区别:

1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;

5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。

框架

一、SpringBoot

1.什么是springBoot

SpringBoot是spring组件一站式解决方案快速整合第三方框架,主要是简化Spring的使用难度,简化了繁琐的配置(他是如何快速整合的呢?其实他是的基本原来是Maven依赖关系,Maven的集成,完全采用注解化,简化XML配置,)提供各种启动器,开发者能快速上手。

2.SpringBoot的优点

1.容易上手,整合其他第三方框架提升开发效率
2.开箱即用远离繁琐的配置
3.提供了一系列的大型通用非业务功能,内嵌HTTP服务器(Tomcate,jetty)
4.集成maven通过注解简化xml配置,避免jar包冲突

3.SpringBoot的配置文件有哪些格式

.yml 和.properities

他们的主要区别是书写的格式不同,yml格式不支持@PropertySource注解导入配置

​ @PropertySource用来加载指定的配置文件和外部文件,有以下几个属性:

​ 1. value 为要加载的文件,可以是多个,当以classpath开头时,程序会自动到classpath中读取,当以file开头

​ 2.name是表示要加载文件的名称,这里要加载的配置文件必须是唯一的不能是多个

​ 3.encoding,设置编码,一般utf-8

​ 4,ignoreResourceNotFound,当加载的配置文件不存在时,是否报错,默认false,当为true时表示文件不存在不报错

4.SpringBoot的常用注解有哪些

1.@SpringBootApplication

@SpringBootApplication是启动类上的最重要的一个注解,实际上是另外三个注解的组合:
	1.@SpringBootConfiguration
		@SpringBootConfiguration 其实就是对原 @Configuration 注解的简单封装。
			@Bean
				用来代替 XML 配置文件里面的bean配置。
			@ImportResource
				如果有些通过类的注册方式配置不了的,可以通过这个注解引入额外的 XML 配置文件,有些老的配置文件无法通过 					@Configuration 方式配置的非常管用。
			@Import
				用来引入额外的一个或者多个 @Configuration 修饰的配置文件类。
	2.@ComponentScan
		@ComponentScan主要用来开启组件扫描,可以自动扫描指定包路径下的@Component注解类并将bean实例注册到context中。
	3.@EnableAutoConfiguration
		@EnableAutoConfiguration主要用来提供自动装配,是这三个注解中最重要的一个注解。她是Spring Boot新添加的注解,提供了强大的自动依赖功能

2.@Bean注解:用来定义Bean,类似于XML中的标签,spring在启动时,会对加了@Bean注解的方法进行解析,将方法的名字作为beanName,并通过执行方法得到bean对象

3.@Controller、@Service、@ResponseBody、@Autowried

5.SpringBoot 的核心配置文件有哪些

Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。

application 配置文件这个容易理解,主要用于 Spring Boot 项目的自动化配置。

bootstrap 配置文件有以下几个应用场景。

使用 Spring Cloud Config 配置中心时,这时需要在 bootstrap 配置文件中添加连接到配置中心的配置属性来加载外部配置中心的配置信息;

一些固定的不能被覆盖的属性;

一些加密/解密的场景;

6.springBoot启动流程

容器启动(java启动main方法)

  1. 执行注解:扫描、载入自动配置bean

    EnableAutoConfiguration将所有符合自动配置条件的bean定义加载到IOC容器

  2. SpringApplication.run()具体容器启动流程

    1. 获取监听器SpringApplicationRunListeners listnenrs
    2. listeners.starting(),触发ApplicationStartEvent事件
    3. 准备好环境ConfigurableEnviroment,触发ApplicationEnvironmentPrepareEvent事件
    4. 打印启动提示符,默认spring的字符图
    5. 实例化一个可配置应用上下文ConfigurableApplicationContext
    6. 准备上下文Listeners的contextPrepared:空contextLoaded:触发ApplicationPreparedEvent事件
    7. 刷新上下文AbstractApplicationContext.Refresh()
    8. 刷新上下文后:执行启动执行器ApplicationRunner/CommandLineRunner用户可拓展接口
    9. listener.finished成功触发事件ApplicationReadyEvent,遇到异常失败触发事件ApplicationFailedEvent

7,日志logging

记录程序日志信息的目的

1.可以方便了解程序的运行情况

2.可以分析用户的操作行为,喜好等信息

3.方便开发人员检查bug

logging日志级别介绍

  1. Debug
  2. info
  3. warning
  4. error
  5. critical

日志等级说明:

debug:程序调试bug时使用

info:程序正常运行时使用

warning:程序未按预期运行时使用,但并不是错误,如:用户登录密码错误

error:程序出错时使用,如:IO操作失败

critical:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用

默认的是warning等级,当在warning或者warning之上等级的才记录日志信息

日志等级从低到高的顺序是:debug

logging日志的使用

在logging包中记录日志的方式有两种:

1.输出到控制台

2.保存到日志文件

8.springBoot如何启动Tomcat的

  1. 首先,springboot在启动时会先创建一个spring容器
  2. 在创建spring容器过程中,会利用@ConditionalOnClass技术来判断当前Classpath中是否存在Tomcat依赖,如果存在则会生成一个启动Tomcat的Bean
  3. spring容器创建完成之后,就会获取启动Tomcat的Bean,并创建Tomcat对象,并绑定端口等,然后启动Tomcat

9.springboot配置文件的加载顺序是怎样的?

优先级从高到低,高优先级的配置文件覆盖低优先级的配置文件,所有配置文件会形成互补配置。

  1. 命令行参数,所有的配置都可以在命令行上进行制定;

  2. java系统属性(System.getProperties())

  3. 操作系统环境变量;

  4. jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件

  5. jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件再来加载不带profile

  6. jar包外部的application.properties或application.yml(不带spring.profile)配置文件

  7. jar包内部的application.properties或application.yml(不带spring.profile)配置文件

  8. @Configuration注解类上的@PropertySource

二、springMVC

6.springMVC的核心组件

DispatcherServlet(前端控制器
相当于转发器、中央处理器。DispatcherServlet是整个流程控制的中心
HandlerMapping(处理器映射器
根据请求的url查找Handler。HandlerMapping负责根据用户请求找到Handler即处理器
HandlerAdapter(处理器适配器
按照特定规则(HandlerAdapter要求的规则)去执行Handler。
View resolver(视图解析器
View Resolver负责将处理结果生成View视图
View(视图)
View是一个接口,实现类支持不同的View类型(jsp、freemarker),就是我们真正呈现的用户效果

7.SpringMVC的工作流程

1.前端用户发送请求到前端控制器,Dispatcher Servlet

2.Dispatcher Servlet收到请求后,调用HandlerMapping去获取Handler,HandlerMapping根据具体的url请求找到具体的Handler交给Dispatcher Servlet。0

3.Dispatcher Servlet调用HadnlerAdapter执行Handler将执行结果返回Dispatcher Servlet

4.Dispatcher Servlet将执行结果ModleAndView交给view resolver进行视图解析,结果返回

5.Dispatcher Servlet对view进行渲染返回给用户

8.SpringMVC的常见注解

@RequestMapping
	用于处理url映射的注解,用在方法和类上。用在类上表示,类中所有的响应请求的方法都是以该类为父路径
@RequestBody
	接收http请求的json数据将json转化成Java对象
@ResponseBody
	将对象转化成json响应给客户。

三、Spring

1.spring常用注解

  • 组件类注解:

    @Component标注一个普通的springBean类

    @Repository标注一个DAO(数据访问层)组件类

    @Service标注一个业务逻辑组件类

    @Controller标注一个控制器组件类

  • 装配bean时常用注解

    @Autowried

    @Resource

  • 其他

    @Confuguration

    @Bean

2.@Resource和@Autowired

@Resource和@Autowired都可以用来装配bean,都可以用于字段或setter方法。
@Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false。
@Resource默认按名称装配,当找不到与名称匹配的bean时才按照类型进行装配。名称可以通过name属性指定,如果没有指定name属性,当注解写在字段上时,默认取字段名,当注解写在setter方法上时,默认取属性名进行装配。

3.Spring中的Bean创建的生命周期有哪些步骤?

spring中一个Bean的创建大概分为以下几个步骤:

  1. 推断构造方法
  2. 实例化
  3. 填入属性,也就是依赖注入
  4. 处理Aware回避
  5. 初始化前,处理@PostConstruct注解
  6. 初始化,处理InitializingBean接口
  7. 初始化后,进行AOP

4.spring中Bean是线程安全的么?

spring本身并没有对Bean做线程安全处理,所以:

1.如果Bean是无状态的,那么Bean则是线程安全的

2.如果Bean是有状态的,那么Bean则不是线程安全的

另外,Bean是不是线程安全,跟Bean的作用域没有关系,Bean 的作用域只是表示Bean的生命周期范围,对于任何生命周期的Bean都是一个对象,这个对象是不是线程安全的,还得看这个Bean对象本身。

5.ApplicationContext和BeanFactory有什么区别

BeanFactory是spring中非常核心的组件,表示Bean工厂,可以生成Bean,维护Bean,而ApplicationContext继承了BeanFactory,所以ApplicationContext拥有BeanFactory所有的特点,也是一个Bean工厂,但是ApplicationContext除了继承BeanFactory之外,还继承了诸如EnvironmentCapable、MessageSource、ApplicationEventPublisher等接口,从而ApplicationContext还有获取系统环境变量、国际化、时间发布等功能,这是BeanFactory所不具备的。

6.Spring中的事务是如何实现的

  1. spring事务底层是基于数据库事务和AOP机制的
  2. 首先对于使用了@Transaction注解的Bean,spring会创建一个代理对象作为Bean
  3. 当调用代理对象的方法时,会先判断该方法是否加了@Transactional注解
  4. 如果加了,那么则利用事务管理器创建一个数据库连接
  5. 并且修改数据库连接的Autocommoit属性为false,禁止连接的自动提交,这是实现spring事务非常重要的一步
  6. 然后执行当前方法,方法中会执行SQL
  7. 执行完当前方法后,如果没有出现异常就直接提交事务
  8. 如果出现异常,并且这个异常是需要回滚的就回滚事务,否则仍然提交事务
  9. spring事务的隔离级别对应的就是数据库的隔离级别
  10. spring事务的传播机制是spring事务自己实现的,也是spring事务中最复杂的
  11. spring事务的传播机制是基于数据库连接来做的,一个数据库连接一个事务,如果传播机制配置为需要新开一个事务,那么实际上就是先建立一个数据库连接,在此新数据库连接上执行sql

7.spring中什么时候@Transactional会失效

因为spring事务是基于代理来实现的,所以某个加了@Transactional的方法只有被代理对象调用,那么这个注解才会生效,所以如果是被非代理对象来调用这个方法,那么@Transactional则不会生效

同时如果某个方法是private的,那么@Transactional也会失效,因为底层cglib是基于父子类来实现的,子类是不能重载父类的private方法的,所以无法很好的利用代理,也会导致@Transactional失效

8.spring容器的启动流程是怎样的

  1. 在创建spring容器,也就是启动spring时:
  2. 首先会扫描,扫描得到所有的BeanDefinition对象,并存到一个Map中
  3. 然后筛选出非懒加载的单例BeanDefinition进行创建Bean,对于多例Bean不需要在启动过程中去进行创建,对于多例Bean会在每次获取Bean时利用BeanDefinition去创建。
  4. 利用BeanDefinition创建Bean就是Bean创建生命周期,这期间包括了合并BeanDefinition、推断构造方法、实例化、属性填充、初始化前、初始化、初始化后等步骤,其中的AOP就是发生在初始化后这一步骤的
  5. 单例Bean创建完了之后,spring会发布一个容器启动事件
  6. spring启动结束
  7. 在源码中会更复杂,比如源码中会提供一些模板方法,让子类来实现,比如源码还实际到一些BeanFactoryPostProcessor和BeanPostProcessor的注册,spring的扫描就是通过BeanFactroyPostProcessor来实现的,依赖注入就是通过BeanPostProcessor来实现的
  8. 在Spring启动过程中还会去处理@Import等注解

9.spring用到了那些设计模式?

工厂模式:BeanFactory、FactoryBean

适配器模式:AdvisorAdapter接口对Advisor进行了适配

访问者模式:PropertyAccessor接口,属性访问器,用来访问和设置某个对象的某个属性

装饰器模式:BeanWrapper

代理模式:AOP

观察者模式:事务监听机制

策略模式:instantiationStrategy 根据不同情况进行实例化

模板模式:JdbcTemplate

委派模式:BeanDefinitionParserDelegate

责任链模式:BeanPostProcessor

9.spring是什么?

Spring是一个轻量级的IOC和AOP容器框架,目的是用于简化企业应用程序开发,使开发者只关心业务需求

10.spring的优点

1.Spring是中间层、粘合剂、对其他主流框架进行整合,使得浑然一体

2.spring的IOC、DI机制降低对象之间的耦合性

3.spring的AOP支持将一些通用的任务如:安全、日志、权限等进行管理提高可用性

11.Spring的核心是什么

IOC 和 AOP。

IOC让相互协作的组件保持松散的耦合,

而AOP编程允许把遍布于应用各层的功能分离出来形成可重用的功能组件。

12.IOC是什么?

IOC是一种解耦的设计思想

是指控制反转,指创建对象的控制权转交给了spring容器来进行管理控制对象的生命周期。由spring根据配置文件去创建实例和管理实例之间的依赖关系,对象与对象之间实现松耦合

12+反射

概念

反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用她的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

哪里用到反射

JDBC,利用反射动态加载了数据库驱动程序。

Web服务器中利用反射调用了Servlet的服务方法

Eclispe等开发工具利用反射动态剖析对象的类型与结构,动态提示对象的属性和方法。

很多框架都用到了反射机制,注入属性,调用方法,如Spring

反射机制的优缺点

优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性

缺点:对性能有影响,这类操作总是慢于直接执行java代码。

反射使用步骤

1.Class.forName(“全限类名”)例如:com.mysql.jdbc.Driver

类名.class

对象.getClass();

2.获取构造器对象,通过构造器new出一个对象。Clazz.getConstructor/Clazz.newInstance

3.通过class对象获得一个属性对象Field c=cls.getDeclaredFields():,获得某个类的所有声明的字段,即包括public、private、proteced,但是不包括父类的声明字段

4.通过class对象获得一个方法对象,getMethod(“方法名”,class parameaType)

M.setAccessible(true)(让私有的方法可以执行)

13.什么是DI?

DI依赖注入

和控制反转是同一概念的不同角度,IOC容器动态的将某个对象所需要的资源注入到组件中。

注入方式:

set注入

构造器注入

静态工厂注入

实例化工厂注入

14.什么是AOP?

AOP是一种面向切面的编程思想,是对OOP的一种补充。用于将那些与业务无关但却对多个对象产生影响的的公共行为和逻辑抽取并封装成一个可重用的模块,这个,模块叫切面

15.AOP核心概念

切面:被抽取出来的公共模块

连接点:执行的目标方法

切点:满足扫描条件的所有连接点的集合

通知:定义切面方法执行的时机

目标对象:被代理的对象。

16.四种切入点表达式

粗粒度
bean: 按照bean匹配,当前bean中的方法都会执行通知
within 可以匹配多个类
细粒度
execution方法参数级别
@annoction 按照注解匹配

17.AOP5种通知方式

before 执行目标方法之前执行
after 执行目标方法之后执行
around 之前目标方法前后都执行
afterThrowing 出现异常后执行
afterReturning 有返回值后执行

18.AOP中的代理

静态代理
Aspect-J静态代理
动态代理
JDK动态代理
cglib动态代理

19.JDK动态代理

JDk动态代理只提供接口的代理,不支持类的代理,要求被代理的类必须是实现接口。JDK代理的核心是 InvocationHandler 和proxy。在获取代理对象的时候使用proxy动态创建目标对象的代理类,当代理对象调用真实的方法时,InvacatinHandler调用invoke()方法反射调用目标类的方法

20.CGLIB动态代理

如果被代理的类没有实现接口,那么springAOP会选择使用cglib动态代理目标类,cglib是一个代码生成的类库,可在运行时动态生成一个指定类的子类对象并覆盖其中的特定方法,添加增强代码实现AOP

21.静态代理和动态代理的区别

Aspect-J是静态代理也成为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AsprctJ(切面)织入java字节码中,运行时就是增强后的AOP对象。

spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会修改字节码,而是每次运行时在内存中临时为方法生成一个Aop对象,这个aop对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法

22.spring框架中用到的设计模式

工厂模式
	Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象
单例模式
	Spring 中 Bean 的默认作用域就是 singleton(单例)的
		单例模式的好处
			对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统           开销;
			由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时           间。
代理模式
	Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
模板模式
	用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate等。
观察者模式
	定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现:ApplicationListener。

23.spring的核心接口 BeanFactory 和ApplicationContext有什么不同

1、ApplicationContext 是 BeanFactory 的子接口,功能更全
	BeanFactory是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。
	ApplicationContext是BeanFactory的派生接口,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
2、加载方式
	BeanFactroy 采用的是延迟加载形式来注入Bean,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化
	ApplicationContext 是在容器启动时,一次性创建了所有的Bean。
		这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
	相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
3、创建方式
	BeanFactory 通常以编程的方式被创建。
	ApplicationContext 除了编程方式,还能以声明的方式创建,如使用ContextLoader。
4、注册方式
	BeanFactory需要手动注册,而ApplicationContext则是自动注册。

四、MyBatis

22.MyBatis是什么

MyBatis是一款优秀的持久层框架,一个半ORM(对象关系映射)的框架,它支持定制化SQL,存储过程,高级映射

MyBatis底层封装了JDBC,避免了几乎所有的JDBC代码,手动设置参数以及获取结果集

23.Mybatis的优点和缺点

优点:

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

缺点:

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

25.ORM是什么?全自动ORM和半自动ORM有什么区别?

ORM时对象关系映射,是一种为了解决关系型数据库与简单Java对象的映射关系的技术。将程序中的对象自动持久化到关系型数据库

自动与半自动的却别在于用不用手写sql

26.Hibernate和Mybatis的区别

​ 1.映射关系

​ MyBatsi是一个半自动对象关系映射的框架

​ Hibernate是一个全自动对象关系映射的框架

​ 2.SQL优化

​ Hibernate’对sql进行了封装,优化困难

​ MyBatis需要手动编写sql,支持动态sql优化简单

​ 3.开发难易,学习成本

Hibernate是重量级框架,学习使用门槛高,适用于需求相对稳定的中小型项目

MyBatis是轻量级框架,学习门槛低。适用于需求变化频繁的互联网大型项目

26+springboot引入mybatis步骤

1.pom.xml文件引入正确的依赖

2.创建javabean

3.创建Mapper接口

4.搭建xml文件配置

5…yml文件制定mybatis全局配置文件和接口映射文件位置

27.MyBatis底层工作原理

1.创建SqlSwssionFactory
2.通过SqlSessionFactory创建SqlSession
3.通过SqlSesson执行数据库操作
4.调用session.commit()提交事务
5.调用session.close()关闭会话

28.#{} 和${} 的区别

#{} 是预编译处理,mybatis在处理#{}的时候,会将sql中的#{}替换为?占位符进行占位,调用预编译语句preparedStatement中的set注入参数,可以有效的防止sql注入攻击。支持基本数据类型(八大数据类型、包装类、BigDecimal)

** 是 字 符 串 的 替 换 。 ∗ ∗ M y B a t i s 在 处 理 {} 是字符串的替换。**MyBatis在处理 MyBatis{}的时候会将${}里面的变量原原本本的赋值到sql里面,不会给他加上单引号会出现sql注入问题。不支持基本类型。

  1. #{}是预编译处理,是占位符,${}是字符串替换,是拼接符
  2. Mybatis在处理#{}时,会将sql中的#{}替换为?,调用preparedStatement来赋值
  3. Mybatis在处理 时 , 就 是 把 {}时,就是把 {}替换成变量的值,调用statement来赋值
  4. 使用#{}可以有效的防止SQL注入,提高系统安全

29.MyBatis 动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。

30.MyBatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?

虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。

原理是,Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。

31.ResultMap 和 ResultType 的区别

resultType和resultMap功能类似 ,都是返回对象信息,

区别在于resultMap要手动配置一下,表和类的对应关系。当实体类和数据库表字段一一对应的时候,

resultType会自动建立对应关系;当实体类和数据库表字段不一致的时候,使用resultMap手动建立对应关系。

32.MyBatis-Plus是什么?

​ MyBatis-Plus是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。如果添加了MybatisPlus的依赖 但是不想使用MybatisPlus的类和方法 这时候仍然可以按照之前使用Mybatis的步骤做开发 几乎和之前没有任何的改变

微服务

一、微服务基础理解

1.什么是Spring cloud

是在Spring Boot 基础上构建的,用于快速构建分布式系统的通用模式的工具集.这些工具集包括(服务注册、服务配置、负载均衡、声明式调用、网关、熔断器、链路监控等)

2.spring cloud的核心组件

1、服务注册与发现 Eureka、nacos组件

2、负载均衡 Ribbon组件

3、声明式调用 Feign组件

4、熔断器 Hystrix、sentinel组件

5、路由网关组件 Zuul、GateWay组件

6、 配置中心nacos、Apollo(携程)、spring cloud config组件

7、服务链路追踪 Spring Cloud Sleuth 、zipkin组件

3.springBoot 和 Spring cloud的区别和联系

SpringBoot 不依赖于 SpringCloud,SpringCloud 依赖于 SpringBoot,属于依赖关系。

SpringBoot 专注于快速、方便的开发单个的微服务个体,SpringCloud 关注全局的服务治理框架。整合并管理各个微服务,为各个微服务之间提供服务注册与发现、负载均衡、声明式调用、熔断器、路由网关、配置中心、服务链路追踪、微服务监控等集成服务

网关(Zuul、GateWay)

1.网关Zuul的核心功能

统一的权限校验
继承 zuulFeliter 过滤器实现过滤
统一的调用入口
集成hystrix
集成ribbon
默认启用负载均衡
默认不开启自动重试
会对后台造成很大的压力
会造成雪崩

服务的注册中心(eureka、Nacos)

1.eureka的四条运行机制

注册
客户端一次次反复尝试注册,直到注册成功为止
拉取
客户端每30s拉去一次eureka上面的注册表单,刷新注册表
心跳
客户端每30s发送一次心跳数据
连续三次收不到一个服务的心跳,会删除服务
自我保护
由于网路不稳定,15分钟内,85%的服务出现心跳异常,自动进入自我保护模式,所有的注册信息不删除
等待网络恢复正常后会自动退出保护模式
开发阶段可关闭保护模式10-113

服务的配置中心(Spring Cloud Config 、Nacos)

为啥要有配置中心?

每次修改配置都在application.yml文件中修改,修改成功后必须重启服务才会生效,但是我们真正运行的服务如果突然重启的话会对数据产生影响,所以需要一种不用重启项目也能让配置生效的技术,这就是服务配置中心。

服务的配置中心有 Nacos、Spring cloud config

服务之间的调用(Feign)

1.RestTemplate

2.LoadBalanceClient

3.@LoadBalance

4.Feign

5.Ribbon

负载均衡(Nginx、Ribbon)

1.什么是Nginx

Nginx是一个 轻量级/高性能的反向代理Web服务器,他实现非常高效的反向代理、负载平衡,他可以处理2-3万并发连接数,官方监测能支持5万并发

2.负载均衡策略

轮询

upstream backserver {
  server 192.168.0.14;
  server 192.168.0.15;
}

权重

upstream backserver {
  server 192.168.0.14 weight=3;
  server 192.168.0.15 weight=7;
}

IPHash

upstream backserver {
  ip_hash;
  server 192.168.0.14:88;
  server 192.168.0.15:80;
}

3.什么是正向代理和反向代理

正向代理

前端的请求交给正向代理,由正向代理向服务器发送请求。对于服务器来讲只会认为他在与代理进行交互,不知道谁发来的请求,保护了用户。

正向代理的好处

  1. 能访问原来不能访问的资源
  2. 可做缓存加速访问资源
  3. 隐藏用户信息

反向代理

反向代理是后端服务器不直接对外暴漏,请求首先发送到反向代理,然后由反向代理将请求转发到后端服务器。

阻止web攻击Nginx可做反向代理,也可以做负载均衡分发前端请求。

4.为什么Nginx性能这么高?

Nginx采用多进程单线程模式,多路IO复用模型。事件处理机制:异步非阻塞事件处理机制:运用了epoll模型,提供了一个队列,排队解决。

服务的限流和熔断(Hystrix、Sentinel)

1.为什么需要用到限流?

节假日、大型活动,这些场景会引起服务器流量暴涨,导致网页无法显示,App反应慢呢,功能无法正常运转,甚至会引起系统崩溃。

如何在这些业务在流量变化无常的情况下保持各种业务安全运行,系统在任何情况下都不会崩溃呢?我们可以在系统负载过高的时候采用限流、降级、熔断技术保护系统

2.什么是Sentinel

Sentinel是阿里开源的一套用于服务容错的综合性解决方案,他以流量作为切入点,从流量口直熔断降级、系统负载保护等多个维度保护服务的稳定性。

3.Sentinel的流控方式有哪些

直接:设定阈值类型和单机阈值,进行现在,当流量一旦超过设计的阈值则快速失败
关联:当关联的资源达到阈值的时候限流自己
链路:限流一条链路上的资源

4.sentinel的限流效果

  1. 快速失败:流量达到指定阈值,直接报回异常
  2. warmUp预热:假如单机阈值为100,系统的初始化阈值调为100/3,即阈值为33,然后10s阈值才恢复到正常的100.
  3. 排队等待:匀速排队,让请求以均匀的速度通过,阈值类型必须设为QPS否则无效。

消息队列(RabbitMQ)

1.什么是RabbitMQ

RabbitMQ是一个消息代理:它接受和转发消息

2.RabbitMq的优点

异步解耦

系统间通过消息通信,不用关心其他系统的处理

流量消峰

​ 比如一个购物系统,突然有前端产生的大量订单要存进数据库,这就产生的峰,这时可以使用Rabbitmq,把订单先存入消息队列里面,然后创建一个消费者慢慢的将订单存进数据库,这就实现了流量消峰

灵活路由

​ rabbit有6种工作模式,有多种灵活的方法转发队列中的消息

3.Rabbitmq的工作模式

一.simple模式(即最简单的收发模式)

img

1.生产者产生消息,将消息放入队列

2.消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患 消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。

二.work工作模式(资源的竞争)

img

1.消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize) 保证一条消息只能被一个消费者使用)。

三.publish/subscribe发布订阅(共享资源)

img

1、每个消费者监听自己的队列;

2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。

四.routing路由模式

img

1.消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;

2.根据业务功能定义路由字符串

3.从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中。

4.业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误;

五.topic 主题模式(路由模式的一种)

img

1.星号井号代表通配符

2.星号代表多个单词,井号代表一个单词

3.路由功能添加模糊匹配

4.消息产生者产生消息,把消息交给交换机

5.交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费

4.常用的交换器

fanout:如果交换器收到消息,将会广播到所有绑定的队列上

direct:如果路由键完全匹配,消息就被投递到相应的队列

topic:可以使来自不同源头的消息能够到达同一个队列。 使用 topic 交换器时,可以使用通配符

5.工作模式如何实现消息的合理转发

设置手动Ack
消息回执
向服务器发送一个通知
告诉服务器一条消息已经处理完成
服务器可以通过Ack’,知道消费者时空闲还是繁忙
设置Qos=1
每次抓取消息的数量
消息处理完成之后不会抓取新的消息
手动Ack模式下才有效

链路监控(服务调用监控)(Zipkin+seluth)

分布式缓存(Redis)

1.什么是Redis

redis是非关系型数据库,数据以键值对的形式存储进redis数据库。

redis可以存储键和五种类型值之间的映射

与传统的数据库不同Redis的数据是存在内存中的,所以读写非常快,每秒可处理超过10万次读写

2.说说Redis的五种数据类型(应用场景)

String:常规key-value缓存应用。常规计数: 微博数, 粉丝数,统计文章字数。

hash:存储对象数据

list:关注列表,粉丝列表

set:集合要求值不能重复,可以应用在粉丝管理,粉丝不能有重复的

zset:与set类似,区别是set不是自动有序的

3.macahe和redis的区别

与macache一样,为了保证效率数据都是缓存道内存当中。

区别就是redis会周期性的把更新的数据写入磁盘或者把修改操作写入适当的记录文件,并且在此基础上实现主从同步。redis支持主从同步,数据库可以从主服务器向任意数量的从服务器上同步。从服务器可以关联其他服务器的主服务器器

4.redis基础命令

redis-cli 进入控制台
exit     退出
shutdown 停止
keys *   查找全部
select  查找
del     删除
type    查看数据类型
flushall  清空所有数据库
flushdb   清空当前数据库
incr/incrby  递增
decr/decrby  递减
append   追加
strlen   字符串长度
mset    批量添加
mget    批量获取

5.Java中如何操作Redis

Jedis和RedisTemplate

Jedis

	1.编写测试类
	2.构建Redis的客户端Jedis
		Jedis jedis=new Jedis("192.168.138.129",6379)
		Jedis("host",port)
	3.在redis服务器端修改redis.conf文件
		将 build 17.0.0.1 这个配置注释掉
		将 protected-mode 改为 no
	4.现在就可以使用Jedis操作对象了
		jedis.ping()
pong

RedisTemplate

	1.添加依赖
		spring-boot-start-data-redis
	2.yml配置
        spring:
          redis:
            host: 192.168.138.129
            port:  6379
	3.使用RedisTempalte对象操作redis数据库
	@Autowired
    private StringRedisTemplate srt;
    @Test
    String pong=srt.getConnectionFactory().getConnection().ping();
    System.out.pringtln(pong);

6.详细说说Redis的数据结构

如果仅有string类型很难实现存储对象,更新对象属性这样的操作,hash可以存储这种有对象关系的数据
Redis的list类型其实就是一个每个子元素都是String类型的双向链表
可以通过push,pop操作从链表头部或者尾部添加删除元素
Redis的Set是String类型的无序集合。集合成员是唯一的,这意味着不能出现重复数据
Redis中Set集合是通过哈希表实现的,所以添加、删除、查找的复杂度都是1

7.key的有效时间设定

redis在实际的使用过程中更多的用作缓存,然而缓存数据一般都需要设定有效时间,到期后数据自动销毁

如何设置?

expire key second 设置key值的有效时长
pexpire key milliseconds 设置时长以毫秒为单位(适用于秒杀)

如何查看?

TTL 查看key的剩余时间,当返回值为-2的时候,表示键被删除。当可以存在但是没有剩余时间的时候返回-1

8.Redis持久化的原因

Redis是一种内存数据库,在断电的时候数据可能会丢失

9.Redis如何持久化

rdb 和 AOF 两种

rdb

Rdb方式是手动的,周期性的将内存中的数据做一个快照存储到磁盘中,快照文件叫做 .rdb
在redis中使用save/bgsave命令完成
Redis一般默认持久化的方法是rdb,系统启动时自动开启这种持久化方式

AOF

Aof是通过记录写操作日志方式,记录Redis数据的一种持久化方式,这个机制默认是没有开启的。
打开 Aof 持久化机制之后,redis每接受一次写命令就会写入日志文件中,当然先写入os cache隔一段时间酒fsync一下
redis重启时有限通过AOF进行数据恢复,因为使用AOF比较完整。

10.rdb中save和bgsave的区别

Redis save 命令执行一个同步保存操作,将当前Redis实例的所有数据快照以rdb 文件的形式保存到硬盘
bgsave命令执行后立即返回ok,然后redis会fork出一个新的子进程,原来的redis进程会继续处理客户端请求,而子进程负责将文件保存到磁盘然后退出

11.rdb优点

1.rdb会生成多个数据文件,每个文件都代表某一时刻redis中的数据,因此这种多数据文件方式非常适合做“冷备”,可以将这些数据发送到一些远程的安全存储上。
2.rdb对redis对外提供的读写服务影响小,可以让redis保持高性能。
	为什么对读写影响小,保持高性能?
	因为redis可以fork出一个子进程,让子进程执行对磁盘的IO
3.相对于Aof来讲,基于rdb数据文件重启和恢复redis进程更加快速

12.rdb缺点

1.如果想要在redis故障时,如果想尽可能少丢失数据,那么rdb没有Aof
	为什么没有Aof好?
	因为rdb的save在每60s内数据超过1000才存入磁盘,若没有超过不会存入。若在某一个60s内数据没有超过1000,redis挂掉了,则这些数据也丢失了。
2.rdb每次在fork子进程来执行rdb快照数据文件生成,如果数据量过大时,可能会导致对客户提供的服务暂停数毫秒或者数秒

13.AOF的持久化三种策略

always
	每次写入一条数据就立刻将这个数据写入日志然后fsync到磁盘
everysec
	每隔1s写一次
no
	由操作系统决定何时持久化

14.AOF的rewrite操作

​ rewrite重写日志,会清除掉一些过期的日志
​ 比如目前日志中存放了100万条数据的写日志,而redis中目前只有10w条数据,rewrite会根据这10w条数据重新构建一套新的日志到AOF覆盖之前的日志。

15.AOF的优点

1.AOF更好的保护数据不丢失
2.AOF日志文件以append-only模式写入,所以没有任何的磁盘寻址的开销,效率非常的高,而且文件不容易破损,即使文件的尾部破损了也容易修复
3.AOF日志文件过大时,会进行后台重写操作进行压缩
4.AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删操作的紧急修复。

16.AOF缺点

对于同一份数据来说AOF日志文件比rdb体积更大
AOF脆弱
AOF支持写的QPS低

17.Redis主从复制

单个redis支持读写能力还是有限的,此时我们需要使用多个redis来提高redis的并发处理能力,这些redis如何协同工作,这就用到主从架构

构建Redis集群,使用主从架构。可以构建master和salver设计主从关系,master可以读也可以写、slave只可以读

18.什么是Redis的哨兵机制

哨兵(Sentinel)是Redis的主从架构模式下,实现高可用性(high availability)的一种机制。由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线(宕机了)状态时,自动将下线主服务器属下的某个从服务器(Slave)升级为新的主服务器(Master),然后由新的主服务器代替已下线的主服务器继续处理命令请求

19.哨兵工作原理分析

1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令(严格来讲就是心跳包)。
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值(这个配置项指定了需要多少失效时间,一个master才会被这个sentinel主观地认为是不可用的。 单位是毫秒,默认为30秒), 则这个实例会被 Sentinel 标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。
5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。
6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。
7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。
8): 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。

20项目中如何使用redis

​ 博客数字统计
​ strlen
​ 将审计日志追加到指定的key
​ append
​ 博客点赞操作
​ incr
​ 秒杀操作的计时是如何实现的
​ pexpire 以毫秒为单位设置时长
​ 如何设置缓存时长
​ expire key second
​ pexpire key milliseconds
​ 发布一篇博客 需要hmset
​ 浏览博客 hmget
​ 判断博客是否存在 hexist
​ 分布式系统中你登陆成功以后是如何存储用户信息的

21.说说缓存穿透问题

原因: 前端恶意提交无效的请求、热点数据失效

缓存中没有需要的数据,就会跳过缓存访问数据库,缓存中一直没有就一直访问数据库,此时缓存就跟摆设一样。这就是缓存穿透。

22.如何解决缓存穿透问题?

  1. 缓存中存入无效数据

​ 对于恶意的攻击请求,key值是空的,或者是其他数据库中不存在的key值。我们把这些无效数据加入缓存中,遇到恶意请求时直接返回null。这样阻止缓存穿透。

​ 缺点:恶意的请求太多,自己枚举不完

  1. 添加过滤器 BloomFilter

在缓存之前设置一道过滤器,里面存有校验目前数据库中存有的key值的校验,请求发来时先进性过滤,符合规则才可以继续访问缓存,有效的防止缓存穿透。

23.什么是缓存雪崩

存在Redis中的数据是有时间限制的,时间一到就会失效然后从缓存中清除出去。若不巧在某一时间大量的数据集体失效,大量的前端请求访问,但是缓存中的数据失效了,这些请求就会访问数据库这就是缓存的雪崩。

解决方案

设计缓存随机失效

设置sentinel或者Hystrix进行限流和熔断减少损失。

权限控制(RBAC)

RABC

​ Role-Base Access Controler 基于角色的权限控制

​ Resource Base Access Controller 基于资源的权限控制

1.基于角色的权限控制

权限与角色相关联,用户通过成为适当的角色的成员而得到这些角色的权限,这极大的简化了权限管理。这样的管理层级依赖大,权限赋予角色,又把角色赋予给用户。

2.角色权限的弊端

  1. 角色用作功能权限,数据权限的话会导致角色数量非常多
  2. 角色是一位的不同角色之间是相互独立的,而人员之间一般都有树状的组织关系。
  3. 对于不太确定的系统来说,角色不好定义

3.基于资源的权限控制

目的是设计一种通用的权限管理系统,不和任何实际的业务相关联,只做权限相关的管理和验证。

之所以资源为核心,是因为基于角色的限制。基于资源的权限系统是围绕资源树和组织树来进行权限的。

数据库

E:\Tencen\微信\WeChat Files\wxid_2nciglwhomi122\FileStorage\File\2021-07

1.为什么要使用数据库

数据保存在内存

优点: 存取速度快

缺点: 数据不能永久保存

数据保存在文件

优点: 数据永久保存

缺点:1)速度比内存操作慢,频繁的IO操作。2)查询数据不方便

数据保存在数据库

1)数据永久保存

2)使用SQL语句,查询方便效率高。

3)管理数据方便

1+数据库备份

备份方式:

1直接拷贝物理文件

2.在可视化工具中手动导出

3.使用命令行导出mysqldump

mysqldump -u主机 -p密码 数据库 表123>物理磁盘/文件名
    导入:
    mysql -u用户名 -p密码 库名<备份文件
    登录的情况下,切换到指定的数据库source备份文件

1++数据库的规约,三大范式

第一范式:原子性:保证每列不可再分

第二范式:每张表只描述一件事(前提:满足第一范式)

第三范式:确保数据表中每列数据都和主键直接相关而不能间接相关(前提:满足第一范式和第二范式)

规范性和性能的问题:

考虑商业化的需求和目标(成本,用户体验),数据库的性能更加重要

在规范性的问题的时候,需要适当考虑一下福反省

故意给某些表增加一些冗余字段(从多表查询变为单表查询)

故意增加一些计算列(从大数据量降低为小数据量的查询:索引)

2.什么是索引?

索引是一种数据结构。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。

出数据之外,数据库系统还维护了为满足特定查找算法的数据结构,这种数据结构以某种特定的方式引用数据,这种数据结构就叫做数据库索引

更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。

3.为什么使用索引

当然是因为索引的优点

4. 索引的优点

  1. 索引提升查询效率
  2. 通过创建唯一索引,保证数据库表中每一条数据的唯一性
  3. 减少分组和排序的时间

5.索引的缺点

  1. 创建索引和维护索引都需要花费时间,时间会随着索引数的增加而增加
  2. 索引需要占据物理空间
  3. 对表中数据增删改的时候耗时增加,因为索引也需要动态的维护。

6.索引的使用场景

  • 经常需要搜索的列上
  • 作为主键的类上
  • 经常用在链接的类上,这些连接一般都是外键
  • 经常需要根据范围进行搜索的列上(例如分数。根据分数范围搜索)
  • 经常需要进行排序的列上

7.索引的类型有哪些?

唯一索引、主键索引、符合索引、聚集索引、非聚集索引、聚簇索引、非聚簇索引、稠密索引、稀疏索引。

8.如何建立一个索引

alter table 表名 add index(字段名)

9.索引的数据结构

索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引B+树索引

默认是B+数索引

9+索引失效

  1. 如果条件中有or,即使其中有部分条件带索引也不会使用

    要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引

  2. 复合索引,如果不用前列,后续列也将无法使用

  3. like查询是以%开头

  4. 存在索引列的数据类型隐式转换,则用不上索引,比如列类型是字符串,那一定要在条件中将数据使用引号引起来,否则不使用索引

  5. where子句里对索引列上有数学运算/使用函数,用不上索引

  6. 如果mysql觉得使用全表扫描比索引快,则不使用索引

9++什么情况下不推荐使用索引

  1. 数据唯一性差(一个字段的取值只有几种时)的字段不要使用索引

  2. 频繁更新的字段不要使用索引

  3. 字段不再where语句出现时不要添加索引,如果where后有 is null/is not null/like %输入符%等条件,不建议使用

  4. where子句里对索引列使用不等于,使用索引效果一般

10.索引的基本原理

索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。

索引的原理很简单,就是把无序的数据变成有序的查询

把创建了索引的列的内容进行排序

对排序结果生成倒排表

在倒排表内容上拼上数据地址链

在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

11.创建索引的三种方式

第一种方式:在执行CREATE TABLE时创建索引

第二种方式:使用ALTER TABLE命令去增加索引(可用于创建普通索引、unique索引和primary key 索引三种索引格式)

ALTER TABLE table_name ADD INDEX index_name (column_list);

第三种方式:使用CREATE INDEX命令创建(可用于对表增加普通索引或unique索引,可用于建表时创建索引)

CREATE INDEX index_name ON table_name (column_list);

删除索引

drop index index_name on table_name;
alter table table_name drop index index_name;
alter table table_name drop primary key;

12.百万级别或以上的数据如何删除

关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。

所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)
然后删除其中无用数据(此过程需要不到两分钟)
删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。
总结:先删除索引再删除数据

13.B树和B+树的区别

  • 在B树中,你可以将键和值存放在内部节点和叶子节点;但在B+树中,内部节点都是键,没有值,叶子节点同时存放键和值。
  • B+树的叶子节点有一条链相连,而B树的叶子节点各自独立。

img

14.使用B树的好处、使用B+树的好处

使用B树的好处
B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。

使用B+树的好处
由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间

15.什么是数据库事务?

事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。

16.事物的四大特性

原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

16+事务隔离级别

读未提交:安全性最差,可能发生并发数据问题,性能最好

如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据
解决了更新丢失,但还是可能会出现脏读

读提交:Oracle默认的隔离级别

如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。

可重复读:MySQL默认的隔离级别,安全性较好,性能一般

可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。

串行化:表级锁,读写都加锁,效率低下,安全性能高,不能并发

提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y6kyx2Ot-1629779155635)(C:\Users\MTA\AppData\Roaming\Typora\typora-user-images\image-20210809175027921.png)]

17.什么是脏读?幻读?不可重复读?

脏读(Drity Read):(读取未提交数据)某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。

不可重复读(Non-repeatable read):(前后多次读取,数据内容不一致)在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

幻读(Phantom Read):(前后多次读取,数据总量不一致)在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的 查询中,就会发现有几列数据是它先前所没有的。

18.varchar与char的区别

char的特点

char表示定长字符串,长度是固定的;

如果插入数据的长度小于char的固定长度时,则用空格填充;

因为长度固定,所以存取速度要比varchar快很多,甚至能快50%,但正因为其长度固定,所以会占据多余的空间,是空间换时间的做法;

对于char来说,最多能存放的字符个数为255,和编码无关

varchar的特点

varchar表示可变长字符串,长度是可变的;

插入的数据是多长,就按照多长来存储;

varchar在存取方面与char相反,它存取慢,因为长度不固定,但正因如此,不占据多余的空间,是时间换空间的做法;

对于varchar来说,最多能存放的字符个数为65532

大表数据查询,怎么优化

优化shema、sql语句+索引;
第二加缓存,memcached, redis;
主从复制,读写分离;
垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;

19.SQL优化

为什么要进行 SQL优化

系统的吞吐量瓶颈往往出现在数据库的访问速度上

优化成本:硬件> 系统配置> 数据库表结构 >SQL索引

优化效果:硬件< 系统配置<数据库表结构

SQL优化的具体操作

1.尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引扫描全表

例如:select * from t where username like ‘%陈%’;

优化方式:尽量在字段后面进行模糊查询

select * from t where username like ‘陈%’;

2.尽量避免使用 in/not in 会导致引擎扫描全表

例如: select * from t where id in(2,3);

优化方式:如果是连续的数值使用between代替

select * from t where id between 2 and 3

3.尽量避免使用or

例如: select * from t where id=2 or id=3;

优化方式:使用union拼接查询结果

select * from t where id=2

union

select * from t where id=3;

4.尽量避免null值的判断

select * from t where s is null;

优化方式:可以给字段添加默认值0,对0值进行判断

select * from t where s=0

5.尽量避免在where条件中等号左侧进行表达式函数

例如: select * from t where s/10=9;

优化 select * from t where s=9*10;

6.使用索引做条件查询的时候,尽量避免使用 < > != 等判断条件

7.Order By条件要与where中条件一致,否则order by不会利用索引排序

例如:select * from t order by age;

优化 select * from t where age>0 order by age;

20.Where和having的区别

  1. having 的使用前提是已经筛选出了这个字段

​ where可以在没有筛选出字段的时候也可以使用,where是从数据表中的字段直接进行的筛选的。

比如: select后面

1)select addtime,name from dw_users where addtime> 1500000000

2)select addtime,name from dw_users having addtime> 1500000000
  1. 只可以用having,不可以用where情况

当select的内容中有聚合函数的话只能用having。

多表联查

一、内连接 返回两个表的公共记录

select * from 表1 inner join 表2 on 表1.公共字段=表2.公共字段

二、左外连接 以左边的表为准,右边如果没有对应的记录用null显示

select * from 表1 left join 表2 on 表1.公共字段=表2.公共字段

三、右外连接 以右边的表为准,左边如果没有对应的记录用null显示

select * from 表1 right join 表2 on 表1.公共字段=表2.公共字段

聚合函数

SQL Aggregate Functions

  1. avg 求平均数
    select AVG(Amount) from Details;
  2. min求最小值

​ select MIN(Amount) from Details;

  1. max求最大值
    select MAX(Amount) from Details;

  2. sum求总和
    select SUM(Amount) from Details;

  3. count求数据的条数
    select COUNT(Amount) from Details;

前端

1.VUE生命周期

8个钩子函数

beforeCreate created

beforeMont monted

beforeUpdate updated

beforeDistory distoryed

2.什么是地狱回调问题

前端ajax的回调函数层级嵌套,上一层回调函数的结果是下一层回调函数的参数,这样的层层嵌套,如果有一层回调结果出错就会导致结果出错,这就是回调地狱问题。

如何解决回调地狱:

  1. 闭包非闭
  2. 使用promise对象 async await

分布式

什么是CAP理论

CAP理论是分布式领域中非常重要的一个指导理论,C(Consistency)表示强一致性,A(Availablity),P(Partition Tolerance)表示分区容错性,CAP理论指出在目前的硬件条件下,一个分布式系统是必须要保证分区容错性的,而在这个前提下,分布式系统要么保证CP,要么保证AP,无法同时保证CAP。

分区容错性表示,一个系统虽然是分布式的,但是对外看上去应该是一个整体,不能由于分布式系统内部的某个节点挂点,或网络出现了故障,而导致系统对外出现异常,所以,对于分布式系统而言是一定要保证分区容错性的。

强一致性表示,一个分布式系统中各个节点之间能及时的同步数据,在数据同步过程中,是不能对外提供服务的,不然就会造成数据不一致,所以强一致性和可用性是不能同时满足的

可用性表示,一个分布式系统对外要保证可用。

什么是BASE理论

由于不能同时满足CAP,所以出现了BASE理论

  1. BA::;Basically Available,表示基本可用,表示可以允许一定程度的不可用,比如由于系统故障,请求时间变长,或者由于系统故障导致部分非核心功能不可用,都是允许的
  2. S:Soft state表示分布式系统可以处于一种中间状态,比如数据正在同步
  3. E:Eventually consistent,表示最终一致性,不要求分布式系统数据实时达到一致,允许在经过一段时间后再达到一致,在达到一致过程中,系统也是可用的。

什么是RPC

RPC,表示远程过程调用,对于java这种面相对象语言,也可以理解为远程方法调用,RPC调用和HTTP调用是有区别的,RPC表示的是一种调用远程方法的方式,可以使用HTTP协议,或直接基于TCP协议来实现RPC,在java中,我们可以通过直接使用某个服务接口的代理对象来执行方法,而底层则通过构造HTTP请求来调用远端的方法,所以有一种说法是RPC协议是HTTP协议之上的一种协议,也是可以理解的。

分布式ID是什么?有哪些解决方案

在开发中,通常会需要一个唯一的ID来标识数据,可以通过数据库的主键,或直接在内存中维护一个自增数字来作为ID都是可以的,但对于一个分布式系统,就会有可能出现ID冲突,此时有以下解决方案

  1. uuid,这种方案复杂度最低,但是会影响存储空间和性能
  2. 利用单机数据库的自增主键,作为分式ID的生成器,复杂度适中,ID长度较之UUID更短,但是受到单机数据库性能的限制,并发量大的时候,此方案也不是最优方案。
  3. 利用redis、zookeeper的特性来生成id,比如redis 的自增命令、zookeeper的顺序节点,这种方案和单机数据库(MySQL)相比,性能有所提高,可以适当选用
  4. 雪花算法,一切问题如果能直接用算法解决,那就是最合理的,利用雪花算法也可以生成分布式ID,底层原理就是通过某台机器在某一毫秒内对某一个数字自增,这种方案也能保证分布式架构中的系统id唯一,但是只能保证趋势递增,业界存在tinyid、leaf等开源中间件实现了雪花算法。

分布式事务

主要使用的是阿里的 Seata框架

Seata AT

Seata At事务分两个阶段来管理全局事务

第一阶段:执行各分支事务

第二阶段:控制全局事务最终提交或者回滚

第一阶段:执行各分支事务

​ 微服务系统中,各服务之间无法相互感知事务是否执行成功,这时就需要一个专门的服务,来协调各个服务的运行状态。这个服务称为 TC(Transaction Coordinator),事务协调器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FRS8yDqm-1629779155641)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805145313386.png)]

订单系统开始执行保存订单之前,首先启动 TM(Transaction Manager,事务管理器),由 TM 向 TC 申请开启一个全局事务:这时TC会产生一个全局事务ID,称为 XID,并将 XID 传回 TM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z4R2HtsK-1629779155644)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805145421133.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xWmQAcpR-1629779155647)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805145831160.png)]

这样就开启了全局事务

全局事务开启后,开始执行创建订单的业务。首先执行保存订单,这时会先启动一个 RM(Resource Manager,资源管理器),并将 XID 传递给 RM。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zegrIbKJ-1629779155648)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805145920531.png)]

RM 负责对分支事务(即微服务的本地事务)进行管理,并与 TC 通信,上报分支事务的执行状态、接收全局事务的提交或回滚指令。

RM 首先会使用 XID 向 TC 注册分支事务,将分支事务纳入对应的全局事务管辖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d2ShljAr-1629779155651)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150007631.png)]

现在可以执行保存订单的分支事务了。一旦分支事务执行成功,RM 会上报事务状态:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HjOmzOMM-1629779155652)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150131603.png)]

TC 收到后,会将该状态信息传递到 TM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V5m8bpup-1629779155653)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150156454.png)]

到此,保存订单过程结束。下面是调用库存服务,减少商品库存,与订单的执行过程相同。

首先调用库存服务,启动 RM,并传递 XID:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vyoyrils-1629779155655)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150531072.png)]

库存服务的 RM 使用 XID 向 TC 进行注册,纳入全局事务管辖:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GuQlTd2V-1629779155657)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150619614.png)]

执行本地事务成功后上报状态,TC会将状态发送给TM:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24ClpDLC-1629779155658)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150655838.png)]

完成账户分支事务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wlf6JMHE-1629779155658)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805150852828.png)]

第二阶段:控制全局事务最终提交

TM(全局事务管理器)收集齐了全部分支事务的成功状态,它会进行决策,确定全局事务成功,向 TC 发送全局事务的提交请求:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0Phpl0o-1629779155659)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805151001405.png)]

TC 会向所有 RM 发送提交操作指令,RM 会完成最终提交操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mjqOY5oh-1629779155660)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805151259507.png)]

全局事务全部提交完成!

如果失败第二阶段:控制全局事务最终回滚

假设订单业务执行过程中,扣减账户金额这一步分支事务执行失败,那么失败状态对TC上报,然后再发送给TM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u9zcdc1q-1629779155662)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805151442868.png)]

TM 会进行决策,确定全局事务失败,向 TC 发送全局事务的回滚请求:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9689bw5g-1629779155663)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805151507982.png)]

然后,TC 会向所有 RM 发送回滚操作指令,RM 会完成最终回滚操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mX6H4cpo-1629779155664)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805160157049.png)]

TCC与AT 事务的主要区别为:

  • TCC 对业务代码侵入严重
    每个阶段的数据操作都要自己进行编码来实现,事务框架无法自动处理。
  • TCC 效率更高
    不必对数据加全局锁,允许多个事务同时操作数据。

Seata TCC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B2yyDYSW-1629779155666)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805161348485.png)]

第一阶段 Try
以账户服务为例,当下订单时要扣减用户账户金额:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tSBSgiei-1629779155667)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805163558966.png)]

假如用户购买 100 元商品,要扣减 100 元。

TCC 事务首先对这100元的扣减金额进行预留,或者说是先冻结这100元:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6YDfNhl8-1629779155668)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805163608157.png)]

第二阶段 Confirm
如果第一阶段能够顺利完成,那么说明“扣减金额”业务(分支事务)最终肯定是可以成功的。当全局事务提交时, TC会控制当前分支事务进行提交,如果提交失败,TC 会反复尝试,直到提交成功为止。

当全局事务提交时,就可以使用冻结的金额来最终实现业务数据操作:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QTC5rGvJ-1629779155669)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805163623571.png)]

第二阶段 Cancel
如果全局事务回滚,就把冻结的金额进行解冻,恢复到以前的状态,TC 会控制当前分支事务回滚,如果回滚失败,TC 会反复尝试,直到回滚完成为止。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZJx0jwUn-1629779155670)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805164134069.png)]

多个事务并发的情况
多个TCC全局事务允许并发,它们执行扣减金额时,只需要冻结各自的金额即可:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ML0baS7f-1629779155671)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210805164153738.png)]

面试难题

常见的运行时异常有哪些?

在代码里面如何处理这些异常呢?

部署项目出错了,怎么在Linux里面查看错误的日志?

十万条数据如何快速插入到数据库里面?如何快速删除

Linux

Linux常见的一些命令

一、目录操作

pwd				查看当前工作目录
clear 			清除屏幕
cd ~			当前用户目录
cd /			根目录
cd -			上一次访问的目录
cd ..			上一级目录

创建目录

mkdir aaa		在当前目录下创建aaa目录,相对路径;
mkdir ./bbb		在当前目录下创建bbb目录,相对路径;
mkdir /ccc		在根目录下创建ccc目录,绝对路径;

二、文件操作

删除

rm -r a.java		删除当前目录下的a.java文件(每次回询问是否删除y:同意)
1

强制删除

rm -rf a.java		强制删除当前目录下的a.java文件

三、文件内容操作(查看日志,更改配置文件)

修改文件内容

vim a.java   	进入一般模式
i(按键)   		进入插入模式(编辑模式)
ESC(按键)  		退出
:wq 			保存退出(shift+:调起输入框)
:q!			不保存退出(shift+:调起输入框)(内容更改)
:q				不保存退出(shift+:调起输入框)(没有内容更改)

四、压缩和解压缩

tar -zcvf start.tar.gz a.java b.java	将当前目录下a.java、b.java打包
tar -zcvf start.tar.gz ./*				将当前目录下的所欲文件打包压缩成haha.tar.gz文件

tar -xvf start.tar.gz				解压start.tar.gz压缩包,到当前文件夹下;
tar -xvf start.tar.gz -C usr/local(C为大写,中间无空格)
									解压start.tar.gz压缩包,到/usr/local目录下;

Linux下怎么部署自己的项目?部署项目的流程

  1. 安装docker
  2. 通过docker安装JDK
  3. 通过docker安装 tomcat
  4. 通过docker安装MySQL数据库
  5. 把数据的sql文件在导入数据库,开启数据库的root用户访问权限
  6. 关闭防火墙
  7. 启动tomcat就可以访问项目了。

shiro

1.什么是shiro

是一个强大易用的java安全框架,提供了认证、授权、加密、会话管理、与web集成、缓存等功能,对于任何一个应用程序,都可以提供全面的安全服务,相比其他安全框架,shiro要简单的多。

2.Shiro的核心概念Subject、SecurityManager、Realm

Subject:主体,代表了当前“用户”,是一个抽象概念,所有的Subject都绑定到SecurityManager,与Sunject的所有交互都会委托给SecurityManager;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且他管理着所有Subject;可以看出他是shiro的核心

Realm:域,shiro从Realm获取安全数据,就是说SecurityManager要验证用户身份,那么他需要从Realm获取响应的用户进行比较以去顶用户身份是否合法;也需要从Realm得到用户响应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

3.Authentication身份验证

principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。

credentials:证明/凭证,即只有主题知道的安全值,如密码/数字证书等。

身份认证流程:

  1. 首先调用Subject.login(token)进行登录,其会自动委托给SecurityManager,调用之前必须通过SecurityUtils.setSecurityManager()设置;
  2. SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
  3. Authenticator才是真正的身份验证者,shiro api中核心的身份认证入口点,此处可以自定义插入自己的实现;
  4. Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
  5. Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常标识身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
Authorization授权

授权方式:

1.编程式:通过写if/else授权代码完成

Subject subject=SecurityUtils.getSubject();

if(subject.hasRole(“admin”)){

//有权限

}else{

//无权限

}

2.注解

@RequiresRoles(“admin”)

public void helloWord(){

//有权限

}

授权流程:

1.首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;

2.Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;

3.在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;

4.Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthroizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

Cryptography加密

1.首先创建一个DfaultHashService,默认使用SHA-512算法;

2.可以通过hashAlgorithmName属性修改算法;

3.可以通过privateSalt设置一个私盐,其在散列时自动与用户传入的公盐混合产生一个新盐;

4.可以通过feneratePublicSalt属性在用户

设计模式

设计模式的原则

  1. 单一职责原则

    控制类的粒度大小,将对象解耦,提高其内聚性。

  2. 接口隔离原则

    要为各个类建立他们需要的专用接口

  3. 依赖倒转原则

    要面向接口编程,不要面向实现编程

  4. 里式代换原则

    继承必须确保超类所拥有的性质在子类中依然成立

  5. 开闭原则

    对扩展开放对修改关闭

  6. 迪米特法则

    只与你的直接朋友交谈,不跟陌生人说话

  7. 合成复用原则

    尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

防止订单重复提交的技术解决方案

  1. 创建唯一索引表:创建根据订单编号来插入的唯一索引表。不存在插入成功说明未重复,存在说明重复,无法插入报错
  2. redis计数器:订单进来创建该笔订单的唯一编号计数器+1,如果大于1说明重复,等于1说明不重复。
@Controller
@RequestMapping("/test")
public class TestController extends BaseController {
`@RequestMapping("/send")
	@ResponseBody
	public   String send(String id) {
		redisTemplate.opsForValue().increment(id, 1);//订单编号提交redis计数器+1
		//获取计数器是多少
		String  num=(String) redisTemplate.opsForValue().get(id);
		logger.info(num);
		//如果计数器不等于 说明订单重复提交 其实就是大于 1的情况
		if(!"1".equals(num)){
			throw new MessageException("订单重复提交");
		}
		return "hello";	
	}	

OOM(OutOfMemory内存溢出)

原因:JVM内存严重不足

分类:

1.java heap space

堆内存没有足够空间存放新创建的对象抛异常:java.lang.OutOfError:Java heap space错误

原因分析:

​ 1. 请求创建一个超大对象,通常是一个大数组

​ 解决方案:检查是否合理,比如是否一次性查询了数据库全部结果而没有做结果数限制

​ 2. 超出预期的访问量、数据量,通常是上游系统请求流量飙升,常见于各类促销、秒杀活动,可以结合业务流 量指标排查是否有尖状峰值

​ 解决方案:添加机器资源,或者做限流降级

  1. 过度使用终结器(Finalizer),该对象没有立即被GC

    解决方案:找到持有的对象,修改代码设计,比如关闭没有释放的连接

​ 4. 内存泄漏,大量对象引用没有释放,JVM无法对其自动回收,常见于使用了File等资源没有回收。

2.GC overhead limit exceeded

​ 当java晋城花费98%以上的时间执行GC,但只恢复了不到2%的内存,且该动作连续重复了5次,就会抛出异常java.lang.OutOfError:GC overhead limit exteeded

简单的说就是应用程序已经基本耗尽了所有的可用内存,GC也无法回收。

3.Permgen space

永久代(Permanent Generation)已用满,通常是因为加载的class数目太多或体积太大

原因分析:

永久代存储对象主要包括以下几类:

1.加载/缓存到内存中的class定义,包括类的名称,字段,方法和字节码;

2.常量池

3.对象数组/类型数组所关联的class

4.JIT编译器优化后的class信息

PermGen的使用量与加载到内存的class的数量/大小正相关

根据Permgen space报错时机,采用不同解决方案:

1.程序启动错误,修改MaxPermSize启动参数,调大永久代空间

2.应用重新部署时报错,很有可能是没有应用没有重启,导致加载了多份class信息,只需要重启JVM即可解决

3.运行时报错,应用程序可能会动态创建大量class,而这些class的生命呢周期很短暂,但是JVM不会加载calss,可以设置+CMSClassUnloadingEnabled和UseConcMarkSweepGC这两个参数允许JVM卸载class。如果无法解决,可以通过jmap命令dump内存对象jmap-dump:format=b,file=dump.hprof然后分析开销最大的classLoader和重复的class

4.Metaspace

ationStrategy进行多Realm身份验证;
5. Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常标识身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

Authorization授权

授权方式:

1.编程式:通过写if/else授权代码完成

Subject subject=SecurityUtils.getSubject();

if(subject.hasRole(“admin”)){

//有权限

}else{

//无权限

}

2.注解

@RequiresRoles(“admin”)

public void helloWord(){

//有权限

}

授权流程:

1.首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;

2.Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;

3.在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;

4.Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthroizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

Cryptography加密

1.首先创建一个DfaultHashService,默认使用SHA-512算法;

2.可以通过hashAlgorithmName属性修改算法;

3.可以通过privateSalt设置一个私盐,其在散列时自动与用户传入的公盐混合产生一个新盐;

4.可以通过feneratePublicSalt属性在用户

设计模式

设计模式的原则

  1. 单一职责原则

    控制类的粒度大小,将对象解耦,提高其内聚性。

  2. 接口隔离原则

    要为各个类建立他们需要的专用接口

  3. 依赖倒转原则

    要面向接口编程,不要面向实现编程

  4. 里式代换原则

    继承必须确保超类所拥有的性质在子类中依然成立

  5. 开闭原则

    对扩展开放对修改关闭

  6. 迪米特法则

    只与你的直接朋友交谈,不跟陌生人说话

  7. 合成复用原则

    尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

防止订单重复提交的技术解决方案

  1. 创建唯一索引表:创建根据订单编号来插入的唯一索引表。不存在插入成功说明未重复,存在说明重复,无法插入报错
  2. redis计数器:订单进来创建该笔订单的唯一编号计数器+1,如果大于1说明重复,等于1说明不重复。
@Controller
@RequestMapping("/test")
public class TestController extends BaseController {
`@RequestMapping("/send")
	@ResponseBody
	public   String send(String id) {
		redisTemplate.opsForValue().increment(id, 1);//订单编号提交redis计数器+1
		//获取计数器是多少
		String  num=(String) redisTemplate.opsForValue().get(id);
		logger.info(num);
		//如果计数器不等于 说明订单重复提交 其实就是大于 1的情况
		if(!"1".equals(num)){
			throw new MessageException("订单重复提交");
		}
		return "hello";	
	}	

OOM(OutOfMemory内存溢出)

原因:JVM内存严重不足

分类:

1.java heap space

堆内存没有足够空间存放新创建的对象抛异常:java.lang.OutOfError:Java heap space错误

原因分析:

​ 1. 请求创建一个超大对象,通常是一个大数组

​ 解决方案:检查是否合理,比如是否一次性查询了数据库全部结果而没有做结果数限制

​ 2. 超出预期的访问量、数据量,通常是上游系统请求流量飙升,常见于各类促销、秒杀活动,可以结合业务流 量指标排查是否有尖状峰值

​ 解决方案:添加机器资源,或者做限流降级

  1. 过度使用终结器(Finalizer),该对象没有立即被GC

    解决方案:找到持有的对象,修改代码设计,比如关闭没有释放的连接

​ 4. 内存泄漏,大量对象引用没有释放,JVM无法对其自动回收,常见于使用了File等资源没有回收。

2.GC overhead limit exceeded

​ 当java晋城花费98%以上的时间执行GC,但只恢复了不到2%的内存,且该动作连续重复了5次,就会抛出异常java.lang.OutOfError:GC overhead limit exteeded

简单的说就是应用程序已经基本耗尽了所有的可用内存,GC也无法回收。

3.Permgen space

永久代(Permanent Generation)已用满,通常是因为加载的class数目太多或体积太大

原因分析:

永久代存储对象主要包括以下几类:

1.加载/缓存到内存中的class定义,包括类的名称,字段,方法和字节码;

2.常量池

3.对象数组/类型数组所关联的class

4.JIT编译器优化后的class信息

PermGen的使用量与加载到内存的class的数量/大小正相关

根据Permgen space报错时机,采用不同解决方案:

1.程序启动错误,修改MaxPermSize启动参数,调大永久代空间

2.应用重新部署时报错,很有可能是没有应用没有重启,导致加载了多份class信息,只需要重启JVM即可解决

3.运行时报错,应用程序可能会动态创建大量class,而这些class的生命呢周期很短暂,但是JVM不会加载calss,可以设置+CMSClassUnloadingEnabled和UseConcMarkSweepGC这两个参数允许JVM卸载class。如果无法解决,可以通过jmap命令dump内存对象jmap-dump:format=b,file=dump.hprof然后分析开销最大的classLoader和重复的class

4.Metaspace

你可能感兴趣的:(java,面试)