南京莱斯-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圣经

全部评论
考虑多多吗,部门核心,hc多多,感兴趣点我主页了解详情哈
点赞 回复 分享
发布于 昨天 17:27 上海

相关推荐

评论
点赞
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务