百度日常实习凉经
手撕:n个数范围是[1,n]怎么找出重复数字,并且不改变原来的数组,O(1)的空间复杂度
类似环形链表,先定位重逢的点,然后从头开始定位重复元素,一开始没写出来面试官的提醒下最后还是oc了
场景题:应该是挂在这了
(1)怎么设计一个在线共同编辑的word结合场景等分析
(2)如何处理sql慢查询大量请求响应缓慢
核心目标:快速定位慢查询根源,针对性优化。
步骤:
- 监控与告警:使用工具(如 Prometheus + Grafana)监控数据库 QPS、慢查询比例、CPU/内存/磁盘 IO。设置慢查询阈值(如 MySQL 的 long_query_time=1s),触发告警。
- 日志分析:收集慢查询日志(MySQL 的 slow_query_log),通过工具(如 pt-query-digest)分析高频慢 SQL。检查执行计划:EXPLAIN SELECT ...,关注全表扫描(type=ALL)、未用索引(key=NULL)。
- 上下文关联:结合业务日志,确认慢查询是否由突发流量(如促销)、数据量增长或低效代码(如循环调用 SQL)引起。
1.ConcurrentHashMap
的扩容机制
1. 扩容触发条件
- 元素数量超过阈值:当
ConcurrentHashMap
中的元素数量超过容量 * 负载因子
时,触发扩容。默认负载因子为 0.75。 - 并发插入时检测到需要扩容:在插入元素时,如果发现当前数组已满(即元素数量达到阈值),会触发扩容。
2. 扩容过程
ConcurrentHashMap
的扩容过程是渐进式的,允许多个线程并发参与扩容,具体步骤如下:
(1)初始化新数组
- 当需要扩容时,
ConcurrentHashMap
会创建一个新的数组,其容量是原数组的两倍。 - 新数组的初始化是懒加载的,只有在真正需要迁移数据时才会进行。
(2)并发迁移数据
- 迁移任务分片:将整个数组的桶分为多个任务分片(通常是 16 个),每个线程负责迁移一部分桶。
- 迁移单个桶:对于每个桶,线程会将其链表或红黑树拆分为两部分:一部分留在原数组的位置。另一部分迁移到新数组的对应位置(通过重新哈希计算新的索引)。迁移过程中,原桶会被标记为 ForwardingNode,表示该桶正在迁移。
- 协助扩容:在扩容过程中,如果有新的线程插入数据,发现某个桶正在迁移(即遇到
ForwardingNode
),该线程会协助完成迁移任务,而不是等待。
(3)替换数组
- 当所有桶都迁移完成后,
ConcurrentHashMap
会将内部数组引用指向新数组。 - 旧数组会被垃圾回收。
(4)其他:插入节点
- 桶为空:如果目标桶为空,使用 CAS 操作尝试直接插入新节点。如果 CAS 成功,插入完成;否则,重试整个流程。
- 桶不为空:如果目标桶已有节点,锁定该桶(使用
synchronized (f)
),然后进行以下操作:链表:遍历链表,查找是否存在相同的键:如果存在,更新值(除非 onlyIfAbsent 为 true)。如果不存在,插入新节点到链表末尾。如果链表长度超过阈值(默认 8),调用 treeifyBin 将链表转换为红黑树。红黑树:如果桶中存储的是红黑树,调用红黑树的插入方法 putTreeVal。
3.HashMap扩容机制
- 触发条件:元素数量 > 容量 × 负载因子(默认16 × 0.75=12)。
- 扩容步骤:新容量 = 旧容量 × 2(如16→32)。重建哈希表,重新计算每个元素的位置((hash & (newCap-1)))。旧链表拆分为高位链和低位链(利用hash & oldCap是否为0)。
4.动态代理实现
代理模式(Proxy Pattern)是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式常用于延迟加载、访问控制、日志记录、事务管理等场景。根据代理对象的创建时机和使用方式,代理模式主要分为静态代理和动态代理。
静态代理定义:静态代理是在编译期就已经确定代理类和被代理类的关系。代理类通常需要手动编写,并持有被代理对象的引用,通过代理类来间接访问被代理对象。
动态代理定义:在运行时动态生成代理类,无需在编译期手动编写代理类。Java 提供了两种动态代理机制:基于接口的 JDK 动态代理和基于类的 CGLIB 动态代理。
- JDK动态代理:基于接口,通过Proxy.newProxyInstance()创建代理对象,实现InvocationHandler接口的invoke()方法添加逻辑。
- CGLIB动态代理:通过继承目标类生成子类,拦截方法调用,无需接口。使用MethodInterceptor接口的intercept()方法增强逻辑。
5.AOP的原理怎么实现
6.32位系统和64位的系统实现JVM虚拟机和java程序有什么区别
1. 内存寻址能力
- 32位系统:地址空间:每个进程的虚拟地址空间为4GB(2³²字节)。其中,一部分地址空间被操作系统保留,通常Java应用程序可用的堆内存最大约为2GB到3GB(具体取决于操作系统配置)。限制:由于地址空间的限制,32位JVM无法分配超过约3GB的堆内存,这在处理大型应用或大数据集时可能成为瓶颈。
- 64位系统:地址空间:每个进程的虚拟地址空间理论上可达16EB(2⁶⁴字节),但实际受限于操作系统和硬件,通常可用的虚拟地址空间为128TB或更多。优势:64位JVM可以分配更大的堆内存(理论上无上限,但实际受限于物理内存和操作系统限制),适合需要大量内存的应用场景,如大数据处理、高性能计算等。
2. JVM实现与配置
- 32位JVM:堆内存限制:默认堆内存较小,通常需要手动配置以充分利用可用内存,但受限于32位系统的地址空间。兼容性:适用于大多数现有的Java应用程序,尤其是那些对内存需求不高的应用。
- 64位JVM:堆内存配置:可以配置更大的堆内存(通过-Xmx和-Xms参数),适合需要大量内存的应用。优化选项:支持更多的JVM优化选项和特性,如G1垃圾收集器、ZGC等,适应不同的性能需求。兼容性:大多数Java应用程序可以在64位JVM上无缝运行,但需要确保所有依赖库和组件也兼容64位环境
java -Xms256m -Xmx1024m -Xss512k MyJavaApp
这个命令将为MyJavaApp分配初始堆内存256MB,最大堆内存1024MB,每个线程的栈内存大小为512KB。
7.k8s和docker的理解
8.声明式事务失效的原理
为什么声明式事务必须使用public,还有对啥类型的误差会存在异常
- 非公共方法上的注解:在Spring AOP中,只有public方法上的@Transactional注解才会被代理,protected、private方法上的注解不会生效。
- 异常处理不当:默认情况下,
@Transactional
注解只对未捕获的RuntimeException和Error进行回滚。如果抛出的异常被捕获并且没有重新抛出,或者抛出的异常不是RuntimeException或其子类,事务将不会回滚。可以通过设置rollbackFor
属性来指定哪些异常需要回滚。
Java 中的异常类型
Java 使用 Throwable
类作为所有错误和异常的超类。Throwable
有两个主要的子类:
- **
Error
(错误)** - **
Exception
(异常)**
1. Error(错误)
Error
表示严重的问题,通常是虚拟机相关的问题,程序通常无法处理这些错误。常见的 Error
包括:
- **
OutOfMemoryError
**:内存不足错误,表示 JVM 无法分配更多内存。 - **
StackOverflowError
**:栈溢出错误,通常由于递归调用过深导致。 - **
NoClassDefFoundError
**:类定义未找到错误,通常在编译后类文件丢失时发生。
特点:
- 通常由 JVM 或底层资源问题引起。
- 程序一般无法恢复,建议记录日志并终止程序。
2. Exception(异常)
Exception
表示程序可以处理的异常情况。它又分为两类:
a. 受检异常(Checked Exception)
受检异常是在编译时强制要求处理的异常,开发者必须显式地捕获或声明抛出这些异常。常见的受检异常包括:
- **
IOException
**:输入输出操作失败时抛出,如文件读写错误。 - **
SQLException
**:数据库操作失败时抛出。 - **
ClassNotFoundException
**:找不到指定的类时抛出。
特点:
- 编译器会检查是否处理这些异常(捕获或声明抛出)。
- 通常表示可以预见的、可恢复的错误情况。
b. 非受检异常(Unchecked Exception)
非受检异常是在编译时不强制要求处理的异常,通常是程序逻辑错误导致的。非受检异常继承自 RuntimeException
。常见的非受检异常包括:
- **
NullPointerException
**:试图访问null
对象的成员时抛出。 - **
ArrayIndexOutOfBoundsException
**:数组索引越界时抛出。 - **
ArithmeticException
**:算术运算错误时抛出,如除以零。 - **
IllegalArgumentException
**:方法接收到不合法的参数时抛出。
特点:
- 编译器不会强制要求处理这些异常。
- 通常表示程序逻辑错误,应通过修改代码避免。
9. 消费延迟一天怎么处理
其他:分布式锁怎么加库存,使用incr的操作加锁,保证目标最终和,降低竞争
10. G1和CMS
G1回收器
- 内存划分:将堆划分为多个大小相等的Region(1~32MB),分为Eden、Survivor、Old、Humongous(大对象)区。
- 工作流程:初始标记(STW):标记GC Roots直接关联的对象。并发标记:遍历堆标记存活对象。最终标记(STW):处理并发标记期间的引用变化。筛选回收(STW):选择回收价值高的Region进行清理。
CMS:
- 初始标记(Initial Mark):短暂停顿,标记 GC Roots 直接引用的对象。
- 并发标记(Concurrent Mark):与应用线程并发执行,遍历整个对象图,标记所有可达对象。
- 预清理(Preclean):并发执行,处理在并发标记阶段发生变化的对象引用。
- 重新标记(Remark):短暂停顿,修正并发标记期间因应用线程运行而产生的标记变动。
- 并发清除(Concurrent Sweep):与应用线程并发执行,清除不可达对象,回收空间。
- 并发重置(Concurrent Reset):准备下一次垃圾回收。
G1:
- 初始标记(Initial Mark):短暂停顿,标记 GC Roots 直接引用的对象,并记录下需要清理的 Region。
- 并发标记(Concurrent Mark):与应用线程并发执行,遍历整个对象图,标记所有可达对象。
- 最终标记(Final Mark):短暂停顿,处理引用变化,并完成标记工作。
- 清理(Cleanup):部分并发,部分停顿,统计每个 Region 的存活对象,准备进行空间回收。
- 内存整理(Compaction):在必要时,对 Region 进行内存整理,消除碎片。
设计目标 | 低延迟,适用于对响应时间要求高的应用 | 平衡吞吐量和延迟,适用于大堆内存和多核 CPU 的应用 |
堆内存划分 | 连续的堆内存空间 | 将堆内存划分为多个固定大小的区域(Region),每个区域可以是 Eden、Survivor 或 Old |
垃圾回收阶段 | 初始标记、并发标记、预清理、重新标记、并发清除、并发重置 | 初始标记、并发标记、最终标记、清理(可并行或并发执行) |
停顿时间控制 | 尽量减少停顿时间,但无法精确控制 | 提供可预测的停顿时间,通过设置
参数 |
碎片化处理 | 容易产生内存碎片,可能导致 Full GC | 通过区域化管理,减少内存碎片,避免 Full GC 的发生 |
适用堆大小 | 适用于中小型堆(通常小于 4GB) | 适用于大型堆(通常大于 4GB),可以高效管理大内存 |