对java序列化有了解吗?

Java 序列化是将对象转换为字节流的过程,以便可以将其保存到文件、通过网络传输或在内存中缓存。

JDK自带的序列化,要让一个类支持序列化,需要实现 java.io.Serializable 接口。

缺点:

  • 生成的字节流体积大。可读性查。
  • Java 特有的,其他语言无法直接解析其生成的字节流。
  • JDK 反序列化机制存在安全漏洞,攻击者可以通过构造恶意字节流执行任意代码。

Jackson 库序列化,广泛用于Spring框架。我项目使用jackson的objectmapper序列化。

对java反序列框架有了解吗?

使用jackson进行反序列化。

讲一下java的常用gc算法以及各自的侧重点

标记-清除:先标记不被回收的,在清除没有被标记的。会产生较多碎片。适用于老年代。

标记-整理:先标记不被回收的,将存活的对象向内存一端移动,然后清理边界外的内存。不会产生较多碎片。适用于老年代。

复制算法:将内存分为两块,每次只使用其中一块。当一块内存用满时,将存活的对象复制到另一块内存,然后清空当前内存。内存利用率较低。适用于新生代。

新生代中的对象大多数是临时对象,生命周期很短,很快就会被回收。

复制算法只需要复制少量存活对象,效率高。新生代的内存区域通常较小(如几十MB到几百MB),复制算法的内存开销(需要一半内存作为空闲区域)可以接受。

cms和g1各自优缺点和适用场景,对比一下这两个

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器。已经过时。

优点:并发收集所以低停顿。

缺点

标记清除算法产生较多碎片。

在并发清理阶段,应用程序可能产生新的垃圾(浮动垃圾),这些垃圾只能等到下一次GC时回收。

使用场景:适用于老年代

G1,目前在使用的垃圾收集器。

  • G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) ,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
  • 空间整合:与 CMS 的“标记-清除”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的,产生的内存碎片少
  • 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒
  • 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。

缺点:

  1. 实现复杂:G1的实现较为复杂,可能导致额外的性能开销。

使用场景:

适用于大内存和低延迟场景中,整堆收集。

讲一下有哪些场景打破双亲委派机制

Java 的JDBC允许第三方提供实现类。

DriverManager位于rt.jar` 中,由启动类加载器加载,而数据库驱动类由第三方提供,由应用类加载器加载。

启动类加载器无法直接加载应用类路径下的类,因此需要通过线程上下文类加载器(Thread Context ClassLoader)打破双亲委派机制。

动态代理:动态代理(如 JDK 动态代理、CGLIB)需要动态生成代理类并加载。动态代理生成的类通常需要由自定义类加载器加载,而不委托父类加载器。

对springboot自动化配置有了解吗?

@SpringBootApplication 注解中包含了 @EnableAutoConfiguration,用通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配.

慢sql如何排查?可能有哪些原因导致慢sql

开启慢查询日志,

缺少索引,索引失效。

mysql的索引是什么?b+树,相较于hash有什么优点

范围查询+有序

讲一下tcp报文格式

image-20250119124152176

调表的时间复杂度空间复杂度

O(logn), 跳表通过多层链表存储数据,每层的节点数逐渐减少,空间复杂度为线性级别。

n+n/2+n/4+⋯≈2n,因此空间复杂度为 O(n)。

如果一个进程创建了过多的线程(比如几万个),可能会导致以下问题:

  • 每个线程都需要分配独立的栈空间。如果创建了几万个线程,内存消耗会非常巨大,可能导致内存耗尽(Out of Memory)。
  • 线程切换时,操作系统需要保存当前线程的状态(如寄存器、程序计数器等)并恢复下一个线程的状态。过多的线程会导致频繁的上下文切换,消耗大量 CPU 资源,降低系统整体性能。
  • 操作系统对单个进程可以创建的线程数有限制(如 Linux 的 ulimit -u/proc/sys/kernel/threads-max)。超过限制会导致线程创建失败。

http post如何实现幂等性?

服务端记录多个历史版本,请求的时候携带版本号。

Mysql事务出现死锁怎么处理?除了顺序分配资源以外?

MySQL 内置了死锁检测机制,当检测到死锁时,会自动选择一个事务作为“牺牲者”,回滚该事务以解除死锁。

了解过缓存行或者伪共享吗?

  • 缓存行是 CPU 缓存中的最小数据单元,通常大小为 64 字节(具体大小取决于 CPU 架构)。当 CPU 从内存中读取数据时,会一次性加载一个缓存行,而不是单个字节或字。
  • 伪共享是指多个线程同时修改位于同一个缓存行中的不同变量,导致缓存行在 CPU 核心之间频繁无效化,从而降低性能。

对应Redis的热点key有什么解决方案?

推荐:

Key 分片

  • 思路:将热点 Key 拆分为多个子 Key,分散到不同的 Redis 节点。
  • 实现例如,将 hot_key 拆分为 hot_key_1hot_key_2hot_key_3,分别存储在不同的节点

  • 优点:分散热点 Key 的访问压力。

  • 缺点:数据一致性需要额外处理。

本地缓存

  • 思路:使用本地缓存工具(如 Guava Cache、Caffeine)缓存热点 Key 的值。
  • 优点:减少对 Redis 的访问压力。
  • 缺点:本地缓存占用应用服务器的内存。

保底:

缓存降级

  • 在 Redis 无法承受压力时,降级到其他存储或直接返回默认值。
  • 优点:保证系统的可用性。
  • 缺点:数据可能不准确。

限流

  • 对热点 Key 的访问进行限流,避免单个节点过载。
  • 实现:使用限流工具(如 Redis 的 INCR 命令或分布式限流框架)限制访问频率。超出限流阈值时,直接返回错误或默认值。
  • 优点:保护 Redis 节点不被压垮。
  • 缺点:可能影响用户体验。

新技术:

使用分布式缓存

  • 将热点 Key 存储到多个分布式缓存节点。通过哈希算法决定访问哪个节点。

  • 优点:分散热点 Key 的访问压力。

  • 缺点:需要引入新的技术栈,增加运维成本。

如何解决缓存击穿问题?

解决方案

逻辑过期:只允许一个线程去重建缓存,其他线程返回脏数据。

互斥锁:只允许一个线程去获得锁,其他线程等待重建完成。

预防方案:

提前预热缓存。

保底方案:

在缓存失效时,通过限流或熔断机制控制请求量,避免数据库被压垮。

为什么数据库采用b+树,不是b树?

  • 树的高度低,查询效率高,查询速度稳定:因为非叶子节点,只存索引,不存数据,树的高度更低。
  • 适合范围查找和排序操作:所有数据都存储在叶子节点,且叶子节点通过指针连接成链表,便于范围查询。
  • 更高效的插入和删除:插入和删除操作主要集中在叶子节点,且叶子节点通过指针连接,调整更简单。而b树插入和删除可能涉及内部节点,调整更复杂。

缓存一致性

什么是TCP粘包和拆包?如何实现?

TCP粘包

发生在应用层。用户发送的多条数据包,比如hello和world,使用一个TCP来发送,接收方无法区分两条消息的边界。

使用固定长度的数据包、使用特殊字符分割、使用标志位+长度。

TCP拆包

发生在传输层。用户发生的一条数据包,由于MSS的限制,被用多条TCP发生。

解决方案:

  • 使用HTTP,使用消息头加消息体(长度)。
  • Netty 供了多种内置的解码器来解决粘包和拆包问题。

字节的文化

创业、多元、务实、坦诚、极致、共同成长

说一下你对redis的理解

高性能的键值对存储系统,常用作缓存中间件。

基于内存,NoSQL,优化过的数据结构,支持持久化、集群。

MySQL最多可以存多少数据?为什么是2000W而不是2亿?

从理论上讲,MySQL 单表可以存储 数十亿甚至上百亿 条数据。

但是维护成本显著增高:单表数据量超过 2000W 时,B+ 树的深度可能从 3 层增加到 4 层,查询时需要多一次磁盘 I/O,性能明显下降。当单表数据量超过 2000W 时,通常建议使用 分区表分表 来优化性能。

UV和DAU怎么统计?

UV 是指在 一定时间范围内,访问网站或应用的 独立用户数量。同一个用户无论访问多少次,都只计为 1 个 UV。

DAU 是指在 一天内,访问网站或应用的 独立用户数量。同一个用户无论访问多少次,都只计为 1 个 DAU。

Redis的HyperLogLog占用内存极小,且 计算速度快,非常适合处理大规模数据的去重统计。

HyperLogLog 是 Redis 提供的一种 基数统计 算法,用于高效地估计一个集合中 唯一元素的数量(即基数)。它的特点是 占用内存极小,且 计算速度快,非常适合处理大规模数据的去重统计。

优点

  • 内存占用低:每个 HyperLogLog 仅占用 12KB 内存。
  • 计算速度快:时间复杂度为 O(1),适合实时统计。
  • 支持大规模数据:可以统计多达 2^64 个唯一元素。

缺点

  • 近似统计:结果存在一定误差,不适合需要精确统计的场景。
  • 不支持元素查询:无法判断某个元素是否已存在于 HyperLogLog 中。

MySQL的主从同步是如何实现的

使用binLog日志。

image-20250226130132313

static关键字

volatile 和 AtomicInteger的区别

AtomicIntege原子类使用了volatile关键字修饰,保证了可见性,但是不能保证原子性,如 i++ 是非原子的。

AtomicIntege原子类提供了一些复合操作函数,使用CAS(底层是unsafe类的nativ方法)保证原子性。

进程间可以资源共享吗?

其实就是进程之间通信。

分布式锁

实现思路:

  • 利用set nx ex获取锁,并设置过期时间,保存线程标示
  • 释放锁时先判断线程标示是否与自己一致,一致则删除锁。(Lua脚本保持原子性)

Redis分布式锁特性:

  • 利用set nx满足互斥性
  • 利用set ex保证故障时锁依然能释放,避免死锁,提高安全性
  • 利用Redis集群保证高可用和高并发特性

如果要实现可重入,可以把string改成hash结构。

redis.call('hset', key, 'threadId', threadId)
redis.call('hset', key, 'count', 1)
redis.call('expire', key, expireTime)

但是还是有缺点,没有超市续约功能。

Redisson分布式锁原理

  • 可重入:利用hash结构记录线程id和重入次数
  • 可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制,不浪费cpu资源
  • 超时续约:利用watchD0g,每隔一段时间(releaseTime/3),重置超时时间,也就是重新expire。

缺点:redis单个节点崩溃就会失效,

解决方案:联锁, 通过在多个 Redis 实例(通常是 5 个)上同时获取锁,来确保即使某个 Redis 实例宕机,锁依然安全有效。

单例模式有哪些

⼀个单例类在任何情况下都只存在⼀个实例。

  • 构造⽅法必须是私有的
  • 由⾃⼰创建⼀个私有的静态变量存储实例。
  • 对外提供⼀个静态公有⽅法获取实例。

饿汉式:在类加载时就创建实例,线程安全。

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

懒汉式,在第一次调用 getInstance 时创建实例。线程不安全,需要额外处理多线程问题。

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

线程安全的double-check的懒汉式

//线程安全的单例模式
public class Singleton {
    // 使用 volatile 关键字确保 instance 的可见性
    private static volatile Singleton instance;

    // 私有构造函数,防止外部实例化
    private Singleton() {
        // 初始化代码
    }

    // 获取单例实例的静态方法
    public static Singleton getInstance() {
        // 第一次检查,避免不必要的同步
        if (instance == null) {
            // 加锁,确保线程安全
            synchronized (Singleton.class) {
                // 第二次检查,防止多个线程同时通过第一次检查
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

MySQL查询语句执行顺序

FROM → 2. WHERE → 3. GROUP BY → 4. HAVING → 5. SELECT → 6. DISTINCT → 7. ORDER BY → 8. LIMIT

HTTP1.1,2.0,3.0

HTTP1.1

  1. 队头阻塞:http会先请求html网页,然后请求css,图片等东西,如果css不收到,后面的请求也不会发出。
  2. 基于文本协议,明文传输 ,不安全
  3. 不支持服务端推送

HTTP2

  • 多路复用解决队头堵塞:多个请求和响应可以在同一个 TCP 连接上并行传输,互不干扰。但是HTTP/2 仍然依赖 TCP 作为传输层协议,而 TCP 是面向连接的、可靠的协议。如果 TCP 数据包在传输过程中丢失,TCP 会重传丢失的包,导致后续数据包被阻塞(即使它们已经到达接收端)。
  • 二进制协议,性能高,一定程度加密。
  • 头部压缩(HPACK):减少了 HTTP 头部的冗余数据。
  • 服务器推送(Server Push):服务器可以主动向客户端推送资源,减少请求次数。

image-20250226192506069

HTTP/3

  • 基于 QUIC 传输层协议,运行在 UDP 之上,完全解决了 TCP 的队头阻塞问题。
  • UDP 本身不提供可靠性,因此可以在应用层(如 QUIC)实现自定义的可靠性机制
  • 默认加密(使用 TLS 1.3)。

读写穿透

读/写穿透模式 通过将缓存与数据库整合为一个服务,由服务维护一致性,简化了调用者的操作。实现该模式的关键在于:

  1. 读操作:先查缓存,未命中时加载数据库数据并写入缓存。
  2. 写操作:先更新数据库,再更新缓存。
  3. 服务封装:提供统一的接口,隐藏缓存与数据库的细节。

通过这种模式,可以显著提升系统性能,同时保证数据的一致性。希望以上内容能帮助你实现读/写穿透模式!

异步缓存写入

  • 读操作 主要依赖缓存,缓存命中时直接返回数据,缓存未命中时从数据库加载数据并写入缓存。
  • 写操作 只操作缓存,由后台线程异步将缓存数据持久化到数据库,缓存和数据库之间可能存在短暂的不一致,但最终会保持一致。

Redis内存是有上限的,如何进行淘汰?

redis.conf` 中设置 `maxmemory-policy

默认策略:当内存不足时,新写入操作会返回错误,不会淘汰任何数据。

allkeys-lru:从所有键中淘汰最近最少使用(Least Recently Used, LRU)的键。

volatile-lru 从设置了过期时间的键中淘汰最近最少使用的键。

volatile-ttl:设置了过期时间的键中淘汰剩余生存时间(Time To Live, TTL)最短的键。

MySQl执行查询语句后的日志如何记录的

通用查询日志、慢查询日志、错误查询日志。

数据库错误恢复如何实现的

RedoLog(已提交但未写入磁盘),UndoLog(回滚未提交的事务)

检查点:

  1. 数据库定期将内存中已修改的数据页写入磁盘。
  2. 写入完成后,记录一个检查点,表示在此之前的日志不再需要重放。

什么是分布式事务

分布式事务是指跨越多个分布式系统或数据库的事务操作,需要保证这些操作要么全部成功,要么全部失败,以满足事务的 ACID 特性(原子性、一致性、隔离性、持久性)。

两阶段提交(2PC,Two-Phase Commit)

  • 阶段一(准备阶段)
    • 事务协调者向所有参与者发送准备请求。
    • 参与者执行事务操作,但不提交,返回准备结果(成功或失败)。
  • 阶段二(提交阶段)
    • 如果所有参与者都准备成功,协调者发送提交请求,参与者提交事务。
    • 如果有参与者准备失败,协调者发送回滚请求,参与者回滚事务。
  • 优点:强一致性。
  • 缺点:同步阻塞、性能低、单点故障。

线程之间如何通信,举例子

为什么Redis如此快

  1. 基于内存
  2. 优化过的数据结构
  3. 单线程模型,没有线程切换,也不会存在锁竞争
  4. 非阻塞IO,比如epoll模式
  5. 网络协议简单高效

http和websorcket区别

  • HTTP: HTTP 是一种请求-响应协议。客户端(如浏览器)向服务器发送请求,服务器处理请求后返回响应。
  • WebSocket: WebSocket 是一种全双工通信协议。客户端和服务器之间建立一个持久的连接,双方可以随时发送数据,而不需要等待请求。WebSocket 连接一旦建立,就可以持续通信,直到其中一方主动关闭连接。
  • HTTP: 每次请求和响应都需要携带完整的 HTTP 头部信息,包括方法、路径、状态码、Cookie 等。
  • WebSocket: 在连接建立后,数据传输时只需要携带少量的控制信息,头部开销较小。
  • HTTP: HTTP 本身不支持实时通信。适用于传统的 Web 应用,如网页浏览、文件下载。
  • WebSocket: WebSocket 是专门为实时通信设计的。它允许服务器主动向客户端推送数据,非常适合需要实时交互的应用场景,如在线聊天、实时游戏、股票行情等。

对于Java的OOM(Out of Memory)如何解决

堆内存不足:对象过多,堆内存耗尽。

栈内存不足:线程栈内存耗尽(通常是递归调用过深或线程过多)。

元空间(Metaspace)不足:加载的类过多,元空间内存耗尽。

解决方案:对于大内存应用,使用 G1ZGC 垃圾回收器。增加栈和堆空间。及时释放资源

DNS为什么使用UDP而不是TCP

  1. 低开销 UDP无需建立和断开连接,减少了通信开销,适合DNS查询这种短小的请求。
  2. 速度快 UDP没有握手过程,响应更快,适合对实时性要求高的DNS查询。
  3. 小数据包 DNS查询和响应通常很小,UDP的512字节限制在大多数情况下足够使用。

尽管UDP是首选,但在以下情况下DNS会使用TCP:

  • 响应数据超过512字节(启用EDNS0时)。
  • 区域传输(AXFR/IXFR)需要可靠传输。
  • 某些DNSSEC查询需要TCP的可靠性。

TIME_WAIT 堆积是什么原因如何解决

  1. 短连接过多:如果客户端频繁创建和关闭 TCP 连接(如 HTTP 短连接),会导致大量连接进入 TIME_WAIT 状态。
  2. 主动关闭连接的一方:TCP 连接中,主动关闭连接的一方会进入 TIME_WAIT 状态。如果服务器或客户端频繁主动关闭连接,会导致 TIME_WAIT 堆积。

使用长连接:尽量避免频繁创建和关闭连接,改用长连接(如 HTTP 的 Keep-Alive)

确保连接关闭由合适的一方发起。例如,尽量让客户端主动关闭连接,减少服务器端的TIME_WAIT 状态。

如果是服务器端问题,可以通过增加服务器或使用负载均衡分散连接压力。

DNS有什么安全问题,什么是DNSSEC

DNS欺骗、劫持,重定向到攻击者的钓鱼网站。

DNSSEC(DNS Security Extensions) 是一组用于增强 DNS 安全性的扩展协议。它通过数字签名和公钥加密技术,解决了 DNS 的安全问题。

项目频繁出现fullgc如何排查?

看看堆内存设置是否过小。

避免频繁创建和销毁对象,尽量复用对象。

检查是否有大对象频繁创建。

Redis 某个节点失效,大量的请求打过来,怎么办。

使用Redis Sentinel 或 Redis Cluster 实现高可用。

手动故障转移。

如果 Redis 不可用,可以暂时降级到本地缓存或数据库,确保服务基本可用。

MyBatis的缓存机制

一级缓存(Local Cache)

特点

  • 作用范围:默认开启,作用范围是 SqlSession 级别。
  • 生命周期:与 SqlSession 绑定,当 SqlSession 关闭或清空时,缓存也会被清空。
  • 共享性:一级缓存是 SqlSession 私有的,不同 SqlSession 之间无法共享。

工作原理

  • 在同一个 SqlSession 中,如果多次执行相同的 SQL 查询(相同的 SQL 和参数),MyBatis 会从一级缓存中直接返回结果,而不会再次查询数据库。
  • 如果执行了 INSERT、UPDATE、DELETE 操作,MyBatis 会自动清空一级缓存,以保证数据一致性。

二级缓存(Global Cache)

  • 作用范围:默认关闭,需要手动开启,作用范围是 Mapper 级别
  • 生命周期:与应用程序的生命周期一致,只有当应用程序关闭时,缓存才会被清空。
  • 共享性:二级缓存是跨 SqlSession 的,多个 SqlSession 可以共享同一个二级缓存。

工作原理

  • 当 SqlSession 提交或关闭时,MyBatis 会将一级缓存中的数据存入二级缓存。
  • 后续的 SqlSession 查询时,如果二级缓存中存在数据,则直接从缓存中返回结果,而不会访问数据库。
  • 如果执行了 INSERT、UPDATE、DELETE 操作,MyBatis 会自动清空二级缓存,以保证数据一致性。

二者区别

  • 一级缓存:适合单次会话中重复查询的场景。
  • 二级缓存:适合跨会话共享数据且数据更新频率较低的场景。
  • 二者都可能出现脏读。

Spring的bean为什么默认是单例?

Spring 容器只会创建一个 Bean 实例,并在整个应用程序中共享。这避免了频繁创建和销毁对象的开销,提高了性能。

在大多数场景中,Bean 是无状态的,线程安全。

常见linux命令

  • ls:列出目录内容
  • cd: 切换目录
  • ping:测试链接情况
  • cp:复制文件 -r递归复制
  • tar:压缩文件。
  • mv:移动文件
  • rm:删除文件 -r递归删除 -f强制删除
  • vim:文本编辑器 :wq保存 i编辑模式

AOP的原理

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

动态代理原理

动态代理通过反射机制调用目标对象的方法,用于在运行时动态获取和操作类的信息。

两种实习方式:JDK动态代理、CGLIB动态代理。

Copyright © 版权信息 all right reserved,powered by aspire-zero and Gitbook该文件修订时间: 2025-03-04 10:08:33

results matching ""

    No results matching ""