面试题总结:JAVA面试题(2024.02.29)

面试题汇总

http://t.csdnimg.cn/jtuir

– Bryan

Redis

Redis 使用场景

缓存:穿透,击穿,雪崩,双写一致,持久化,数据过期,淘汰策略

分布式锁:setnx/redisson

缓存穿透

不存在的数据,反复去数据库中查询

解决方案

  • 存空值
  • 布隆过滤器:通过算法计算hash,不一定能判断出存在,但是一定可以判断不存在

缓存击穿

设置过期时间,刚失效有大量数据同时请求

解决方案:

  • 互斥锁 强一致性
  • 逻辑过期,新开线程更新数据 高可用性

缓存雪崩

同一时段内,大量key同时失效

解决方案:

  • 给不同key的ttl加上随机值
  • 利用Redis集群提高服务的可用性
  • 给缓存业务限流降级
  • 给业务加上多级缓存

双写一致

保证redis和mysql的数据一致性

读操作:缓存命中,直接返回;缓存未命中,查询数据,写入缓存,设定超时时间

写操作:延迟双删 删除缓存-修改数据- 延时 sleep(100) -删除缓存

双写一致:异步通知保证数据一致

回答:redisson的读写锁方案

持久性

RDB 和AOF

  • RDB redis database file redis数据快照文件

    将内存中所有文件保存到磁盘中

自动配置 reids.conf

  • AOF append only file 追加文件

默认关闭

记录命令

数据过期策略

  • 惰性删除 不会删除,访问时如果过期就删除 对cpu友好对内存不友好
  • 定期删除

数据淘汰策略

默认 - 不淘汰任何key 但不允许在新写入

  • LRU (least recently used) 最近最少使用 当前时间减去最后一次访问时间 越大跃先淘汰
  • LFU(least frequently used) 最少频率使用

分布式锁使用场景

集群情况下的定时任务,抢单,幂等

setnx set if not exists

命令:set lock value NX EX 10 NX 互斥 EX 过期时间

释放:DEL key

  • watch dog 续期 releaseTime/3
  • 可重入锁(同一线程中)
  • 主从一致性

主从复制,主从同步

主从复制 给一个版本号,bgsave生成RDB文件,更新成功 版本号更新

offset 偏移量 版本号 replid数据集标记 master上的唯一值

增量同步 根据offset 同步offset之后的数据

哨兵模式

监控,自动故障恢复,通知

脑裂问题 : 重选master后 旧master恢复了 强制降级为slave 导致数据丢失

解决方案:设置从节点数量,增大同步延时时间

分片

面试题

  • redis 单线程快的原因

存内存操作;单线程避免上下文,锁和安全等;使用I/O多路复用模型,非阻塞I/O

  • 解释一下I/O多路复用模型

单个线程,同时监听多个socket

mysql

如何定位慢查询

运维工具Skywalking 报告展示哪个sql慢

sql语句慢 怎么分析

聚合查询 - 新增临时表

​ 多表查询 - 优化sql语句

​ 表数据量过大查询 - 添加索引

执行计划 sql 前加Expain/DESC 分析

  • key:用到的索引
  • key_len 索引所占空间 Extra- 额外优化建议
  • type-null/system/const/eq_ref/ref/range/index(索引树扫描)/all

​ 深度分页查询

聚簇索引

聚簇索引:数据与索引放到一块,B+树叶子节点保存了整行数据,有且只有一个

非聚簇索引:数据与索引分开存储,B+树的叶子节点保存主键,可以有多个

回表 通过二级索引找多对应主键值,到聚集索引中查找整行数据的动作

**覆盖索引 **查询使用了索引,并且返回的值能在改索引中全部找到

Mysql超大分页怎么处理 覆盖索引+子查询

索引创建原则

主键索引/唯一索引/联合索引

  • 针对数据量比较大,查询频繁的 10w+
  • 针对常查寻的字段 where ,order by ,group by
  • 选择区分度高的字段
  • 字符串类型 尽量使用前缀索引
  • 尽量使用联合索引,减少回表
  • 控制索引数量,索引越多,成本越高
  • 如果索引列不包含null ,在创建时约束字段 not null

索引失效

  • 违反最左前缀法则
  • 范围查询 右边的列,不能使用索引
  • 索引上运算操作
  • 字符串不加单引号
  • 以%在前的模糊查询

SQL优化经验

  • 表设计优化

根据《阿里开发手册》嵩山版;sql优化 避免 select *

  • 索引优化
  • sql语句优化
  • 主从复制,读写分离
  • 分库分表

事务的特性

事务相关

  • A(原子性)C(一致性)I(隔离性)D(持久性)

事务隔离级别

并发事务问题:脏读,不可重复读,幻读

隔离级别:读未提交,读已提交,可重复读(默认),串行化

undo log和redo log区别

缓冲池 主内存的一个区域 在执行增删改查先操作缓冲池 按一定频率同步磁盘 减少IO次数

数据页 inndb数据页的最小单元 16k

redo log 重做日志,记录事务提交时的物理修改 保证事务持久性

undo log 回滚日志,记录修改前的信息。主要作用 提供回滚和MVCC(多版本并发控制) 保证一致性和原子性

保证隔离性:锁 / MVCC

mvcc 多版本并发控制 保证读写没有冲突

mysql主从同步原理

二进制日志,主库将DDL和DML写到 binlog中 - 从库读取-写入从库Relay log-从库重做中继日志中的事件

分表分库

垂直分库 微服务 高并发下 减少IO 提高效率

框架篇

Spring

Spring框架中单例bean时线程安全的吗?

不是

什么时AOP,在项目中有没有用过AOP?

面向切面编程,找到切点,在切点操作,减少重复代码降低耦合度

  • 记录操作日志

  • 缓存处理

  • spring 事务处理

    在方法之前拦截 开启事务 结束后提交事务 如果报错 回滚事务

    适用于大批量插入操作

Spring中事务失效的场景有哪些?

  • 异常捕获处理
image-20240221175926505
  • 抛出检查异常
image-20240221180132733
  • 非public方法
image-20240221180341030

spring bean的生命周期

BeanDefinition 对象-构造函数-依赖注入-Aware接口-BeanPostProcessor#Before-初始化方法-BeanPostProcessor#after-销毁

Spring的循环引用问题

三级缓存解决循环依赖问题

  • 一级缓存 单例池 singleton 对象

  • 二级缓存 缓存早期的bean对象 半成品对象 一二级解决一般对象的循环依赖

  • 三级缓存 缓存ObjectFactory,表示对象工厂,用来创建某个对象的 singletonFactories对象 一二三级解决代理对象循环依赖问题

question 2

A依赖B,B依赖A,用的是构造方法注入

A:bean周期中构造方法时第一个执行,spring框架不能解决依赖注入

使用@Lazy进行懒加载 什么时候需要对象再进行创建

image-20240221183329808

SpringMVC的执行流程

image-20240221183726642 image-20240221183917531

Springboot的自动配置原理

@SpringbootAplication

  • SpringbootConfiguartion
  • EnableAutoConfiguration
  • ComponentScan

其中@EnableAutoConfiguartion是实现自动化配置的核心注解。该注解通过@Import注解导入对应的配置选择器

Spring框架中常见的注解

image-20240221185215070 image-20240221185231416 image-20240221185401934

微服务

Eureka 注册中心

Eureka和Nacos的区别
模块NacosEureka说明
注册中心服务治理基本功能,负责服务中心化注册
配置中心Eureka需要配合Config实现配置中心,且不提供管理界面
动态刷新Eureka需要配合MQ实现配置动态刷新,Nacos采用Netty保持TCP长连接实时推送
可用区AZ对服务集群划分不同区域,实现区域隔离,并提供容灾自动切换
分组Nacos可用根据业务和环境进行分组管理
元数据提供服务标签数据,例如环境或服务标识
权重Nacos默认提供权重设置功能,调整承载流量压力
健康检查Nacos支持由客户端或服务端发起的健康检查,Eureka是由客户端发起心跳
负载均衡均提供负责均衡策略,Eureka采用Ribion
管理界面Nacos支持对服务在线管理,Eureka只是预览服务状态
基本参数

server.port=7001
spring.application.name=eureka-server
eureka.client.serviceUrl.defaultZone=http://localhost:7002/eureka/
eureka.client.registerWithEureka=true
eureka.client.fetchRegistry=true
#禁用 Eureka 的 ReadOnlyMap 缓存 (Eureka 端)
eureka.server.use-read-only-response-cache=false
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.prefer-file-system-access=false
#客户端信息上报到服务的周期
eureka.client.instance-info-replication-interval-seconds=5
#客户端拉取服务器配置信息的周期,默认30s
eureka.client.registry-fetch-interval-seconds=5
#启用主动失效,并且每次主动失效检测间隔为 3s (Eureka 端)
eureka.server.eviction-interval-timer-in-ms=3000
#服务过期时间 (服务提供方)
eureka.instance.lease-expiration-duration-in-seconds=15
#服务刷新时间配置,每隔这个时间会主动心跳一次 (服务提供方)
eureka.instance.lease-renewal-interval-in-seconds=5
#ribbon 刷新时间 (客户端)
ribbon.ServerListRefreshInterval=5000

feign+ribbon+hystirx交互概览

Feign 接口调用

  1. 作为HTTP的客户端替代RestTemplate,支持注解的方式
  2. Feign组件中引入了Ribbon和Hystrix组件,这使得Feign也能够为我们提供负载均衡、熔断、降级的功能;

在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。

ribbon 负载均衡

  • 服务雪崩 一个服务失败 导致整个服务失败
  • 服务降级 一种保护机制 根据f
  • 服务熔断 服务不可用 每隔5s进行尝试请求

Hystrix熔断

在微服务系统中,Hystrix 能够帮助我们实现以下目标:

  • 保护线程资源:防止单个服务的故障耗尽系统中的所有线程资源。
  • 快速失败机制:当某个服务发生了故障,不让服务调用方一直等待,而是直接返回请求失败。
  • 提供降级(FallBack)方案:在请求失败后,提供一个设计好的降级方案,通常是一个兜底方法,当请求失败后即调用该方法。
  • 防止故障扩散:使用熔断机制,防止故障扩散到其他服务。
  • 监控功能:提供熔断器故障监控组件 Hystrix Dashboard,随时监控熔断器的状态。

SkyWalking 服务监控 追踪链路

监控服务请求时间 找到耗时最多的服务

微服务限流

原因 并发量大 / 防止恶意请求

  • 漏桶算法
  • 令牌算法

CAP理论和BASE理论

Consistency 一致性

Availability 可用性

Partition 分区容错性

base 对CAP的解决思路

  • basically available 基本可行性
  • soft state 软状态
  • Eventually consistency 最终一致性

分布式事务解决方案

Seata架构

tc – 事务协调器 维护全局和分支事务的状态 协调全局事务的提交和回滚

tm – 事务管理器 全局事务范围 开始全局事务的执行 提交 回滚

rm – 资源管理器 每个微服务

  • XA

    CP模型 需要等待各个事务分别提交 银行业务

  • AT

    AP模型 底层使用undolog实现 性能高 互联网

  • TCC

    AP 需要人工编码实现

MQ 架构

​ 在A执行写事务时 在同一事物中,发消息给B服务,异步性能好

分布式幂等问题

  • 数据库唯一索引

  • token+redis

    第一次请求:获取token - 存入redis (k:user v:token) - 返回token

    第二次请求:带着token请求服务端,查询是否存在token,存在执行业务流程,不存在则直接返回 - 返回请求结果

  • 分布式锁

快速失败 控制锁的粒度

分布式任务调度 xxl-job

  1. 解决集群任务执行问题
  2. cron定义灵活
  3. 定时任务失败了,重试和统计
  4. 任务量大,分片执行
  • xxl-job 任务有哪些

    轮询,故障转移分片广播

  • 失败了怎么办

    故障转移+失败重试+日志分析 --》邮件告警

  • 大数量执行

消息中间件

RabbitMQ

保证消息不丢失

发送MQ丢失

消息成功后,有回值 ack

数据丢失处理方案

  1. 回调方法即时重发
  2. 记录日志
  3. 保存数据库 定时重发 ,成功后删除数据
MQ内丢失

交换机和队列做持久化

消费者数据丢失

当消费者处理成功后,返回ack回值

设置重试次数,加入死信队列 或人工处理

MQ重复消费问题

唯一id

分布式锁

死信交换机

延时队列=死信交换机+TTL

死信交换机:消费者拒绝或处理消息失败 消息过期 投递消息堆积满了,最早的消息成为死信

TTL: time to live 消息存活时间 队列设置了存活时间 消息本身设置了存货时间

消息堆积

  • 增加消费者
  • 在消费者开启线程池,处理更多的消息
  • 扩大队列容量,提高堆积上限

惰性队列:将消息存在磁盘中,只有在加载时才会去读取,支持百万存储

高可用

普通集群 镜像集群 仲裁集群

kafka

消息丢失

异步发送,重试

brocker中,存储丢失,设置acks模式为all

消费者,关闭自动提交偏移量,手动提交,同步加异步(finally)

重复消费

手动提交,幂等性

前端人脸识别+滑块功能

如何保证消息的顺序性

指定分区 分区内时有序的

保证kafka的高可用

消息分区 不受单台服务器限制,可以不受限制的处理更多数据

顺序读写 磁盘顺序读写日高读写效率

页缓存 读写磁盘数据中,将数据存到页缓存中

零拷贝 减少上下文切换及数据拷贝

消息压缩 减少此怕IO 网络IO

分批发送 将消息打包批量发送 减少网络开销

集合

时间复杂度

算法的时间和数据规模之间的增长关系

表示量级 O(n) O(n^2)

速记:常对幂指阶

空间复杂度: 算法暂用的额外存储空间和数据规模之间的增长关系

List

数组

是一种连续的内存空间存储相同数据类型数据的线性数据结构

寻址公式:a[i]=baseAddress+i*dataTypeSize

  • 为什么所引从0开始?

​ 根据寻址公式,对于CPU来说 会多一次 i-1 的操作 有性能损耗

  • 操作数据的时间复杂度

    1. 查询

      已知索引:O(1)

      未知索引:O(n) 若排序,使用二分查找法 O(log n )

    2. 插入/删除

      O(n)

ArrayList源码分析

ArrayList的底层实现原理时什么?
  • ArrayList的底层是用动态数组实现的
  • 初始容量为0
  • 1.5倍扩容
  • 添加数组时
    1. 确保已使用数组长度+1后足够下一个数据
    2. 计算容量,如果不符合1,则1.5倍扩容
    3. 扩容后,将添加的数组加到size的位置上
    4. 返回成功的布尔值
new ArrayList(10) 扩容了几次

声明和实例了一个Arraylist 制定了容量为10 没有扩容

如何实现数组和List之间的转换
String[] str={"a","b"};
List<String> list = Arrays.asList(str);  Arraylist指向了数据的引用(同一个地址),如果数组修改,ArrayList也会受到影响
String[] array = list.toArray(new String[list.size()]);  数组拷贝

ArrayList和LinkedList的区别

单向链表
  • 时间复杂度

​ 查询 头节点 O(1)

​ 查询其他节点O(n)

​ 插入删除 头 O(1) 其他O(n)

双向链表

前驱节点+data+后继节点

支持双向遍历

  • 时间复杂度

    查询:头尾 O(1) 其他O(n)

    插入删除 头尾 O(1) 其他O(n)

区别
  1. 底层数据结构

    ArrayList 动态数据

    LinkedList 双向链表

  2. 操作效率

    查询:ArrayList 查询 内存连续,根据寻址公式 LinkedList不支持下标

  3. 空间占用

    Arraylist 内存连续,相对节省空间

    LinkedList 双向链表,需要保存两个指针,相对占内存

  4. 线程安全

    均不安全

    线程安全方案工具类

    • Collections.synchronizedList(new ArrayList())

HashMap

二叉树

每个节点有两个’叉‘ ,分别为左子节点和右子节点。

  • 二叉搜索树

​ 左节点小于父节点,右节点大于父节点 没有相等的值 时间复杂度 O(log n)

红黑树

自平衡二叉树 BST 时间复杂度 O(log n)

性质:

  1. 节点要么时红色,要么是黑色
  2. 根节点是黑色
  3. 叶子节点是黑色
  4. 红色节点的子节点必须是黑色
  5. 从任意节点到叶子节点的所有路径都包含相同数目的黑色节点

目的是为了一旦不符合这些规则就会发生旋转,保证数据的平衡

散列表

hash表,根据k直接访问内存位置V的数据结构,由数组演化来。

hash冲突:散列冲突-链表法

DDos攻击

分布式拒绝服务攻击 效率问题

hashmap实现原理

底层用hash表数据结果+链表+红黑树

添加时,hash(key)相同,key也相同,则替换

不同存入红黑树或者链表(链表长度大于8,数组长度大于64,则链表->红黑树)

获取数据时,通过key获取元素

put方法具体流程

image-20240303112457415
  1. put方法流程
  2. 扩容
  3. get方法

HashMap的寻址算法

扰动算法,使hash值更加均匀,减少hash冲突

  1. 计算hash值
  2. 二次hash,hashcode右移16位异或运算使得hash更加均匀
  3. 最后(capacity-1)&hash 得到索引

(n-1)&hash:得到数组中的索引,代替取模,性能更好数组长度必须是2的n次幂

2的n次幂

  1. 效率更高
  2. 扩容时,重新计算效率更高

hashmap的扩容原理

扩容条件:

  • 当前存储的数量大于等于阈值
  • 当某个链表的长度>=8,但数组的节点数size()<64时

特点:先插后判断是否需要扩容(扩容时是尾插法)

扩容之后对table的调整:

table容量变为2倍,但是不需要像之前一样计算下标,只需要将hash值和旧数组长度相与即可确定位置。

  • 对于没有hash冲突的节点,通过e.hash&(newCap-1) 计算新索引为位置

  • 对于红黑树走红黑树的添加

  • 对于链表 遍历链表,可能需要拆分链表,判断e.hash&oldCap是否为0.为零则在原始位置,否则移动到原始位置+增加数组大小的位置上

hashmap不安全的原因

  1. 非同步操作

    多线程情况下导致读写不一致

  2. 非原子性操作

    put()方法包含计算hash,查找和插入,初始化,生成链表,红黑树

  3. 扩容情况

    是将就数组复制到新数组的过程

hashMap在1.7下多线程死循环问题

数组加链表

在扩容过程中,链表使用头插法,在进行迁移过程中,可能导致死循环

image-20240303115827511

解决方法 头插法->尾插法

多线程

基础篇

线程和进程的区别

程序由指令和数据组成,但是这些指定要运行,数据要读写,就必须经指令加载到CPU ,数据加载至内存。在指令运行中还需要用到磁盘,网络等设备。进程就是用来加载指令,管理内存,管理IO等。

一个此案成就是一个指令流,经指令流中的一条条指令以一定的顺序交给CPU执行

一个进程有多个线程

二者对比
  • 进程是正在运行的程序实例,进程包含了线程,每个线程执行不同的任务
  • 不停的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间
  • 线程更轻量,线程上下文切换成本一般要比进程上下文切换第

并行和并发的区别

单核CPU

单核CPU下线程实际上还是串行执行的

操作系统有一个组件叫任务调度器

微观串行,宏观并行

多核CPU

每个核core都可以调度运行线程,这时候线程可以是并行的

并发:同一时间应对多件事情的能力

并行:同一时间动手做多件事情的能力

创建线程的方式

继承Thread类

实现runnable接口

实现Callable接口

线程池创建线程

runnbale和callable的区别
  1. Runnable接口run方法没有返回值
  2. Callable接口call方法有返回值,是个泛型,和Future,FutureTask配合可以用来获取异步执行的结果
  3. Callable接口的call()方法允许抛出异常;二runnable接口的run()方法异常内部消化
run和start的区别

run方法可以多次执行

start方法只能执行一次

线程包括哪些状态,状态之间是如何变化的

NEW,RUNNABLE,BLOCKED,WAITING_TIME_WAITING,TERMINATED

线程状态之间是是如何变化的
  • 创建线程 new
  • 调用start() runnable
  • 线程获取到了CPU的执行权 terminated
  • synchronized/lock blocked
  • wait() waiting
  • sleep(50) timed_waiting

新建T1,T2,T3三个线程,如何保证他们按顺序执行

join方法:等待线程运行结束

t2代码块中 t1.join() 方法

t3代码块中t2.join()方法

notify()和notifyAll()

notifyAll()唤醒所有wait线程

notify()随机唤醒一个线程

wait和sleep

共同点

让当前线程防区CPU的是使用权,进入阻塞状态

不同点

  • 方法归属不同

sleep–Thread

wait–Object

  • 醒来时机不同

sleep(long)和wait(long)等待后醒来

wait可以被notify唤醒

  • 锁特性不同
    • wait方法调用必须先获取wait的对象锁,sleep无限制
    • wait执行后会释放锁对象,允许其他线程获取对象锁
    • sleep如果在synchronized代码块中执行,并不会释放对象所

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

  1. 使用推出标志,正常退出
  2. 使用stop方法
  3. 使用interrupt方法终端线程
    • 阻塞状态会抛出异常interruptedException
    • 正常线程 可以根据打断状态来标记是否退出

安全篇

synchronized底层

Monitor 监视器 由jvm提供 c++实现

  • waitSet 关联调用了wait方法的线程,处于waiting状态

  • entryList 关联没有获取得到锁的线程,处于blocked状态

  • Owner 存储当前获取锁的线程,只有一个线程可以获取

回答

Sync对象锁采用互斥的方式让同一时刻至多一个线程能有持有锁对象

底层是由monitor实现的,monitor是jvm级别的对象,由c++实现

monitor内部由三个属性,owner,entryList,waitSet

owner同一时间只能关联一个线程

进阶

内部的偏向锁,轻量级锁

描述
重量级锁底层Monitor实现,涉及到用户态和内核态切换,上下文切换,成本高,性能低
轻量级锁线程加锁时间是错开的,没有竞争;可以使用轻量级锁。轻量级锁修改了对象头的锁标志,相对重量级锁性能提升了很多。每次修改都是CAS操作,保证原子性
偏向锁一段很长时间都被一个线程使用锁。第一次获取锁,会有一段CAS操作,之后线程再获取锁,只需要判断markdown中是否是自己的id即可

JMM(java内存模型)

  • JMM模型定义了共享内存中多线程程序的读写规范的行为规范,通过这些规范保证了内存读写指令的正确性
  • JMM把内存分为两块,一块是私有线程的工作区域,一块是所有内存的共享区域
  • 线程和线程之间是互相隔离,线程和线程交互需要通过主内存

CAS

Compare And Swap 乐观锁思想,无锁的情况下,保证线程操作共享数据的原子性

  • AbstractQueueSynchronizer AQS框架
  • AtmicXXX类

共享变量时使用自旋锁

CAS底层调用unsafe类中的方法,由系统提供

乐观锁和悲观锁

CAS是基于乐观锁概念,比较和替换

synchronized是悲观锁概念

volatile

共享变量

  • 保证线程间的可见性
  • 修饰的共享变量会在读写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到阻止重排序的效果

AQS

  • 抽象队列同步器:构建锁或者其他同步组件的基础架构。像reentrantLock
  • AQS内部维了一个先进先出的双向队列,队列中存储的排队的线程
  • 再AQS内部还有一个属性state,这个state就相当是一个资源,默认是0,如果队列中的有个线程修改成功为1,则当前线程获取了资源
  • state修改的时候使用cas操作,保证多个相乘修改的情况下原子性
syncAQS
关键字,c++java实现
悲观锁,自动释放锁悲观锁,手动开启和关闭
锁竞争激烈都是重量级锁,性能差竞争激烈情况,提供多种方案

ReentrantLock

可重入锁

  • 可中断
  • 可以设置超时时间
  • 可以设置公平锁
  • 支持多个条件变量
  • 与sync一样,支持重入
原理

CAS+AQS队列实现

支持公平锁和非公平锁,无参为非公平锁,传true设置为公平锁

sync和lock的区别

  • 语法层面

    sync是关键字,再jvm上由c++实现

    lock是接口,由jdk提供,java实现

    sync退出代码块自动实现,lock需要调用unlock解锁

  • 功能层面

    二者均为悲观锁,具备互斥,同步,可重入等功能

    Lock存在sync不具备的功能,如公平锁,可打断,可超时,多条件变量

    Lock有不同场景的实现ReentrantLock,ReentrantReadWriteLock

  • 性能层面

    在没有竞争时,sync做了很多优化,入偏向锁,轻量级锁

    在竞争激烈时,Lock实现会提供更好的性能

死锁产生的条件

一个线程同时获取多把锁,造成死锁

如何诊断死锁
  • jps:进程状态
  • jstack:进程堆栈信息
image-20240304103109798
  • 可视化工具,visualVM,Jconsole

concurrentHashMap

高效Map集合

CAS 添加数据+sync 锁住首节点

导致并发程序出现问题的根本原因

java并发编程三大特性:

  • 原子性

  • 可见性

  • 有序性

线程池

核心参数

  • corePoolSize 核心线程数

  • maximumPoolSize 最大线程数

  • keepAliveTime 生存时间

  • unit 时间单位 - 救急线程生存时间

  • workQueue 当没有空闲核心线程时,新来任务会加入此队列排队,队列满了会创建救济线程执行任务

  • threadFactory 线程工厂

  • hander 拒绝策略

执行原理
image-20240304161949262
拒绝策略
  1. AbortPolicy 抛异常
  2. CallerRunsPolicy 用调用者的所有线程执行
  3. DiscardOldestPolicy 丢弃阻塞队列种最靠前的任务,并执行当前队列
  4. DiscardPolicy 直接丢弃任务

线程中常见阻塞队列

ArrayBlockingQueue 有界阻塞队列

LinkedBlockingQueue 单向链表

DelayedWorkingQueue

SynchronousQueue

ArrayLinked
强制有界默认无界,支持有界
底层链表底层数组
惰性,创建节点添加数据初始化Node数组
两把锁 - 头尾一把锁

如何确定核心线程数

  • IO密集型任务 核心线程数:2N+1

​ 文件读写 DB读写 网络请求

  • CPU密集任务 核心线程数:N+1

    计算型代码,Bitmap转换,Gson转换

    (N为CPU数量)

线程池的种类

  1. 创建固定线程数的线程池

    newFixedThreadPoolExector

  2. 单线程话的线程池

    newSingleThreadPoolExector

  3. 可缓存线程池

    newCacheThreadPoolExcutor

  4. 延迟和周期执行功能线程池

    ScheduledThreadPoolExcutor

为什么不建议用Executors创建

允许请求的长度为integer.MAX_Value ,可能导致消息堆积,导致OOM 内存溢出

使用场景

线程池的使用场景,在你的项目中哪里用到了线程池

场景1:数据导入

使用了线程池+CountDownLatch批量把数据库中的数据导入到了(ES)中,避免OOM

闭锁/倒计时锁

用来进行线程同步协作,等待所有线程完成倒计时

  • 参与构造参数用来初始化等待计数值
  • await()用来等待计数归零
  • countDown()用来让计数减一
image-20240305105707835
场景2:数据汇总

调用多个接口来汇总数据,如果所有接口都没有依赖关系,我们可以使用线程池+future来提升性能

image-20240305105739839
  • 在实际开发过程中,难免需要调用多个接口来汇总数据,如果所有接口都没有依赖关系,我们就可以使用线程池+future来提升性能
  • 报表汇总
场景3:异步调用

为了避免下一级方法影响上一级方法(性能考虑),可以使用异步线程调用下一个方法,可以提升方法响应时间

如何控制某个方法允许并发访问的数量

Semaphore 信号量 JUC包下的工具类 底层AQS 通常限制执行线程的数量

通常用于资源有限的场景,常用于限流

使用

  1. 创建Semaphore对象,可以给一个容量
  2. acquire()可以请求一个信号量,这个时候的信号量个数-1
  3. release()释放一个信号量,此时信号量个数+1
image-20240305113334026

ThreadLocal

多线程中对于解决线程安全的一个操作类,他会为每一个线程都分配一个独立的线程的副本从而解决了线程冲突的问题。threadlocal同时实现了线程内资源共享

基本使用

  • set(value) 设置值
  • get() 获取值
  • remove()清除值

底层实现

本质上是一个此案成内部存储类,从而让多个线程操作自己的内部值,从而实现线程数据隔离

ThreadLocal-内存泄漏问题

java对象中的四种引用类型:强引用,软引用,弱引用,虚引用

弱引用:GC会被干掉

k是弱引用 v是强引用 导致内存泄漏

解决方面 使用remove方法

JVM

java二进制字节码运行环境

JVM组成

程序计数器

线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码的地址

javap -v xx.class 打印堆栈大小

java堆

  • 线程共享区域:主要保存对象实例,数组等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常

  • 组成:年轻代和*+*老年代

    • 年轻代:Eden区和两个大小严格相同的Survivor区
    • 老年代:保存生命周期长的对象
  • jdk1.7和1.8的区别

    1.7中有永久代

    1.8去除了永久代,放到了原空间中,防止内存溢出

虚拟机栈

  • 每个线程运行时所需要的内存,称为虚拟机栈
  • 每个栈有多个栈帧组成,对应着每次方法调用时所占用的内存
  • 每个线程只能由一个活动栈帧,对应着当前正在执行的哪个方法

垃圾回不涉栈内存

栈内存越大越好吗?

未必,默认是1024k。栈过大会使得线程数变少。

方法内的局部变量是否是线程安全的?

如果方法内的局部变量没有逃离范围,是线程安全的

栈内存溢出情况
  • 栈帧过道导致内存溢出,如 递归
  • 栈帧过大导致栈内存溢出
堆和栈
  • 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来春初java对象和数组的。堆会GC垃圾回收
  • 栈内存是私有的,堆内存是线程共有的
  • 空间不足时 栈:StackOverFlowError 堆:OutOfMemmoryError

方法区

各个线程共享的内存区域

主要存储类的信息/运行时常量池

虚拟机启动时创建,关闭时释放

空间不足:OutOfMemmoryError:Metaspace

常量池

可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息

javap -v xx.class

当类被加载,他的常量池就会放入到运行时常量池,并把符号地址变为真实地址

直接内存

并不属于JVM中的内存结构,不由JVM进行管理。是续集的系统内存,常见于NIO操作时,用于数据缓冲区,它分配回收成本较高,但读写性能高

类加载器

JVM只会运行二进制文件,类加载器的作用就是将字节码文件加载到JVM中,从而让Java程序能够启动起来

类加载器有哪些
  • 启动类加载器
  • 扩展类加载器
  • 应用类加载器
  • 自定义类加载器
双亲委派模型

加载某一个类,现委托上一级的加载器进行加载,如果上级加载器也有上级,则会继续向上委托,如果该类委托上级没有被加载,子加载器尝试加载该类。

为什么使用双亲委派机制
  1. 通过双亲委派机制可以避免某一个类被重复加载,当弗雷已经加载后则无需重复加载,保证唯一性。
  2. 为了安全,保证类库API不会被篡改
类装载的执行过程
  • 加载

    • 通过类的全名,获取类的二进制数据流
    • 解析类的二进制数据流为方法区内的数据结构
    • 创建java.lang.Class类的实例,表示该类型。作为方法去这个类的各种数据的访问接口
  • 验证

    验证类是否符合JVM规范,安全性检查

    • 文件格式验证
    • 元数据验证
    • 字节码验证
    • 符号引用验证
  • 准备

    为类变量分配内存并设置类变量初始值

    • static变量,分配空间再准备阶段完成,设置默认值,复制再初始化阶段完成
    • static变量是final的基本类型,以字符串常量,值以确定,复制在准备阶段完成
    • static变量是final的引用类型,复制在初始化阶段完成
  • 解析

    把类中的符号引用变为直接引用

  • 初始化

    对类的静态变量,静态代码块执行初始化操作

    • 如果初始化,父类未初始化,先初始化父类
    • 多个静态代码块,顺序加载
  • 使用

    • 调用静态类成员信息
    • 使用new关键字为其创建成员信息
  • 卸载

    JVM销毁

垃圾回收

对象什么时候可以被垃圾回收

  1. 引用计数法

    一个对象引用一次,在当前对象头上递增一次引用次数,如果这个对象的引用次数为0,这表示可以被回收

循环引用,导致OOM

  1. 可达性分析算法

    可以作为GC Root对象

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

JVM垃圾回收算法有哪些

  • 标记清除算法
  • 标记整理算法
  • 复制算法

JVM分代回收

分代手机算法

新生代:老年代=1:2

Eden:from:to=8:1:1

Eden几次回收仍然存活,晋升到老年代

MinorGC,MixedGC,FullGC

MInior GC 新生代垃圾回收 暂时时间短

Mixzed GC 新生代+老年代部分区域 垃圾回收 ,G1收集器特有

Full GC 新生代+老年代完整垃圾回收,暂停时间长,尽量避免

JVM垃圾回收器

  • 串行垃圾收集器 Serial GC ,Serial Old GC
  • 并行垃圾收集器 Parallel Old GC,ParNew GC
  • CMS(并发)垃圾收集器 CMS GC,作用在老年代
  • G1垃圾收集器 新生代/老年代
G1
  • 应用于新生代和老年代 JDK9之后默认
  • 划分很多区域,每个区域都可以充当eden,surviver,old,humongous
  • 采用复制算法
  • 相应时间和吞吐量兼顾
  • 分为三个阶段:新生代回收,并发标记,缓和收集
  • 如果并发失败描绘出发Full GC

强引用,软引用,弱引用,虚引用

内存不足,软引用被垃圾回收

垃圾回收时,弱引用被回收

虚引用需要配合引用队列释放资源

JVM实践

JVM调优

  • war

    修改TOMCAT_HOME/bin/Catalina.sh文件

  • jar

    java -Xms512m -Xmxm -jar xxx.jar

JVM调优参数

  • 设置对空间大小

    -Xms 设置堆初始大小 最大物理内存 1/16

    -Xmx 设置堆最大大小 最大物理内存 1/4

  • 虚拟机栈

    -Xss 对每个线程stack大小的调整 -Xss128k

  • Eden Survivor 设置比例

    -XXSurvorRatio=8 survivorLeden=2:8

  • JVM 设置垃圾回收器

    -XX:+Use Parallel GC

    -XX:+UserParallelOldGC

JVM调优工具

  • 命令工具

    jps 进程状态信息

    jstack 查看java进程中线程堆栈信息

    jmap 生成堆转内存快照 内存使用情况 jmap=heap pid 显示java堆信息

    jstat 统计监测工具,显示垃圾回收信息,类加载信息,新生代统计信息

  • 调优工具

    jconsole 查看内存,线程,类等

    VisuaVM

    内存泄漏排查思路

    1. 通过jmap或者设置jvm蚕食获取堆内存快照dump
    2. 通过工具,VisualVM区分析dump文件,VisualVM可以加载离线的dump文件
    3. 通过查看堆信息的情况,可以大概定位内存溢出的那行代码出的问题
    4. 找到对应的代码,通过阅读上下文的情况,进行修复即可

    CPU飙高排查方案和思路

    1. 使用top命令查看占用的情况
    2. 通过top命令查看后,可以查看时哪一个进程占用cpu较高
    3. 使用ps命令查看进程中的线程信息
    4. 使用jstack命令查看进程中哪些线程出现了问题,最终定位

企业场景

设计模式

工厂方法模式

核心目的:解耦

简单工厂模式

所有产品共用一个工厂,新增产品要修改代码

工厂方法模式

开闭原则:对扩展开放,对修改关闭

为每个产品提供一个工厂,让工厂专门负责对应的餐品的生产

抽象工模式

产品的多个维度要配合生产,工厂的工厂

策略模式

  • 该模式定义了一系列的算法,并将每个算法封装起来,使他们可以互相替换,且算法的变化不会影响使用算法的客户
  • 它通过对算法进行封装,把使用算法的责任和短发的实现分割开来,并委派给不同的对象对这些算法进行管理
角色
  • 抽象策略Strategy

    抽象角色,通常由接口/抽象类实现

  • 具体策略Concrete Stratrgy 实现接口,提供算法实现和具体行为

  • 环境Context 持有策略的引用,最终给客户端调用

优劣

优势

  • 策略类之间可以自由切换
  • 易于扩展
  • 避免使用多重条件选择语句,充分体现面向对象的设计思想

缺点

  • 客户端必须知道所有的策略类,并决定选用哪个
  • 测类模式将产生很多策略

登录案例(工厂模式+策略模式)

image-20240306094435999

只要代码中由冗长的if-else或switch分支判断都可以采用策略模式

责任链设计模式

为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的矗立着通过迁移对象记住其下一个对象的引用二连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

角色
  • 抽象处理者
  • 具体处理者
  • 客户类
优劣

优点

  • 降低对象之间的耦合度
  • 增强系统的可扩展性
  • 增强了给对象指派责任的灵活性
  • 责任链简化了对象之间的链接
  • 责任分担

缺点

  • 对于较长的责任链设计多个对象处理,影响性能
  • 简历合理性要求由客户端来保证,增加客户端的复杂性
场景
  1. 内容审核

    视频,文章,课程

  2. 订单创建

    校验-填充-算价-落库

  3. 简历流程审批

    组长=主管=副总裁

常见的技术场景

单点登录

Single Sign On (SSO),一次登录,全部信任

方案

  • JWT
    1. 用户访问其他系统,会在网关判断token是否有效
    2. 如果token无效则返回401,前端跳转登录页面
    3. 用户发送登录请求,返回浏览器一个token,浏览器将token保存到cookie中
    4. 在其访问其他服务时,携带token,由网关同意验证后路由到目标服务
  • Oauth2.0
  • CAS

权限认证

用户 角色 权限 (菜单)

权限框架
  • Apache Shiro
  • Spring Security

上传数据的安全性

使用非对称加密(对称加密),给前端一个公钥让他把数据机密后传到后台,由后台解密。

  • 大文件使用对称加密

表述棘手的问题

  1. 什么背景 技术问题
  2. 过程 解决问题的过程
  3. 最终落地方案

cord死锁问题,ats内存优化问题

日志采集

可以根据日志信息采集系统问题

方式
  • ELK

    • Elasticasearch 全文搜索引擎

    • Logstash 数据手机引擎

    • Kinbaba 数据分析和可视化平台

    image-20240306102902014

    logback-spring.xml 配置Logstash

  • 常规采集

查看日志命令

linux

  • 实时监控日志的变化

    tail -f xx.log

    最后100行 tail -n 100 xx.log

  • 按照行号查询

    查询100至200行 cat -n xx.log|tail -n +100|head -n 100

  • 按照关键字找日志的信息

    cat -n xx.log|grep “dubug”

  • 按照日期查询

    sed -n ‘/2024-03-10 10:38:1031.070,/2024-03-10 10:38:1031.070/p’ xx.log

  • 日志太多,处理方式

    分页查询 cat -n xx.log|grep “grep”|more

生产环境问题怎么排查

已经上线的bug排查的思路:

  • 线分析日志,通常在业务中都会由日志的记录

  • 远程debug

远程代码和本地代码保持一致

  1. 远程diamagnetic需要配置启动参数
java -jar -agentlib:jdep=transport=dt_socket,server=y,suspend=n,address=5005 xx.jar
image-20240306104500857
  1. idea中启动远程debug
  2. 访问远程服务

快速定位系统瓶颈

  • 压测 性能测试
    1. 目的 给出系统当前的性能状况;定位系统性能瓶颈
    2. 指标 响应时间,QPS,并发性,吞吐量,cup利用率,内存使用率,磁盘IO,错误率
    3. 压测工具 jmeter
    4. 后端工程师 配合QA调优
  • 监控工具,链路追踪工具
    1. 监控工具 Prometheus+Grafana
    2. 链路追踪工具 skywalking
  • 线上诊断工具Arthas
    1. ali的
    2. 功能强大

项目亮点 基础实现

系统可用性(滴滴)

对于请求如何保证999的成功率

互联网架构中,通常是通过冗余+自动故障转移来保证系统的高可用特性。

一:服务架构层面

  1. 根据服务对象地区,考虑节点分布
  2. 避免服务单点,至少双机
  3. 防止代码之间干扰,避免稳定代码和迭代频繁代码放在一起,可以按照业务或者功能做服务分离。
  4. 防止服务之间干扰,重要服务最好做隔离,单独部署
  5. 防止数据库压力过大,不然,可能产生雪崩效应,可以根据业务特点做分库分表,加缓存等处理.
  6. 保证服务能力buffer, 尽量有冗余处理能力.

二:运维层面

  1. 服务监控。比如磁盘、CPU、网络
  2. 监控多级别,到达不同级别给出不同警告

三:代码层面

  1. 保证代码异常不会导致服务挂掉
  2. 保证服务是无状态的,可以支持水平扩展

防脚本策略(某AI公司)

后端幂等性校验

前端人脸识别,滑块

基础

IO

分类

阻塞&同步分类

同步阻塞 BIO

同步非阻塞 NIO

异步 AIO

java分类

字节流 inputStream outputStream

字符流 Writer Reader

基于磁盘的问渐渐IO

基于网络的socker-IO

面试

自我介绍

我叫Bryan,今年29岁,2018年毕业与青海大学,青海大学是一所211院校。java开发岗位工作6年。熟悉 前后端分离项目 技术栈:SpringBoot、SpringCloud、mybatis、redis 、kafka、ELK 等,数据库mysql/Oracle,部署工具Jenkins,AWS,linux熟练使用。

之前在高知特担任java开发的工作,主要负责代码设计,开发实现,数据库设计优化,线上问题紧急修复,业务梳理等工作,

项目介绍

技术亮点

  • 再有就是目前比较新的spring cloud之后新的模式,配置从开发端移除的一个概念性的微服务架构 side-car

  • 同步转异步的方式 加快接收速度 减慢处理速度

  • Azure就是配置中心 和storage存储

  • 云存储 S3 BOX

  • 一版就是AWS和微软的Azure 整个CICD流程

  • snowflake/salesforce

结构亮点

结合腾讯云做钩子函数 将链接发给用户

面试心得

  1. 面试时 一定要想好了在回答 梳理自我介绍/项目介绍
  2. 开心自信 这个很重要 整体氛围感要有
  3. 休息好了再去 最好是休息一下再去面试 保证最好的状态
  4. 硕士 加分项 未来准备
  5. 英语 加分项 未来准备
  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值