南京莱斯-Java开发- 二面 面经
1、介绍一下你做过的最有挑战性的项目
项目背景:
- 业务场景:电商平台秒杀系统,需要支持10万+并发抢购
- 技术挑战:高并发、超卖防控、系统稳定性、用户体验
- 团队规模:5人开发团队,我负责核心交易模块
技术方案设计:
- 前端限流:按钮置灰、请求合并、本地倒计时
- 网关层:Nginx限流,单IP每秒最多10次请求
- 应用层:Redis预减库存,Lua脚本保证原子性
- 数据层:MySQL行锁+乐观锁,防止超卖
- 消息队列:RabbitMQ削峰,异步处理订单
遇到的问题:
- 问题1:Redis缓存击穿导致数据库压力骤增 解决:使用互斥锁,只允许一个线程查询数据库
- 问题2:分布式环境下库存扣减不一致 解决:引入Redisson分布式锁,保证原子性操作
- 问题3:订单创建失败但库存已扣减 解决:引入本地消息表+定时任务补偿机制
项目成果:
- 性能指标:支持峰值QPS达到8万,响应时间P99<200ms
- 业务指标:超卖率降为0,用户投诉下降70%
- 技术沉淀:形成秒杀系统技术文档,可复用到其他业务
2、JVM内存模型是怎样的,各区域的作用是什么
线程共享区域:
- 堆(Heap):作用:存储对象实例和数组,GC主要工作区域分代:新生代(Eden+S0+S1,8:1:1)、老年代配置:-Xms初始堆大小,-Xmx最大堆大小特点:所有线程共享,是内存管理的核心区域
- 方法区(Method Area):作用:存储类信息、常量、静态变量、JIT编译后的代码JDK7:永久代PermGen,容易OOMJDK8:元空间Metaspace,使用本地内存配置:-XX:MetaspaceSize、-XX:MaxMetaspaceSize
线程私有区域:
- 程序计数器(PC Register):作用:记录当前线程执行的字节码行号特点:唯一不会OOM的区域,占用内存极小线程切换:保存和恢复执行位置
- 虚拟机栈(VM Stack):作用:存储局部变量表、操作数栈、动态链接、方法出口栈帧:每个方法对应一个栈帧,方法调用时入栈,返回时出栈异常:StackOverflowError(递归过深)、OOM(栈空间不足)配置:-Xss设置栈大小,默认1MB
- 本地方法栈(Native Method Stack):作用:为Native方法服务特点:与虚拟机栈类似,HotSpot合并实现
直接内存(Direct Memory):
- 不属于JVM运行时数据区,但被频繁使用
- NIO的ByteBuffer.allocateDirect()分配
- 避免Java堆和Native堆之间的数据拷贝
- 配置:-XX:MaxDirectMemorySize
内存溢出场景:
- 堆溢出:创建大量对象且无法回收
- 栈溢出:递归调用层次过深
- 方法区溢出:动态生成大量类(CGLib)
- 直接内存溢出:NIO使用不当
3、垃圾回收算法有哪些,各自的优缺点是什么
标记-清除算法(Mark-Sweep):
- 执行过程: 标记阶段:从GC Roots遍历,标记所有可达对象清除阶段:回收未标记的对象
- 优点:实现简单,不需要移动对象
- 缺点:产生内存碎片,分配大对象时可能触发Full GC
- 应用场景:CMS收集器的老年代回收
标记-复制算法(Mark-Copy):
- 执行过程: 将内存分为两块,只使用其中一块GC时将存活对象复制到另一块,清空当前块
- 优点:无内存碎片,分配内存时只需移动指针
- 缺点:可用内存减半,存活对象多时效率低
- 应用场景:新生代回收(Eden和Survivor区)
- 优化:不按1:1划分,而是8:1:1(Eden:S0:S1)
标记-整理算法(Mark-Compact):
- 执行过程: 标记阶段:标记存活对象整理阶段:将存活对象移动到内存一端,清理边界外内存
- 优点:无内存碎片,不浪费空间
- 缺点:需要移动对象,STW时间较长
- 应用场景:老年代回收(Serial Old、Parallel Old)
分代收集算法(Generational Collection):
- 理论基础: 弱分代假说:大部分对象朝生夕死强分代假说:熬过多次GC的对象难以回收跨代引用假说:跨代引用相对少
- 新生代:使用复制算法,Minor GC频繁但快速
- 老年代:使用标记-清除或标记-整理,Full GC慢但频率低
- 晋升机制:对象年龄达到15或Survivor空间不足时晋升
增量收集算法(Incremental Collection):
- 核心思想:将GC过程分成多个小步骤,与应用交替执行
- 优点:减少单次STW时间
- 缺点:总体GC时间增加,线程切换开销
- 应用:CMS的并发标记阶段
三色标记算法(用于并发标记):
- 白色:未访问的对象
- 灰色:已访问但其引用未扫描完的对象
- 黑色:已访问且引用已扫描完的对象
- 问题:并发标记时可能漏标(对象消失问题)
- 解决:增量更新(CMS)或原始快照(G1)
4、Spring的IOC和AOP原理是什么
IOC控制反转原理:
- 核心思想:对象创建权交给Spring容器管理依赖关系由容器注入,而非对象自己创建降低耦合度,提高可测试性
- 实现机制:反射:通过Class.forName()和Constructor.newInstance()创建对象工厂模式:BeanFactory和ApplicationContext作为Bean工厂依赖注入:构造器注入、Setter注入、字段注入
- 容器初始化流程:步骤1:加载配置文件或扫描注解步骤2:解析Bean定义,生成BeanDefinition步骤3:注册BeanDefinition到BeanDefinitionRegistry步骤4:实例化Bean(反射调用构造器)步骤5:属性填充(依赖注入)步骤6:初始化Bean(调用init-method、@PostConstruct)步骤7:放入单例池(一级缓存)
- 三级缓存解决循环依赖:一级缓存singletonObjects:完整的Bean对象二级缓存earlySingletonObjects:早期暴露的Bean对象三级缓存singletonFactories:Bean工厂,用于生成代理对象解决原理:提前暴露未完全初始化的对象引用
AOP面向切面编程原理:
- 核心概念:切面(Aspect):横切关注点的模块化,如日志、事务连接点(JoinPoint):程序执行的某个点,如方法调用切点(Pointcut):匹配连接点的表达式通知(Advice):在切点执行的代码,分为前置、后置、环绕等织入(Weaving):将切面应用到目标对象的过程
- 实现方式:JDK动态代理: 要求:目标对象必须实现接口原理:Proxy.newProxyInstance()生成代理类调用:InvocationHandler.invoke()拦截方法CGLIB动态代理: 要求:目标对象不能是final类原理:通过字节码技术生成子类调用:MethodInterceptor.intercept()拦截方法选择策略:有接口用JDK,无接口用CGLIB
- 织入时机:编译期织入:AspectJ编译器修改字节码类加载期织入:类加载时修改字节码运行期织入:Spring AOP采用,通过动态代理实现
- 通知类型:@Before:方法执行前@After:方法执行后(无论是否异常)@AfterReturning:方法正常返回后@AfterThrowing:方法抛异常后@Around:环绕通知,可控制方法是否执行
- 实际应用场景:事务管理:@Transactional注解权限校验:@PreAuthorize注解日志记录:统一记录方法入参出参性能监控:统计方法执行时间异
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经

查看17道真题和解析