面经1

1.JVM内存分区

https://leslieaibin.github.io/2020/11/10/JVM/1.Java%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E7%AE%80%E4%BB%8B/

2.对象加载在哪里,一定不能在栈上吗,为什么不直接在栈上。

1、功能和作用:

(1)栈,可以看成是方法的运行模型,所有方法的调用都是通过栈帧来进行的,JVM会为每个线程都分配一个栈区,JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作。当线程进入一个Java方法函数的时候,就会在当前线程的栈里压入一个栈帧,用于保存当前线程的状态(参数、局部变量、中间计算过程和其他数据),当退出函数方法时,修改栈指针就可以把栈中的内容销毁。

(2)堆,唯一的目的就是用于存放对象实例,每个Java应用都唯一对应一个JVM实例,每个JVM实例都唯一对应一个堆,并由堆内存被应用所有的线程共享。

所以,从功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的。

2、性能与存储要求:

(1)栈的性能比堆要快,仅次于位于CPU中的寄存器。但是,在分配内存的时候,存放在栈中的数据大小与生存周期必须在编译时是确定的,缺乏灵活性。

(2)堆可以动态分配内存大小,编译器不必知道要从堆里分配多少存储空间,生存周期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据,因此可以得到更大的灵活性。但是,由于要在运行时动态分配内存和销毁对象时都需要占用时间,所以效率低。由于面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定。当然,为达到这种灵活性,必然会付出一定的代价。

不一定,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有User对象都没有在堆上分配。

  • heap是堆,stack是栈。

  • stack的空间由操作系统自动分配和释放,heap的空间是手动申请和释放的,heap常用new关键字来分配。

  • stack空间有限,heap的空间是很大的自由区。在Java中,若只是声明一个对象,则先在栈内存中为其分配地址空间,若再new一下,实例化它,则在堆内存中为其分配地址。

  • 举例:数据类型 变量名;这样定义的东西在栈区。如:Object a =null; 只在栈内存中分配空间new 数据类型();或者malloc(长度); 这样定义的东西就在堆区如:Object b =new Object(); 则在堆内存中分配空间

3.线程池构造参数

https://leslieaibin.github.io/2021/04/29/Thread/5.%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%92%8C%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95/

1
2
3
4
5
6
7
8
9
10
ThreadPoolExecutor{
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程的存活时间
TimeUnit unit, // keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂,用于创建线程
RejectedExecutionHandler handler); // 饱和策略

}

线程方法状态转换

4.自旋锁,读写锁,公平锁,可重入锁。

https://leslieaibin.github.io/2021/05/03/Thread/6.java%E4%B8%AD%E7%9A%84%E9%94%81/

5.进程,线程通信方式

https://leslieaibin.github.io/2022/07/11/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/10.%E8%BF%9B%E7%A8%8B%E9%97%B4%E9%80%9A%E4%BF%A1/

6.tcp timewait,服务器很多timewait怎么办

https://leslieaibin.github.io/2021/09/05/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/7.%E4%B8%89%E6%AC%A1%E6%8F%A1%E6%89%8B%E5%9B%9B%E6%AC%A1%E6%8C%A5%E6%89%8B/

TIME_WAIT

  表示客户端主动关闭socket。

  原因:

  • 大量的短连接存在
  • 特别是 HTTP 请求中,如果 connection 头部取值被设置为 close 时,基本都由服务端发起主动关闭连接
  • TCP 四次挥手关闭连接机制中,为了保证 ACK 重发和丢弃延迟数据,设置 time_wait 为 2 倍的 MSL(报文最大存活时间)

  后果:  

  • TCP 连接中,「主动发起关闭连接」的一端,会进入 time_wait 状态
  • time_wait 状态,默认会持续 2 MSL(报文的最大生存时间),一般是 2x2 mins
  • time_wait 状态下,TCP 连接占用的端口,无法被再次使用;TCP 端口数量,上限是 6.5w(65535,16 bit)
  • 大量 time_wait 状态存在,会导致新建 TCP 连接会出错,address already in use : connect 异常
  • 占用内存,但内存占用并不大,1万条TIME_WAIT的连接,也就多消耗1M
  • 耗CPU,每次找到一个随机端口,需要遍历一遍bound ports的吧,这必然需要一些CPU时间,但也还好,无需太担忧

  解决:

  • 服务器端允许 time_wait 状态的 socket 被重用
  • 缩减 time_wait 时间,设置为 1 MSL
  • 修改内核参数:

CLOSE_WAIT

  表示服务端被动关闭socket。

  根据TCP状态机,服务器端收到客户端发送的FIN,则按照TCP实现发送ACK,因此进入CLOSE_WAIT状态。但如果服务器端不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。

  原因:应用程序写的有问题,没有合适的关闭socket;要么是服务器CPU处理不过来(CPU太忙)或者应用程序一直睡眠到其它地方(锁,或者文件I/O等等),应用程序获得不到合适的调度时间,造成程序没法真正的执行close操作。

  后果:出现大量的CLOSE_WAIT后,服务无法继续正常服务,端口无法被复用,socket资源被耗尽。因为Linux分配给一个用户的文件句柄是有限的,而如果一直被保持,则文件句柄也就不能close,导致句柄资源达到上线,接着就会出现大量Too Many Open Files错误。

7.MySQL联合索引,怎么保证一定走索引,幻读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
(1)    select * from myTest  where a=3 and b=5 and c=4;   ----  abc顺序
abc三个索引都在where条件里面用到了,而且都发挥了作用


(2) select * from myTest where c=4 and b=6 and a=3;
where里面的条件顺序在查询之前会被mysql自动优化,效果跟上一句一样


(3) select * from myTest where a=3 and c=7;
a用到索引,b没有用,所以c是没有用到索引效果的


(4) select * from myTest where a=3 and b>7 and c=3; ---- b范围值,断点,阻塞了c的索引
a用到了,b也用到了,c没有用到,这个地方b是范围值,也算断点,只不过自身用到了索引


(5) select * from myTest where b=3 and c=4; --- 联合索引必须按照顺序使用,并且需要全部使用
因为a索引没有使用,所以这里 bc都没有用上索引效果


(6) select * from myTest where a>4 and b=7 and c=9;
a用到了 b没有使用,c没有使用


(7) select * from myTest where a=3 order by b;
a用到了索引,b在结果排序中也用到了索引的效果,a下面任意一段的b是排好序的


(8) select * from myTest where a=3 order by c;
a用到了索引,但是这个地方c没有发挥排序效果,因为中间断点了,使用 explain 可以看到 filesort


(9) select * from mytable where b=3 order by a;
b没有用到索引,排序中a也没有发挥索引效果

2.索引失效的条件
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
存储引擎不能使用索引范围条件右边的列
尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描

is null,is not null也无法使用索引 ---- 此处存在疑问,经测试确实可以使用,ref和const等级,并不是all

like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描的操作。问题:解决like‘%字符串%’时索引不被使用的方法?

8.怎么杀死一个进程

https://blog.csdn.net/lechengyuyuan/article/details/16337233

9.为什么说B+树比B树更适合数据库索引?

  • 二叉查找树(BST):解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表
  • 平衡二叉树(AVⅥL):通过旋转解决了平衡的问题,但是旋转操作效率太低
  • 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了AⅥ旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多
  • B树:通过将二叉树改为多路平衡查找树,解决了树过高的问题
  • B+树:在B树的基础上,将非叶节点改造为不存储数据的纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效。

1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。

2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

3、方便范围查询,由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。

10.单例模式

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton{
private static Singleton instance;
private Singleton(){}

public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
// 这种实现最大的问题就是不支持多线程

懒汉式,线程安全

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {  
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
必须加锁 synchronized 才能保证单例,但加锁会影响效率。

饿汉式

1
2
3
4
5
6
7
8
public class Singleton {  
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
这种方式比较常用,但容易产生垃圾对象。

双检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {  
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这种方式采用双锁机制,安全且在多线程情况下能保持高性能。

11.MySql 和redis的区别

Redis 和 Mysql 的区别:

​ Mysql 是关系型数据库, 持久化存储, 存放在磁盘里, 功能强大. 检索的话, 会涉及到一定 IO , 数据访问也就慢;
​ Redis 是内存数据库,也是非关系型数据库 数据保存在内存中, 速度快;

Redis 的优点:

读写性能优异
支持数据持久化, 支持 AOF 和 RDB 两种持久化方式
支持主从复制, 主机会自动将数据同步到从机, 可以进行读写分离.
数据结构丰富 : 除了支持 string 类型的value 外还支持string, hash, set, stortset, list 等数据结构.

12.反射

反射就是在运行的时候知道自己是什么类,并能直接操作程序的内部属性和方法。

  • 如何反射获取 Class 对象

    1
    2
    3
    4
    5
    6
    7
    // 1.通过字符串获取Class对象,这个字符串必须带上完整路径名
    Class studentClass = Class.forName("com.test.reflection.Student");
    // 2.通过类的class属性
    Class studentClass2 = Student.class;
    // 3.通过对象的getClass()函数
    Student studentObject = new Student();
    Class studentClass3 = studentObject.getClass();
  • 如何反射获取类中的所有字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 1.获取所有声明的字段
    Field[] declaredFieldList = studentClass.getDeclaredFields();
    for (Field declaredField : declaredFieldList) {
    System.out.println("declared Field: " + declaredField);
    }
    // 2.获取所有公有的字段
    Field[] fieldList = studentClass.getFields();
    for (Field field : fieldList) {
    System.out.println("field: " + field);
    }

  • 如何反射获取类中的所有构造方法

  • 如何反射获取类中的所有非构造方法

静态代理:

一个代理类只能实现一种抽象主题角色,在程序运行之前,代理类.class文件就已经被创建,代理类和委托类的关系在运行前就确定。

动态代理:

一个代理类通过反射机制,可以实现多种不类型的抽象主题角色。动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。以下为动态代理概括图

13.Spring框架

IOC

Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。

img

即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

AOP

理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  ●谁依赖于谁:当然是应用程序依赖于IoC容器

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

动态代理

一起看看,动态代理到底是解决什么问题?

首先,它是一个代理机制。如果熟悉设计模式中的代理模式,我们会知道,代理可以看做是对调用目标的一个包装,这样我们对目标代码的调用不是直接发生的,而是通过代理完成。

JDK Proxy的优势:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比cglib更加可靠。
  • 平滑进行JDK版本升级,而字节码类库通常需要进行更新以保证在新版Java上能够使用。
  • 代码实现简单。

基于类似cglib框架的优势:

  • 有的时候调用目标可能不便实现额外接口,从某种角度看,限定调用者实现接口是有些侵入性的时间,类似cglib动态代理就没有这种限制。
  • 只操作我们关心的类,而不必为其它相关类增加工作量。
  • 高性能。

14.接口与抽象类的区别

抽象类:在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称为抽象方法,抽象方法只有方法的声明,没有方法体。抽象类的特点:

a、抽象类不能被实例化只能被继承;

b、包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;

c、抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;

d、一个子类继承一个抽象类,则子类必须实现父类抽象方法,否则子类也必须定义为抽象类;

e、抽象类可以包含属性、方法、构造方法,但是构造方法不能用于实例化,主要用途是被子类调用。

接口\Java中接口使用interface关键字修饰,特点为:

a、接口可以包含变量、方法;变量被隐士指定为public static final,方法被隐士指定为public abstract(JDK1.8之前);

b、接口支持多继承,即一个接口可以extends多个接口,间接的解决了Java中类的单继承问题;

c、一个类可以实现多个接口;

相同点

(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

不同点

(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。

(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。

(3)接口强调特定功能的实现,而抽象类强调所属关系。

(4)接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。

15.threadlocl

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap

ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocalvalue为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。

每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离

ThreadLocalMap有点类似HashMap的结构,只是HashMap是由数组+链表实现的,而ThreadLocalMap中并没有链表结构。

我们还要注意Entry, 它的keyThreadLocal<?> k ,继承自WeakReference, 也就是我们常说的弱引用类型。

16.重载重写

重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

重写规则:

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 如果不能继承一个类,则不能重写该类的方法。

重载(Overload)

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

最常用的地方就是构造器的重载。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。