「高级java工程师」常见面试题及其答案:

「高级java工程师」常见面试题及其答案(持续更新)_好人老李的博客-CSDN博客

目录

java基础

面向对象与面向过程的区别?

JRE、JDK、JVM的区别?

java的数据类型有哪些?

重写和重载的区别?

java创建对象有几种方式?

反射

什么是反射?有什么作用?

反射的优缺点?

==和equals()的区别?

final、finally、 finalize 的区别?

String

String类为什么设计成不可变?

String、StringBuffer和StringBuilder的区别?

异常

java常见的异常类型有哪些?有什么区别?

IO

BIO、NIO、AIO 有什么区别?

序列化和反序列化

什么是序列化和反序列化?

序列化的作用?

什么是serialVersionUID?

如何生成serialVersionUID?

如何实现序列化?

集合

ArrayList和LinkedList的区别?

HashMap 的工作原理?

HashMap的扩容机制?

ConcurrentHashMap工作原理?

HashMap和HashTable的区别?

多线程

进程和线程的区别?

线程有几种状态?

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

创建线程有哪几种方式?

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

线程池的工作原理?

synchronized与ReentrantLock的区别?

什么是死锁?如何防止死锁?

什么是threadlocal?工作原理?

volatile的作用和原理?

JVM

JVM对锁进行了哪些优化?

JVM 是由哪几部分组成的?

谈谈类的加载过程?

类加载器有哪些?

什么是双亲委派模型?有没有办法打破?

java对象的内存结构?

什么是引用?java中引用有几种类型?

深拷贝和浅拷贝的区别?

gc有哪些类型?有什么区别?

常用的垃圾回收算法有哪些?

jvm中,有哪些垃圾收集器?

哪些对象可以作为GC时的根节点?

Spring

Spring、SpringMVC、SpringBoot的关系?

mysql

索引为什么会让查询变快?

MyISAM和innoDB的区别?

并发事务有哪些问题?

数据库的事务隔离?

redis

redis为什么快?

redis为什么是单线程?

redis 持久化有几种方式?

缓存常见问题及其解法?

消息队列

消息队列的作用?如何选型?

消息队列常见问题及其解法?

分布式

什么是CAP理论和BASE理论?

ZK的工作原理?使用场景?

dubbo的架构和工作原理?

网络通信

TCP三次握手和四次挥手的过程?

TCP是3次握手,但挥手为什么需要4次,而不是3次挥手?

安全

加密算法有哪些?

加密和签名,有什么区别?


java基础

面向对象与面向过程的区别?

面向过程(Procedure xx,简称POP)的思想是:

分析解决问题需要哪些步骤 → 每一个步骤使用函数实现 → 依次调用函数来解决问题

以「把大象搬进冰箱」这个经典问题为例,面向过程的解决思路:

  1. 解决问题分3步:打开冰箱 → 搬大象 → 关闭冰箱

  2. 设计对应的方法:openFridge()、moveElephant()、closeFridge()

  3. 依次调用

int main() { openFridge(); moveElephant(); closeFridge();
}

面向对象(Object xx,简称OOP)的思路是:

分析问题由哪些对象组成 → 使用类实现每一个对象 → 调用对象的方法来解决问题

还是以「把大象搬进冰箱」为例:

  1. 这个问题有涉及2个对象,冰箱和大象

  2. 使用类实现冰箱和大象,冰箱要具备开门和关门的能力,大象要具备走路的能力

  3. 实例化冰箱和大象,调用对象的方法就可以解决问题

class Elephant{ public static void walkInto(target){}
}
class Fridge{ public static void open(){ } public static void close(){ }
}
class APP{ public static void main(String[] args){ Fridge.open(); //冰箱开门 Elephant.walkInto(Fridge); //大象走进冰箱 Fridge.close(); //冰箱关门 }
}

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

面向过程:

  • 优点:性能好,面向对象需要先对类进行实例化,再调用对象的方法,面向过程直接调用函数

  • 缺点:复用性差,扩展性差

面向对象:

  • 优点:面向对象有封装、继承、多态的特性,易复用易扩展

  • 缺点:性能相对差一些,一般来说java程序执行速度比C慢10倍

JRE、JDK、JVM的区别?

  • JDK:Java Development Kit,是一个工具包,用于开发和运行Java程序,包含了java开发工具和JRE。
  • JRE:Java Runtime Environment,提供了一个java程序的运行环境。
  • JVM:Java 虚拟机,负责执行 Java 程序。

总结:JDK包含JRE,JRE包含JVM。

java的数据类型有哪些?

java的数据类型有两种:

  1. 基本数据类型
  2. 引用数据类型

基本数据类型又包括四类8种:

  1. 整数型:byte 1字节 [-128~127],short 2字节,int 4字节,long 8字节
  2. 浮点型:float  4字节,double(默认) 8字节
  3. 字符型:char,2字节,unicode编码值
  4. 布尔型:boolean,1字节,值只有true和false

重写和重载的区别?

  • 重载:overloading,发生在类内部,方法名相同,参数不同。
  • 重写:overriding,是子类对父类的方法进行重写, 返回值和形参不能改变。
  • 重写和重载是java多态性的不同表现形式

java创建对象有几种方式?

1. new

Person person = new Person(18);

2. Class.newInstance

Class person = Person.class;
Person person = null;
try {person = (Person) person.newInstance();
} catch (Exception e) {e.printStackTrace();
}

3. 反序列化(比较耗内存)

Person person = new Person("fsx", 18);
byte[] bytes = SerializationUtils.serialize(person);
Object deserPerson = SerializationUtils.deserialize(bytes);

4. clone()(对象必须实现Cloneable接口,并重写clone方法)

Person person1 = new Person(18);
Person person2 = person1.clone();

反射

什么是反射?有什么作用?

什么是反射:反射是java的一种机制,可以在运行态,获取任意一个类的所有属性和方法,可以用来创建对象、调用方法、对属性进行赋值。

Class<?> clz = Class.forName("xxx.User");
Object object = clz.newInstance();

反射的优缺点?

反射的优点:能够在运行时动态获取类的实例,提高了程序的灵活性

反射的缺点:反射机制中包括了一些动态类型,JVM无法对反射代码进行优化,因此性能较差,对性能要求高的程序尽量少用反射。

==和equals()的区别?

==:引用是否相同,是不是指向同一个内存空间

equals():值是否相同,所指向的内存空间的值是不是相同

String a = new String("123");
String b = new String("123");
a == b //false
a.equals(b) //true

final、finally、 finalize 的区别?

  • final:java的1个关键字,代表最终。如果一个类被声明为final,它不能被继承;如果变量声明为final,给定初始值后,不可修改;如果方法声明为final,不能被重载。
  • finally:java的一种异常处理机制,finally代码总会执行。
  • finalize:java中的一个方法名,在Object类中定义,因此所的类都继承了它,finalize()方法在垃圾收集器删除对象之前对会被调用。

String

String类为什么设计成不可变?

  • 字符串常量池:字符串常量池(String pool) 是Java堆内存中的一个特殊存储区域,当创建一个String对象时,如果此字符串值已存在于常量池,则不会创建一个新的对象,而是引用已经存在的对象,达到复用的效果。String类如果可变,常量池就不支持了。
  • 效率:String对象经常会被比较,如果不可变,String对象的哈希码就可以被缓存,不必每次都计算哈希码,提升性能。

  • 安全:String经常用做重要参数使用,例如URL、文件路径等,如果可变,会有安全隐患。

String、StringBuffer和StringBuilder的区别?

  • String是一个字符串常量,不可改变;StringBuffer和StringBuilder是字符串变量,可以改变,但StringBuffer是线程安全的,StringBuilder是非线程安全的。
  • 如何选择?如果修改少,使用String;如果在多线程下经常修改,使用SreingBuffer;如果是单线程下经常修改,使用StringBuilder。

异常

java常见的异常类型有哪些?有什么区别?

java异常的顶层类是Throwable,他的子类是:Error和Exception。

Error:运行时环境错误,如:内存溢出、系统崩溃等,程序无法恢复。

Exception:可捕获且可恢复的异常,Exception又分为两类:

  • CheckedException(可检查异常/编译时异常):可检查异常需要在源代码里显式地使用try catch捕获,否则编译不过。如IOException、SQLException等。
  • RuntimeException(不可检查异常/运行时异常):运行时异常是可能被程序员忽略的异常。如:ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常等。

IO

BIO、NIO、AIO 有什么区别?

先了解下1个IO请求的处理过程:

同步和异步的区别:同步和异步,是处理方处理IO请求的2种方式,同步是指IO处理线程会一直等待相关的IO数据就绪后再执行逻辑处理,异步是指IO处理线程不会一直等待相关的IO数据就绪,比如可以轮询查看相关IO数据是否已经准备OK。

阻塞和非阻塞的区别:阻塞和非阻塞,是调用方的2种IO请求方式,阻塞是指调用方一直等待处理方的结果,非阻塞是调用方不会一直等待处理方的结果,可以先去执行其他任务,过一段时间再来查看结果是否返回。

BIO:Blocking IO,同步阻塞IO。调用方发起IO请求后,会一直阻塞等待结果返回,同时处理方会一直等到IO数据就绪后,再进入处理。优点:一请求一应答的方式,逻辑简单,易实现。缺点:大量等待,性能很差。

NIO:Non-Blocking IO,同步非阻塞IO。调用方发起IO请求后,会一直阻塞等待结果返回。IO处理线程不会原地等待IO数据,可以先做其他事情,定时轮询检查IO数据是否就绪。

AIO:异步非阻塞IO。调用方发起IO请求后,不会等待结果,先处理其他事情,处理方执行完操作后利用系统函数告知调用方结果。IO处理线程不会原地等待IO数据,可以先做其他事情,定时轮询检查IO数据是否就绪。

序列化和反序列化

什么是序列化和反序列化?

序列化是指将对象写入IO流,反序列化是指从IO流中恢复对象。

序列化的作用?

  1. 远程传输对象
  2. 持久化保存对象

什么是serialVersionUID?

  • serialVersionUID是序列化的版本号, 序列化时,其值与数据一起存储;反序列化时,将检查序列化数据是否与当前类版本匹配。
  • 实现Serializable接口的类都有一个表示序列化版本标识符的静态变量,缺少serialVersionUID时,IDE会发出警告。

如何生成serialVersionUID?

  1. 使用默认值:private static final long serialVersionUID = 1L;
  2. 自动生成: private static final long serialVersionUID = 4603642343377807741L;
  3. 看诉求:如果希望类的不同版本序列化时兼容,需确保类的不同版本具有相同的serialVersionUID;如果不希望类的不同版本序列化时兼容,需确保类的不同版本具有不同的serialVersionUID。

如何实现序列化?

注意:JavaBean实体类必须实现Serializable接口,否则无法序列化。

public class User implements Serializable {   private static final long serialVersionUID = -7890663945232864573L;   private String userName;   public String getUserName() {     return userName;   }   public void setUserName(String userName) {     this.userName = userName;   }@Override   public String toString() {     return "User [userName=" + userName + ", passWord=" + passWord + "]";   }
}

方式1:IO流序列化

Java原生序列化方法是指通过 InputStream 和 OutputStream 之间的转化进行序列化和反序列化。

User u = new User();
u.setUserName("张三");
// 序列化
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obj = new ObjectOutputStream(out);
obj.writeObject(u);
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(new java.io.ByteArrayInputStream(out.toByteArray())));
User user = (User) ois.readObject();

方式2:jackson序列化

大多数公司都将json作为服务器端返回的数据格式,Json序列化一般会使用jackson包。

import com.fasterxml.jackson.databind.ObjectMapper;
User u = new User();
u.setUserName("张三");
// 序列化
ObjectMapper mapper = new ObjectMapper();
byte[] writeValueAsBytes = null;
writeValueAsBytes = mapper.writeValueAsBytes(u);
// 反序列化
User user = mapper.readValue(writeValueAsBytes, User.class);

方式3:FastJson/Gson

fastjson是由阿里巴巴开源的Json解析器和生成器,不过安全漏洞多,不建议使用,推荐使用Google的Gson。

集合

ArrayList和LinkedList的区别?

  1. 都是对List接口的实现,但一个底层是Array(动态数组),一个是Link(链表)
  2. 随机访问时(get和set操作),ArrayList比LinkedList的效率更高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找。
  3. 对数据进行增加和删除的操作时(add和remove操作),LinkedList比ArrayList的效率更高,因为ArrayList是数组,所以在其中进行增删操作时,会对操作点之后所有数据的下标索引造成影响,需要进行数据的移动。

HashMap 的工作原理?

HashMap是Map接口的一种实现,用于存储K-V数据结构的元素。

HashMap的数据结构:

  • JDK7,HashMap的内部数据结构是数组+链表:

  • JDK8开始,当链表长度 > 8时会转化为红黑树,当红黑树元素个数 ≤ 6时会转化为链表。

put元素的原理:

  1. 计算K的hash值:hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)

  2. 计算K的数组位置:index = hash & (length - 1)

  3. 如果有相同K,则覆盖V

  4. 如果没有相同K,JDK7采用头插法(刚添加的元素被访问的概率大),但会引入循环引用问题,导致CPU高,JDK8开始采用尾插法,避免了这个问题。

  5. 如果元素个数超过阈值,进行扩容或数据结构变更(链表 → 红黑树)的操作。

get元素的原理:

  1. 计算K的hash值

  2. 计算K的数组index值

  3. 遍历寻找元素

HashMap的扩容机制?

什么时候扩容?

当元素数量超过阈值时扩容,阈值 = 数组容量 * 加载因子,数组容量默认16,加载因子默认0.75,所以默认阈值12。

为什么容量必须是2的幂?

计算K的数组位置公式:index = h & (length-1),由于2的幂次方-1都是1,这样运算时就可以充分利用到数据的高低位特点,减少hash冲突的概率,提升存取效率。

扩容的原理:

  1. 创建新数组,容量翻倍

  2. 旧数组元素迁移到新数组

ConcurrentHashMap工作原理?

HashMap线程不安全,多线程环境可以使用Collections.synchronizedMap、HashTable实现线程安全,但性能不佳。ConurrentHashMap比较适合高并发场景使用。

ConcurrentHashMap JDK7实现原理:

数据结构:

  • ConcurrentHashMap由一个Segment数组构成(默认长度16),Segment继承自ReentrantLock,所以加锁时Segment数组元素之间相互不影响,所以可实现分段加锁,性能高。

  • Segment本身是一个HashEntry链表数组,所以每个Segment相当于是一个HashMap。

​put元素原理:

  1. 计算K的hash值

  2. 计算K在Segment数组中的下标

  3. 对segment[i]加锁,加入元素,逻辑和HashMap一致

  4. 对segment[i]解锁

  5. 判断是否需要扩容、链表→红黑树

ConcurrentHashMap JDK8的实现原理:

数据结构:Node数组+链表/红黑树

  • 为提升存取效率,摒弃Segment,使用Node数组+链表/红黑树的数据结构。其中,Node和HashEntry的作用相同,但把值和next采用了volatile修饰,保证了可见性;引入了红黑树,元素多时,存取效率高。

  • 并发控制使用Synchronized和CAS来操作,整体看起来像是线程安全的JDK8 HashMap。

​put元素原理:

  1. 计算K的hash值

  2. 计算K在Nodet数组中的下标

  3. 如果头节点为空,则创建Node元素,调研Unsafe.CAS插入元素,失败则自旋保证成功。

  4. 如果头节点不为空,利用 synchronized 锁插入数据

  5. 判断是否需要扩容、链表→红黑树

HashMap和HashTable的区别?

  1. HashTable的方法是Synchronize的,所以线程安全,HashMap非线程安全。
  2. HashMap允许将null作为key或value,Hashtable不允许。
  3. HashTable初始长度是11,HashMap是16,负载因子都是0.75
  4. HashTable扩容是两倍+1 ,HashMap的扩容是两倍。

多线程

进程和线程的区别?

  • 进程是执行中的一段程序,而一个进程中执行中的每个任务即为一个线程。
  • 一个线程只可以属于一个进程,一个进程能包含多个线程。
  • 进程是资源分配的最小单位,线程是资源调度的最小单位。
  • 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间。进程中的线程共享进程中的数据。

线程有几种状态?

  • 新建状态(New):新创建一个线程对象
  • 就绪状态(Runnable):调用线程对象的start()方法,变得可运行,等待cpu的使用权。
  • 运行状态(Running):就绪状态的线程获取到了cpu的时间片,执行程序代码。
  • 阻塞状态(Blocked):线程因为某种原因(调用sleep()、wait()等)放弃cpu的使用权,暂停或停止运行,直到线程进入就绪状态,才有机会获得cpu的使用权从而转入运行状态。
  • 死亡状态(Dead):线程执行完或因异常而退出run()方法

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

相同点:可以使当前的线程进入阻塞状态

不同点:

  1. 所属类不同:sleep()在Thread类中,wait()在Object类中
  2. 调用要求不同:sleep()可以在任何场景下调用,wait()必须使用在同步代码块或同步方法中调用
  3. 是否释放锁:如果两个方法都使用在同步代码块或同步方法中,sleep()不会让线程释放锁,wait()会让线程释放锁。

创建线程有哪几种方式?

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

1. 【推荐】使用退出标志,使线程正常退出run()方法

public void run() {while(!flag) {xxx}
}

2. 使用interrupt()方法中断线程

MyThread thread = new MyThread();
thread.start();
thread.interrupt(); 

如果调用sleep、wait等方法让线程进入阻塞态(线程休眠),线程扔可以感受到中断信号,并且会抛出一个 InterruptedException异常,同时清除中断信号,将中断标记位设置成 false。

3. 使用stop()方法强行终止【不推荐,已废弃】

MyThread thread = new MyThread();
thread.start();
thread.stop(); 

线程池的工作原理?

线程池主要参数:

public ThreadPoolExecutor( int corePoolSize, //核心线程数 int maximumPoolSize, //最大线程数量 long keepAliveTime, //空闲线程存活时间 TimeUnit unit, //时间单位 BlockingQueue<Runnable> workQueue) //任务队列 {}

线程池工作原理:

  1. 提交任务

  2. 核心线程池没满,则创建核心线程执行任务

  3. 核心线程池已满,但等待队列没满,则加入等待队列

  4. 核心线程池已满,等待队列已满,但没有达到最大线程数,则创建非核心线程执行任务

  5. 核心线程池已满,等待队列已满,达到最大线程数,则执行抛弃策略

抛弃策略有哪些?

  • AbortPolicy:抛异常【默认策略】

  • DiscardPolicy:直接抛弃

  • DiscardOldestPolicy:丢弃队列里最老的任务

  • CallerRunsPolicy:谁提交谁执行

synchronized与ReentrantLock的区别?

java实现加锁,主要的方式是synchronized与ReentrantLock。

① 实现原理不同

synchronized原理:

  • synchronized是java的1个关键字,所以其加锁是依赖JVM实现的。

  • 如果synchronized修饰的是一般方法,对应的锁则是对象;修饰静态方法,锁的是当前类的Class实例;修饰代码块,锁的是传入synchronized的对象。

public synchronized void fun() {}
public static synchronized void fun() {}
synchronized(obj) {}
  • synchronized 关键字经过编译,会在同步块的前后生成 monitorenter 和 monitorexit 这两个字节码指令,他们的作用就是获取和释放对象的锁。

  • 那对象的锁在哪里?1个对象由三个部分组成:对象头、实例数据、对齐填充,1个对象的锁状态就存储在对象头的markword中,有无锁、偏向锁、轻量级锁、重量级锁,4种锁状态。

ReentrantLock的原理:

  • ReentrantLock是Lock的子类,底层使用CAS+AQS队列来实现加锁,使用lock()方法加锁,unlock()解锁。

private volatile ReentrantLock lock = new ReentrantLock();
lock.lock();
try { //xxx
} finally { lock.unlock();
}
  • lock()方法:当线程调用该方法,如果锁当前没有被任何线程占用,则当前线程获取到锁,然后设置锁的拥有者为当前线程,并设置AQS的状态值为1;如果当前线程之前已获得该锁,则只把AQS的状态值加1;如果锁已被其他线程持有,则线程会被放入AQS队列后阻塞挂起。

② 是否公平锁

公平锁:先来先得,按照申请锁的顺序去获得锁

  • synchronized为非公平锁

  • ReentrantLock可以选择公平非公平,通过构造方法传入boolean值进行选择,默认false非公平,true为公平。

③ 是否可主动释放锁

  • synchronized 不需要手动释放锁,优点是不会忘记释放锁,缺点是无法干预锁,只能等JVM释放。

  • ReentrantLock需要手动释放锁,优点是灵活,不需要锁了就可以放弃,不需要一直阻塞等待,缺点是可能忘记释放锁,导致死锁。

④ 锁是否可中断

  • synchronized不可中断

  • ReentrantLock可调用interrupt方法进行中断,更加灵活。

如何选择?

  • 如果对公平、中断、可释放等有诉求,可以选ReentrantLock,否则都可以使用synchronized,JVM一直在对synchronized进行优化,性能不差。

什么是死锁?如何防止死锁?

什么是死锁:多个进程或线程一直在互相等待对方的资源

产生死锁的四大必要条件:

  1. 资源互斥:资源同一时刻只能被一个进程或线程使用
  2. 请求和保持:线程获得资源后,又对其他资源发出请求,但是该资源被其他进程占有,此时请求阻塞,但又对自己已有的资源保持不放
  3. 资源不可剥夺:资源不能被其它进程或线程强制剥夺,需要等资源占有者主动释放。
  4. 环路等待:系统中有两个或两个以上的进程或线程组成一条等待环路

防止死锁产生的方法:破坏4大条件

什么是threadlocal?工作原理?

多线程访问共享变量的场景中,一般的解决办法是对共享变量加锁。但是加锁会带来性能的下降,所以ThreadLocal用了一种空间换时间的设计思想,也就是说在每个线程里面,都有一个容器来存储共享变量的副本,然后每个线程只对自己的变量副本来做更新操作,这样既解决了线程安全问题,又避免了多线程竞争加锁的开销。

具体实现:在Thread类里面有一个成员变量ThreadLocalMap,专门存储当前线程的共享变量副本,后续这个线程对于共享变量的操作,都是从这个ThreadLocalMap里面进行变更,不会影响全局共享变量的值。

volatile的作用和原理?

并发编程三要素 :原子性、可见性、有序性。

volatile具备可见性、有序性,不具备原子性,并不能保证线程安全,但常与 CAS 结合,形成一种高性能的无锁。

1. 可见性

当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。即可见性。

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝:

当一个线程修改volatile变量,会立即被更新到主内存中;当其他线程读取volatile变量时,它会直接从主内存中读取。

2. 有序性

编译器和处理器为了优化程序性能而对指令序列进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。被volatile修饰的变量,会禁止指令重排序。从而保证有序性。

JVM

JVM对锁进行了哪些优化?

① 锁升级

锁有无锁、偏向锁、轻量级锁、重量级锁,4状态,标识在对象头中的 mark word。

  • 偏向锁:如果锁不存在竞争,就没必要上锁,打个标记就行。当第一个线程获取锁,会记录下这个线程ID,如果该线程再次尝试获取锁,就可以直接获取锁,开销小。

  • 轻量级锁:大部分情况下,synchronized 中的代码块是被多个线程交替执行的,并不存在太多的竞争发生或者只有短时间的锁竞争,重量级锁是没必要的,采用 CAS 更加合理。轻量级锁是指偏向锁被另一个线程访问时,说明发生了竞争,那么偏向锁就会升级为轻量级锁,线程会通过自旋的方式尝试获取锁。

  • 重量级锁:当多个线程获取轻量级锁,大量自旋反而带来性能消耗,锁会膨胀为重量级锁。重量级锁会让其他申请却拿不到锁的线程进入阻塞状态。

② 锁消除:经过逃逸分析,发现某些对象不可能被其他线程访问,会把相关锁去除。

③ 锁粗化:把几个 synchronized 块合并为一个同步块,把中间无意义的解锁和加锁消除,避免无效的加解锁,提升性能。

④ 自适应的自旋锁:根据自旋的成功率、当前锁拥有者的状态等因素,决定自旋等待的时间,更加智能。

JVM 是由哪几部分组成的?

​        JVM主要包含4个部分,运行时数据区、类加载器、执行引擎、本地库接口。其中运行时数据区最关键,由5部分组成:

  1. 堆:线程共享,大部分对象在这里分配内存。

  2. 方法区:线程共享,存储已被虚拟机加载的类信息,比如常量、静态变量

  3. java虚拟机栈:存储java方法的局部变量、方法出口等信息。

  4. 本地方法栈:与虚拟机栈作用一样

  5. 程序计数器:线程当前执行的字节码行号

谈谈类的加载过程?

类加载主要有3个步骤:加载 → 链接 → 初始化

① 加载:使用类加载器把class字节码文件装载入内存中。(类加载器包括启动类加载器、扩展类加载器、应用类加载器、的自定义类加载器)

② 链接:链接又分为3步

  • 验证:保证加载进来的字节码符合虚拟机规范,不会造成安全问题。

  • 准备:为类变量(不是实例变量)分配内存,赋初始值。

  • 解析:把类名、方法名、字段名等(符号引用),替换为具体的内存地址(直接引用)。

③ 初始化:对static修饰的变量或语句,进行初始化。如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

类加载器有哪些?

JVM在运行的时候,会产生3个类加载器,这三个类加载器组成了一个层级关系每个类加载器分别去加载不同作用范围的jar包:

  1. Bootstrap ClassLoader:加载Java核心类库,也就是 %{JDK_HOME}\lib下的rt.jar、resources.jar等
  2. Extension ClassLoader:加载%{JDK_HOME}\lib\ext目录下的jar包和class文件
  3. Application ClassLoader:加载当前应用里面的classpath下的所有jar包和类文件
  4. 除了系统提供的类加载器外,还可以通过ClassLoader类实现自定义加载器,去满足一些特殊场景的需求。

什么是双亲委派模型?有没有办法打破?

当需要加载一个class文件的时候,首先会把这个class的查询和加载委派给父加载器去执行,如果父加载器都无法加载,再尝试自己来加载这个class。

双亲委派并不是一个强制性的约束模型,我们可以通过一些方式去打破:

  1. 第一种,继承ClassLoader抽象类,重写loadClass方法,在这个方法可以自定义要加载的类使用的类加载器。
  2. 第二种,使用线程上下文加载器,可以通过java.lang.Thread类的setContextClassLoader()方法来设置当前类使用的类加载器类型。

java对象的内存结构?

java对象由三部分组成:对象头、对象体、对齐字节

对象头:

  • Mark Word:表示对象的线程锁状态
  • Klass Word:指向方法区中对应的Class信息
  • 数组长度:可选,只有对象是数组时才会有这个部分

对象体:保存对象的属性和值

对齐字节:减少堆内存碎片

什么是引用?java中引用有几种类型?

什么是引用:

  • 在Java中,访问对象时,不会直接访问对象在内存中的数据,而是通过引用去访问。因此,引用也是一种数据类型,类似于C/C++ 语言中的指针。
  • 定义引用时,会在栈中分配内存,然后指向对象的内存地址。

引用的类型:

  1. 强引用:当我们使用new创建对象时,被创建的对象就是强引用,如Object object = new Object()。只要强引用存在,垃圾回收器将永远不会回收被引用的对象。内存不足时,JVM宁愿抛出OutOfMemoryError,也不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,JVM就可以适时回收对象。
  2. 软引用:用来描述一些非必需但仍有用的对象。只有内存不足时,系统才会回收软引用对象,可以用SoftReference类来表示软引用。
  3. 弱引用:只要 JVM 开始进行垃圾回收,弱引用关联的对象都会被回收。可以用 WeakReference 来表示弱引用。
  4. 虚引用:对象仅持有虚引用,和没有任何引用一样,随时可能会被回收,可以用PhantomReference 类来表示,可以用来跟踪对象被回收的状态。

深拷贝和浅拷贝的区别?

  • 深拷贝:拷贝对象的所有属性,如果属性是对象,也会对这些对象进行深拷贝,直到最底层的基本数据类型为止。所以,原对象的属性值发生了变化,深拷贝后的对象不会受到影响。
  • 浅拷贝:只拷贝对象的第一层属性,如果这些属性是对象,则不会对这些对象进行拷贝,而是直接复制对象的引用。如果原对象的属性值发生了变化,浅拷贝后的对象属性值也会改变。

gc有哪些类型?有什么区别?

  • Full GC/Major GC:针对整个堆进行GC,包括年轻代、老年代、元空间(永久代)
  • MinorGC:年轻代的内存分布为:Eden : From survivor : To survivor = 8:1:1,大多数新对象都在Eden区,当Eden区被占满时,会触发MinorGC,把存活下来的对象转移到To survivor,将From survivor清空。

常用的垃圾回收算法有哪些?

1. 引用计数法

假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失效时,对象A的引用计数器就-1,如果对象A的计算器的值为0,就说明对象A没有引用了,可以被回收。

缺点:无法解决循环引用问题

虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不回被回收。

2. 标记清除法

标记 :从根节点开始标记引用的对象。

清除 :未被标记引用的对象就是垃圾对象,可以被清理。

缺点:效率较低,标记和清除两个动作都需要遍历所有的对象;内存碎片化严重

3. 标记压缩算法

标记压缩算法是在标记清除算法的基础之上,将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。

缺点:多了一步移动内存的步骤,对效率一定影响。

4. 复制算法

将内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到另一个内存空间中,然后将该内存空间清空。

当垃圾对象较多时,需要复制的对象就会少,效率比较高,反之则不适合,会浪费大量内存空间。

5. 分代回收算法

根据回收对象的特点进行选择,jvm中:

年轻代使用了复制算法:

  1. GC开始前,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。
  2. 紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据它们的年龄值来决定去向。年龄达到一定值的对象会被移动到年老代中,没有达到阀值的对象会被复制到“To”区域。
  3. 经过这次GC后,Eden区和From区已被清空。这时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。
  4. GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。

老年代使用了标记整理算法:

  • 老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍。如果系统频繁出现老年代的Full GC垃圾回收,会导致系统性能被严重影响,出现频繁卡顿的情况。
  • JVM优化的核心,就是尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,造成系统卡顿。

jvm中,有哪些垃圾收集器?

1. 串行垃圾收集器:使用单线程进行垃圾回收,垃圾回收时所有线程都要暂停,等待垃圾回收的完成,这种现象称之为STW (Stop-The-World)

2. 并行垃圾收集器:将串行垃圾收集器的单线程改为了多线程,缩短垃圾回收的时间,但仍会暂停应用程序。

3. ParNew垃圾收集器/Parallel垃圾收集器:用在年轻代上的并行垃圾收集器

4. CMS垃圾收集器

CMS是一款并发的、使用标记-清除算法的垃圾回收器,使用在老年代。

GC过程:

  1. 初始标记:从根对象开始,扫描和根对象「直接关联」的对象,并作标记,这个过程虽然STW,但很快就完成了。
  2. 并发标记:紧随初始标记阶段,在初始标记的基础上继续向下追溯标记,这个过程与用户线程并行运行。
  3. 并发预清理:与用户线程同时运行
  4. 重新标记:并发阶段会出现新的垃圾,但不多,所以可以STW进行追加标记
  5. 并发清除:与用户线程同时运行

CMS缺点:

  1. 因为CMS算法有多个并行阶段,那么就需要堆空间预留更多的内存来为新对象分配内存,默认当老年代使用68%时,CMS就开始了,降低了堆空间的利用率。
  2. 只是标记清除,没有标记压缩,所以有内存碎片。

5. G1垃圾收集器

G1在jdk7正式引入,jdk9中变成默认垃圾收集器,替代CMS。

G1最大的特点是取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了逻辑上的年轻代、老年代。

年轻代的垃圾收集,依然采用STW的方式,将存活对象拷贝到老年代或Survivor空间,不会有cms的内存碎片问题。

在G1,有一个特殊的区域,叫Humongous区域。如果一个对象占用的空间超过了region容量50%以上,G1收集器就认为这是一个巨型对象。短期存在的巨型对象,就会对垃圾收集器的性能造成比较大的影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。

哪些对象可以作为GC时的根节点?

  1. 在虚拟机栈中引用的对象,如调用方法时使用的参数、局部变量、临时变量等
  2. 类的静态变量或常量
  3. synchronized同步锁的持有对象
  4. ......

Spring

Spring、SpringMVC、SpringBoot的关系?

Spring:Spring是一个轻量级的开源框架,核心作用:简化java开发,比如:

  • IOC:Inverse of Control,控制反转,原本程序手动创建对象的控制权,交由Spring框架来创建。当需要创建对象时,只需要配置好配置文件或注解即可。

  • AOP:Aspect-Oriented Programming,面向切面编程,能够将那些与业务逻辑无关的公共逻辑(日志管理、权限控制等)封装起来,减少重复代码,降低模块间的耦合度。

​SpringMVC:SpringMVC是属于SpringWeb里面的一个功能模块(SpringWebMVC),基于MVC模式,专门用于开发Web项目。

SpringBoot:SpringBoot包含了Spring的核心:IOC和AOP,并在Spring的基础上进行了功能扩展,用于简化Spring应用的开发,使开发、测试和部署更加方便,比如:

  1. 搭建项目快:几秒钟就可以搭建完成1个完整的项目

  2. 部署项目快:SpringBoot内嵌各种servlet容器,如:Tomcat、Jetty等,只要将项目打成一个可执行的jar包,就能在本地快速独立运行。

  3. 简化配置:根据你引入的jar包,对项目进行自动配置,免去了Spring繁琐的配置过程

  4. 测试方便:内置多种测试框架,如:JUnit、Spring Boot Test等,方便测试

所以,三者的关系是:spring mvc ∈ spring ∈ spring boot

mysql

索引为什么会让查询变快?

数据库中的数据需要存放在硬盘中,不可避免地需要进行磁盘IO操作。

磁盘IO操作需要寻道(找到数据所在的同心圆)、寻址(找到数据所在的同心圆的位置),是耗时操作,所以需要想办法减少磁盘IO操作。

索引Index是一种帮助MySQL高效获取数据的数据结构,常见的有B+树索引:

  1. B+树的非叶节点不存储data,可以存储更多的索引值,使得树更矮,减少磁盘IO操作。

  2. B+树的叶结点构成了一个有序链表,对于范围型的查找和搜索,可以减少磁盘IO操作。

MyISAM和innoDB的区别?

MyISAM和innoDB是MySQL常用的存储引擎

区别:

  1. InnoDB支持事务,MyISAM不支持,但也因此更快。

  2. 都使用了B+树的数据结构,但InnoDB的树叶子节点是数据文件,MyISAM的树叶子节点是数据文件的地址指针。

  3. InnoDB支持表锁、行级锁(默认),MyISAM只支持表级锁。但注意:InnoDB的行锁实现在索引上,不是锁物理行记录,即如果访问没有命中索引,无法使用行锁,退化为表锁。

如何选择:

  • 如果执行大量的读操作,MyISAM性能更好;如果读写都有,选择innodb(默认引擎),支持事务、行锁。

并发事务有哪些问题?

  1. 脏读:事务A发生修改,未提交到数据库,但事务B读取到了这个数据。

  2. 不可重复读:一个事务内多次读同一数据,结果不一致,因为另一个事务在修改。

  3. 幻读:一个事务内读取多行数据,行数不一致,因为另一个事务在做增删操作。

数据库的事务隔离?

  1. Read uncommitted(读未提交):一个事务可以读取另一个事务未提交的数据,会引发脏读。

  2. Read committed (读已提交):一个事务要等另一个事务提交后才能读取数据,会引发不可重复读

  3. Repeatable read (可重复读)【默认】:事务开始读取数据时,不再允许修改操作,会引发幻读。

  4. Serializable (序列化):事务串行执行,可避免脏读、不可重复读、幻读,但效率低,不推荐。

redis

redis为什么快?

redis是一种高性能的 K-V 数据库,官方称单机可支持10w/qps,那redis为什么能这么快?

  1. 直接基于内存进行数据的读写操作,减少磁盘读写操作

  2. 采用单线程,避免了不必要的线程上下文切换、加锁/解锁

  3. 使用多路I/O复用模型,提升/O事件的处理效率

  4. 自己构建VM系统,节省了调用系统函数的时间

redis为什么是单线程?

  1. redis基于内存进行数据操作,瓶颈是内存、带宽,不是cpu,单线程的执行效率够了

  2. 单线程会使得命令串行化,保证了命令的原子性,避免了加锁/解锁的操作

  3. redis处理请求时,只有1个线程,但redis内部还是存在多线程,比如持久化时会使用以子进程或子线程。

redis 持久化有几种方式?

方式1:RDB

RDB:Redis DataBase,是redis的一种持久化技术执行流程:

  • 执行bgsave命令,父进程 fork创建子进程,fork操作会使父进程阻塞,不过一般时间很短

  • 子进程根据父进程指向的内存地址生成快照文件

  • RDB可以在指定的时间间隔对数据进行快照存储,比如15分钟备份一次。

缺点:快照期间,如果父进程修改数据,子进程无法感知,存在数据不一致的可能

方式2:AOF

AOF:Append Only File,将redis执行的每一条写命令追加到磁盘文件appendonly.aof中,当 redis启动时,从AOF文件恢复数据。

一直保存命令,使AOF文件太大怎么办?为解决 AOF 文件膨胀问题,redis 提供了文件重写 (rewrite) 功能,当执行AOF文件重写操作时,会创建一个当前AOF文件的优化版本,包含了恢复当前数据集所需的最小命令集合,然后替换旧文件。

AOF的同步策略:

  • AOF FSYNC NO:不保存

  • AOF FSYNC EVERYSEC:每秒保存一次(默认)

  • AOF_FSYNC ALWAYS:每执行一个命令保存一次(不推荐)

缺点:

  • 频繁记录会对性能有一定的影响

  • AOF文件大于 RDB,修复速度也比 RDB 慢,所以redis的默认持久化配置是 RDB

方式3:混合持久化模式

如何开启?redis.conf配置文件中aof- use-rdb-preamble参数设置为yes,可以开启redis的混合持久化模式

混合持久化过程:

  • 如果没有AOF文件,则加载 RDB文件

  • 如果AOF文件开头为RDB格式,则加载 RDB 内容,再加载剩余 AOF 内容

  • 如果AOF文件开头不是RDB格式,则以AOF格式加载整个文件

混合持久化的优点:RDB文件较小,读取快,但实时性差;AOF实时记录写操作,数据丢失少,但文件大,读取慢。混合持久化结合了RDB和AOF的优点,文件开头为 RDB格式数据,使得Redis 可以更快启动,同时追加AOF格式数据,减低数据丢失风险。

混合持久化的缺点:AOF文件中添加了RDB格式内容可读性差

缓存常见问题及其解法?

使用缓存可以提升请求的处理性能,但凡事都有正反两面,也会带来很多问题。

问题1:缓存穿透/缓存击穿

如果访问数据库不存在的数据,会先缓存查一次,再数据库查一次,如果这类请求过多,会造成性能降低,甚至数据库崩溃。

解法:

  • 对不存在的key,在缓存中置value为null,快速返回,但TTL不能太长,防止key真有数据录入了数据库。

  • 布隆过滤器bloom filter:通过 bloom filter 判断 key 是否存在,如果不存在直接返回即可,无需查缓存。

  • 请求做参数校验,过滤无效请求。

问题2:缓存雪崩

某一时刻,多个key同时失效,压力全部到数据库,导致数据库出现异常。

解法:

  • key的过期时间增加随机值,不会同时失效

问题3:数据不一致

更新数据后,数据库和缓存中同1个key的value不一致。

解法:

  • 数据库更新成功后立即删除缓存

  • 缩短TTL,及时读取DB最新数据。

问题4:HotKey

同一时间大量请求访问特定key,打爆带宽,影响整体redis集群。

解法:

  • 凭借业务经验、实时监控,及时识别HotKey,下发到客户端缓存,减少请求量。

  • 采用redis集群部署,提升可用性。

问题5:BigKey

        BigKey指一个K的value很大,会导致占用过多内存空间、处理时间长导致阻塞后续请求,影响整体redis集群性能。

解法:

  • 拆分为小key,再逻辑重组。

消息队列

消息队列的作用?如何选型?

消息队列是分布式系统的重要中间件,消息队列的作用:

  1. 解耦系统/团队:比如你要做1个登录送优惠券的活动,那么就会涉及登录逻辑、优惠券逻辑、活动逻辑,1种方式是都在1个服务内实现,但逻辑比较复杂,而且不可复用;另1种方式是,涉及登录服务、优惠券服务、活动服务,各服务通过消息队列通信,比如:用户登录后,登录服务生产登录信息到消息队列,活动服务及时消费这个消息,然后告知优惠券系统发券。

  2. 削峰填谷提性能:对于高并发场景,可以先将请求先存储在消息队列,然后按照服务器可承受的处理速度去消费请求,避免服务器被高流量击垮。

  3. 实现广播功能

消息队列主要有RocketMQ、RabbitMQ、Kafka,如何选择?

  • Kafka:由Scala和Java编写,适合大数据领域的实时计算、日志采集等场景

  • RocketMQ:使用Java语言开发,适合可靠性要求很高的场景,比如订单、交易等金融场景。RocketMQ 是阿里出品,经历多次淘宝双十一的考验,稳定性值得信赖。

  • RabbitMQ:社区活跃,文档完善,适合数据量不大的中小公司。缺点是用 Erlang 语言编写,开发和维护成本大。

消息队列常见问题及其解法?

问题1:消息丢失

原因:

  1. 消息的生产者没有成功发送消息到MQ Broker

  2. 消息发送到MQ Broker后,Broker宕机

  3. 消费者消费消息时出现异常

解决方案:

  1. MQ Broker收到消息后回复ACK,没有收到ACK可以再次发送消息

  2. MQ收到消息后,进行消息持久化,出现故障可恢复

  3. 消费者在处理完消息后手动返回ack,MQ收到消费者ack后再删除持久化的消息。

问题2:消息重复

原因:

  1. 生成端:由于网络延迟,Broker没有收到消息,没有返回ACK,生产者会重新发送,最终Broker收到两条相同的消息。

  2. 消息端:消费者处理消息后,由于消费端异常或网络原因,ACK没有返回Broker,消息没有被删除,会再次被消费。

解法:消息的处理逻辑具备幂等性,比如根据消息内的数据,查询是否已存在消费记录。

问题3:消息积压

原因:发送端或消费端性能不足,导致消息发送积压、发送消费积压。

解法:

  1. 提升发送性能:并发发送、批量发送

  2. 提升消费性能:Comsumer扩容、并发处理

分布式

什么是CAP理论和BASE理论?

CAP是指分布式系统只能同时满足CAP中的两个特性:

C:Consistency,一致性;A:Availability,可用性;P:Partition tolerance,区容错性

  • CA:放弃P分区容错性,等同于放弃了分布式系统,不可取。

  • AP:放弃C数据一致性,适合数据准确性要求不高,强调用户体验的项目,如新闻资讯。典型:Eureka,优先保证服务可用,部分时间数据可能不一致。

  • CP:放弃A可用性,适合数据准确性要求很高的业务场景,如交易系统。典型:ZooKeeper,为保证数据一致性,部分时间服务可能不可用。

如何证明CAP理论?

P和C不可能同时存在当节点发生故障,肯定会出现数据不一致。

BASE理论是基于CAP理论演化而来:

  • BA(Basically Available):基本可用,允许损失部分可用性

  • S(Soft State):软状态,允许数据存在中间状态,用于不同节点间进行数据同步

  • E(Eventually Consistent):最终一致,数据副本经过一段时间的异步数据同步之后,数据最终能达到一致,不要求达到实时数据同步的强一致性。

ZK的工作原理?使用场景?

什么是ZK?

ZK:zoo keeper,动物园管理员,是分布式系统常用的一种中间件,很多知名框架都使用到了ZK,如:Dubbo、Kafka 等。

ZK的数据结构:

ZK本质是一个树形结构的文件系统。树中的节点被称为 znode,其中根节点为 /,每个节点上都会保存自己的数据和节点信息。但ZK的设计初衷是实现分布式系统下的服务协调,而不是文件存储,因此 znode 存储数据大小被限制在 1MB 以内。

ZK的特性:

  • 顺序性:为了保证事务的顺序一致性,ZK采用递增的事务 id 号(zxid)标识事务

  • 原子性:所有事务请求的处理结果在整个集群要么都成功,要么都失败。

  • 强一致性/单一视图:顺序性+原子性,可以保证客户端看到的服务端数据都是一致的。

  • 高性能:ZK数据存储在内存中,所以读的性能很高。但由于 ZooKeeper 的所有更新和删除都是事务型,所以写操作频繁时,性能会下滑。

  • 高可用:副本机制、故障恢复

ZK集群:

Zookeeper 集群是一个基于主从复制的高可用集群,有三种角色:

  1. Leader:所有写操作必须通过 Leader 完成,再由 Leader 将写操作广播给其它服务器,同时维护与各 Follwer 及 Observer 间的心跳。

  2. Follower:处理客户端读请求,同时会将写请求转发给 Leader 处理

  3. Observer:与 Follower 类似,但无投票权。

ZK读请求的工作原理

Leader/Follower/Observer 都可直接处理读请求,从本地内存中读取数据,返回给客户端。

ZK写请求的工作原理:

  1. 所有写请求由 Leader 处理(Follower/Observer 可接受写请求,但要转发给 Leader 处理)

  2. Leader 为每个 Follower 分配一个单独的队列,然后将事务 Proposal 依次放入队列中,并根据 FIFO(先进先出) 的策略,将写请求以事务形式发送给所有 Follower

  3. Follower 接收到 Proposal 后,以事务日志的形式写入本地磁盘中,并在写入成功后反馈给 Leader 一个 ACK响应。

  4. 当 Leader 接收到超过半数 Follower 的 Ack 响应后,就会广播一个 Commit 消息给所有的 Follower,通知其进行事务提交,之后 Leader 自身也会完成对事务的提交。

dubbo的架构和工作原理?

Dubbo是一个用于分布式系统服务调用框架,架构:

节点角色说明:

  • Provider:服务提供方

  • Container: 服务运行的容器

  • Consumer:服务消费方

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

  • Monitor:统计服务调用次数和时间的监控中心

调用时序:

  1. Container启动服务

  2. Provider向Registry注册服务

  3. Consumer向注册中心订阅自己需要的服务

  4. Registry返回Provider地址列表给Consumer,如果地址有变更,Registry将基于长连接推送给Consumer。

  5. Consumer从地址列表中,基于负载均衡策略,选一台调用,如果调用失败,再选另一台。

  6. Consumer和Provider,在内存中累计调用次数和调用时间,定时每分钟发送统计数据到监控中心。

网络通信

TCP三次握手和四次挥手的过程?

三次握手:

  • 第一次握手:Client将标志位SYN置1,将数据包发给Server请求建连,进入SYN_SENT状态

  • 第二次握手:Server发现标志位SYN=1,知道Client请求连接,回复SYN=1、ACK=1,表示可以建连,随后进入SYN_RCVD状态

  • 第三次握手:Client收到ACK为1后,发送 ACK=1 给Server,表示收到,随后进入ESTABLISHED状态。Server收到ACK为1,进入ESTABLISHED状态

四次挥手:

  • 第一次挥手:Clien发送FIN,表示想关闭数据通道,随即进入FIN WAIT 1状态。

  • 第二次挥手:Server收到FIN,发送ACK给Client,表示收到,进入CLOSE WAIT状态。

  • 第三次挥手:Server发送FIN,表示同意关闭随即LAST ACK状态。

  • 第四次挥手:Client收到FIN,回复ACK,表示收到,Server随即进入CLOSED状态;Client会先进入TIME WAIT状态,经过一段时间后,也进入CLOSED状态。

TCP是3次握手,但挥手为什么需要4次,而不是3次挥手?

3次握手和4次挥手的区别:当服务端收到客户端的建立连接请求时,会立即回复ACK(确认收到)+SYN(可以建连);当服务端收到客户端的关闭连接请求时,会先回复ACK(确认收到),再回复FIN(可以关闭)。

为什么分2次回复ACK、FIN?当服务端收到FIN时,可能还在进行数据处理工作,无法关闭连接,只能先回复ACK,表示收到关闭请求,等数据处理完,再回复 FIN确认关闭。

安全

加密算法有哪些?

① 摘要算法/hash算法

算法特点:

  • 根据不同的hash函数对信息进行摘要,生成一段固定长度的hash值。

  • 不可逆:不能通过这个hash值获得原始信息

常见的摘要算法:

  • MD5:产生一个128位的哈希值,md5(“cc”):c9c1ebed56b2efee7844b4158905d845

  • SHA:产生一个256位哈希值,sha1(“cc”): bdb480de655aa6ec75ca058c849c4faf3c0f75b1

撞库:黑客收集互联网已泄露的用户+密码信息,生成摘要字典表,可以反向得到消息。

撞库的解法:可多重摘要、加盐摘要,增加破解成本

② 对称加密算法

  • 使用密钥对信息加密,然后使用相同的密钥解密

  • AES是一种经典的对称加密/解密算法

​③ 非对称加密算法

  • 乙生成一对密钥(公钥和私钥),公钥给甲,私钥自己保留。

  • 甲传输数据给乙前,用乙的公钥加密

  • 乙拿到数据后,用本地私钥解密,私钥只有乙有。

  • 密钥是在双方根据SSL/TLS协议建立网络通信通道时,通过hello报文传输。

④ 混合加密算法

RSA加密算法虽然安全,但是计算量非常大,性能差;AES密钥在直接在网络电传输,存在被拦截的风险。可使用 RSA+AES 混合加密,兼顾效率和安全:

  1. 双方建立通道,传输RSA公钥

  2. 发送方使用接收方的RSA公钥,对本地的AES密钥加密

  3. 将加密后的AES秘钥,传输给接收方,接收方通过本地RSA私钥解密,得到AES密钥,原始AES密钥不通过网络传输,安全性高

  4. 发送方使用AES密钥对数据加密,效率高,然后发送数据

  5. 接收方使用AES密钥解密数据

加密和签名,有什么区别?

加密是为了防止信息被泄露,签名是为了防止信息被篡改。

通信过程:

  1. 【建连】建立通信通道,互换公钥

  2. 【加密】发送方使用接收方的RSA公钥加密信息得到data

  3. 【加签】发送方对要发送的信息,先进行消息摘要得到p,然后使用自己的RSA私钥加密p得到sign

  4. 【发送】发送data、p、sign

  5. 【解密】接收方使用自己的私钥解密信息data

  6. 【验签】接收方使用发送方的公钥解密sign,得到 p’,如果 p’== p,说明信息没有被篡改

「java工程师」常见面试题及其答案(持续更新)相关推荐

  1. 「高级java工程师」常见面试题及其答案(持续更新)

    「java工程师」常见面试题及其答案请见: 「java工程师」常见面试题及其答案(持续更新)_好人老李的博客-CSDN博客 目录 java基础 常用的 jvm 调优方法? OOM的常见场景及其原因.解 ...

  2. while循环中指针会自动释放吗_C++】C++常见面试题汇总_持续更新中...

    1:指针(*).引用(&).解引用(*).取地址(&).的概念和区别 概念: 指针指向一块内存,指针保存的是内存的地址:引用是变量的别名,本质是引用该变量的地址. 解引用是取指针指向的 ...

  3. Java多线程常见面试题及答案汇总1000道(春招+秋招+社招)

    Java多线程面试题以及答案整理[最新版]Java多线程高级面试题大全(2021版),发现网上很多Java多线程面试题都没有答案,所以花了很长时间搜集,本套Java多线程面试题大全,汇总了大量经典的J ...

  4. 总结Java常见面试题和答案

    转载自  总结Java常见面试题和答案 int和Integer有什么区别?   答:int是java的原始数据类型,Integer是java为int提供的封装类,java为每个原始数据类型都提供了封装 ...

  5. java常见面试题及答案 1-10(基础篇)

    java常见面试题及答案 1.什么是Java虚拟机?为什么Java被称作是"平台无关的编程语言"? Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程.Java 源文件被 ...

  6. java中级程序员面试题_中级Java程序员常见面试题汇总

    下面是一些中级Java程序员常见面试题汇总,你可以用它来好好准备面试. 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器 ...

  7. 【搞定 Java 并发面试】面试最常问的 Java 并发进阶常见面试题总结!

    本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习 面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.觉得内容不错 ...

  8. java线程池面试题有哪些?java线程池常见面试题

    进行java面试的过程中,java线程池是必问的面试题目,因为这是java的重点知识,也是在java工作中经常会遇到的,那java线程池面试题有哪些?下面来我们就来给大家讲解一下java线程池常见面试 ...

  9. java常见面试题及答案 1-10

    java常见面试题及答案 1.什么是Java虚拟机?为什么Java被称作是"平台无关的编程语言"? Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程.Java 源文件被 ...

最新文章

  1. 科普| 越来越火的图数据库究竟是什么?
  2. vscode 逗号不换行_在VSCode里面配置Vim正确姿势(细节解析)
  3. (1110, “Column ‘arriveTime‘ specified twice“)
  4. 2011计算机2级,全国计算机等级考试四合一过关训练:2级C语言程序设计(2011版)...
  5. 数据库中的DML,DCL,DDL分别是那些操作?
  6. python编写递归函数、求斐波那契数列第n项_python使用递归求斐波那契数列中第n个数的值...
  7. 暑期训练日志----2018.8.11
  8. 洛谷——P1014 [NOIP1999 普及组] Cantor 表
  9. main方法中args_public static void main(String [] args)– Java main方法
  10. 【洛谷3368】树状数组模版题(区间修改,单点查询)
  11. 计算机考研数据结构用哪本书,计算机考研数据结构该用哪本参考书?
  12. RGB色彩模式-最广的颜色系统之一
  13. 大数据查询与处理Pig培训:大数据查询处理技术解析
  14. Pytorch与Tensorflow权重互转
  15. python实践周总结_Python 一周总结
  16. 百度富文本编辑器上传文件到OSS
  17. 软件测试模型 — 测试金字塔
  18. 全球通史读书笔记上(第六章——古代文明的新起)
  19. 从零开始学黑苹果-基础安装教程(10.11.6)
  20. Angular Material 图标素材网址与使用

热门文章

  1. Redhat 5.4 Oracle 10g RAC Openfiler+Multipath + RAW+ ASM 安装文档
  2. python自动回复微信群的消息_程序员用python实现微信消息群发和微信自动回复
  3. IBM认知白皮书:通往智慧之路
  4. 汇编自动出栈_汇编学习-入栈和出栈
  5. 通过四六级英语考试的正确姿势
  6. 猪猪猫.CN-WIN2003SP2快速部署12[服务器版]
  7. i3 1005g1和i5 8265u哪个好
  8. htc sync for all htc android phones,HTC Sync 手机电脑同步软件
  9. 【a101】高精度实数加法
  10. 怎么样做一个合格的矿工