找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2328

积分

1

好友

321

主题
发表于 2025-12-25 08:49:20 | 查看: 30| 回复: 0

JVM垃圾回收器:CMS与G1详解

面试中常被问及JVM的垃圾回收器,其中CMS(Concurrent Mark-Sweep)G1(Garbage-First) 是两个经典且重要的收集器。

CMS的设计目标是获取最短的垃圾收集停顿时间,它主要分为四个阶段:初始标记、并发标记、重新标记、并发清除。其“并发”特性体现在大部分工作线程可以与垃圾收集线程同时运行,但这也带来了缺点,例如对CPU资源敏感、无法处理“浮动垃圾”,以及收集结束后会产生内存碎片。

G1收集器则面向服务端应用,旨在替代CMS。它将堆内存划分为多个大小相等的独立区域(Region),并优先回收价值最高(即垃圾最多)的区域,这也是其名称的由来。G1的运作过程同样复杂,包括初始标记、并发标记、最终标记、筛选回收等阶段。与CMS相比,G1能提供更可预测的停顿时间模型,并最终进行压缩整理,有效避免了内存碎片问题。

Java并发工具包(JUC)深度探讨

除了最常被问到的 ConcurrentHashMap,Java并发工具包(JUC)内容非常丰富。它主要包含以下几大部分:

  • 锁机制:如 ReentrantLockStampedLockReadWriteLock 等,提供了比synchronized关键字更灵活、功能更强大的锁控制。
  • 并发集合:除了 ConcurrentHashMap,还有 CopyOnWriteArrayListConcurrentLinkedQueueBlockingQueue 的各种实现(如 ArrayBlockingQueueLinkedBlockingQueue)等,它们是线程安全的高性能集合。
  • 原子类AtomicIntegerAtomicReference 等,通过CAS(Compare-And-Swap)操作实现无锁的线程安全编程。
  • 并发工具类:最核心的当属 线程池(ThreadPoolExecutor,此外还有用于控制并发线程数量的 Semaphore、用于线程间同步的 CountDownLatchCyclicBarrier 等。

线程池的核心使用与实践

在实际项目中,创建线程池主要有两种方式:

  1. 通过 Executors 工厂类创建(如 newFixedThreadPool, newCachedThreadPool)。
  2. 直接通过 ThreadPoolExecutor 的构造函数手动创建。

阿里巴巴Java开发手册明确不推荐使用 Executors 来创建线程池,主要是因为其预设的几种方式存在潜在风险。例如,newFixedThreadPoolnewSingleThreadExecutor 使用的等待队列是无界的 LinkedBlockingQueue,可能导致任务堆积耗尽内存;newCachedThreadPoolnewScheduledThreadPool 允许创建的线程数量为 Integer.MAX_VALUE,可能创建大量线程耗尽CPU和内存资源。因此,手动配置 ThreadPoolExecutor 的七个核心参数是更佳实践

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:空闲线程存活时间
  • unit:时间单位
  • workQueue:任务队列
  • threadFactory:线程工厂
  • handler:拒绝策略

网络I/O模型:多路复用技术

I/O多路复用是一种高效的网络I/O模型,它允许单个线程监听多个文件描述符(如Socket)的读写事件。常见的实现有 selectpollepoll

  • select/poll:本质上都是轮询机制,需要遍历所有被监控的文件描述符来检查事件是否就绪。当连接数很大时,性能线性下降。select 有文件描述符数量限制(通常1024),poll 使用链表结构则没有此限制。
  • epoll:是Linux下高性能的多路复用实现。它采用了事件通知机制,通过 epoll_ctl 注册文件描述符和关心的事件,当事件就绪时,内核会通过 epoll_wait 直接返回就绪的事件列表,避免了无效的遍历,效率不随连接数增加而显著下降。

Redis数据结构与应用

Redis不仅支持五种基础数据结构,还提供了一些特殊类型,使其能应对更复杂的场景。

五大基础数据结构

  1. String(字符串):最简单的键值对,可用于缓存、计数器等。
  2. Hash(哈希):适合存储对象,如用户信息。
  3. List(列表):双向链表,可实现消息队列、最新列表等。
  4. Set(集合):无序、元素唯一的集合,适用于共同关注、抽奖等。
  5. Zset(有序集合):在Set基础上为每个元素关联一个分数(score),可用于排行榜、带权重的队列。

三种特殊数据结构

  1. Bitmaps(位图):通过位操作实现二值状态统计,如用户签到。
  2. HyperLogLog:用于基数统计(估算不重复元素数量),占用内存极小。
  3. Geospatial(地理位置):存储地理位置信息,并进行距离计算、范围查找等。

缓存一致性解决方案

保证缓存(如Redis)与数据库(如MySQL)的数据一致性是一个经典问题。没有银弹,通常需要在一致性强度、性能和复杂度之间权衡。常见的策略有:

  • Cache Aside Pattern(旁路缓存模式):最常用的模式。读时先读缓存,缓存没有则读库并回写缓存;更新时先更新数据库,再删除缓存(而非更新)。此模式可能产生短时间的数据不一致,但实现简单。
  • Read/Write Through(读写穿透):应用将缓存作为主要数据源,缓存层负责自己与数据库的同步。对应用透明,但实现复杂。
  • Write Behind/Back Caching(异步写回):更新时只更新缓存,缓存异步批量写回数据库。性能最好,但存在数据丢失风险。
    在实际的数据库与中间件应用中,通常会结合消息队列或订阅数据库Binlog等方式,来异步刷新或删除缓存,以最终达成一致性。

消息队列Kafka的核心机制

如何保证消息消费顺序?

Kafka保证的是分区(Partition)内的消息有序,而非整个主题(Topic)有序。因此,要保证某一类消息的顺序消费,需要将这类消息都发送到同一个分区。这通常通过为消息指定相同的Key来实现,因为Kafka默认的分区器会根据Key的哈希值将消息分配到特定分区。

Kafka高性能的奥秘

Kafka的极高性能得益于其多方面的精妙设计,存储层设计是核心之一:

  1. 顺序读写:Kafka将消息持久化到磁盘,但采用了追加写入(Append-only) 的方式,充分利用了磁盘顺序读写速度远快于随机读写的特性。
  2. 零拷贝(Zero-Copy):在消费者读取数据时,Kafka利用操作系统的 sendfile 系统调用,将数据直接从磁盘文件(PageCache)发送到网络Socket,避免了内核缓冲区与用户缓冲区之间的多次拷贝,极大减少了CPU开销和上下文切换。
  3. 页缓存(PageCache):Kafka重度依赖操作系统的页缓存来存储数据,而不是在JVM堆内维护缓存。这减少了GC压力,并且读操作可以直接命中页缓存,速度极快。
  4. 分区与并行:Topic被分为多个分区,散布在多个Broker上,生产与消费都可以并行处理,水平扩展能力极强。
  5. 批量处理:生产者支持批量发送消息,消费者也支持一次拉取一批消息,有效减少了网络I/O次数。
    这些设计,尤其是在大数据流处理场景下,共同造就了Kafka的高吞吐量。

RPC基础概念

RPC(Remote Procedure Call,远程过程调用) 是一种计算机通信协议,允许程序调用位于另一个地址空间(通常是网络上的另一台机器)的子程序或服务,而无需显式编码远程调用的细节。调用者感知不到调用的方法是本地的还是远程的。其简单原理通常包括:客户端存根(Stub)序列化参数并发送请求,服务端存根接收请求、反序列化参数、调用本地方法,再将结果序列化返回给客户端。常见的RPC框架有gRPC、Dubbo、Thrift等。




上一篇:.NET 10 File-Based Apps 实战:为AI Agent编写高效Skill脚本指南
下一篇:Spark数仓开发简历优化指南:技术深度、业务价值与大厂面试核心
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 08:51 , Processed in 0.227154 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表