Java后端开发 面试知识点复习(一)
某外包,主要针对简历提问
技术面
1.自我介绍
姓名+学校+专业,实习经历的项目简单介绍+负责工作的业务和技术简单介绍,个人优势(与JD匹配度)
2.实习项目及负责工作介绍
(1)AOP定义,实现过程,原理,用处,几种机制?
定义:AOP(面向切面编程)将哪些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。
将横切关注点(日志、事务、权限、监控)从业务逻辑中剥离,提高复用性和可维护性。
实现过程:定义切面(@Aspect)→ 定义切点(@Pointcut)→ 定义通知(@Before/@After/@Around等)。
原理:基于动态代理。
- 被代理类有接口,使用JDK动态代理,生产接口的代理类
- 被代理类无皆苦,使用CGLIB,生成子类字节码
几种机制(通知类型):@Before、@After、@AfterReturning、@AfterThrowing、@Around(最强大,可控制是否执行目标方法)。
Spring AOP的实现过程:
(a)容器在创建UserService的Bean时,发现它被切面匹配到了(比如切点表达式execution(* saveUser(..)))。
(b)不直接返回UserService对象,而是用动态代理包装它。
-
有接口 → JDK动态代理(
InvocationHandler+ 反射调用目标方法) -
无接口 → CGLIB(生成子类,也是用反射调用父类方法)
(c)调用userService.saveUser()时,实际调用的是代理对象的方法,代理内部会在调用目标方法的前后执行增强逻辑(通过反射调用@Before、@Around等方法)。
进阶复习:AspectJ(编译时织入) vs Spring AOP(运行时织入,性能稍低但更方便)。
(2)注解的核心机制 。IOC?
注解是一个继承了java.lang.annotation.Annotation的接口,通过反射读取元数据。
注解只是标记,本身没有逻辑。注解需要配合“解析器”才有意义,解析器通常依赖反射来读取注解。
IoC(控制反转):把对象的创建和依赖关系的管理交给Spring容器,而不是由对象自己new。
-
核心机制:利用反射+注解 实现容器
-
@Component/@Service/@Controller→ 扫描后注册到BeanDefinitionMap。 -
@Autowired/@Resource→ 容器通过反射注入依赖(前者按类型,后者按名称)。 -
底层:
BeanFactory/ApplicationContext负责实例化、属性填充、初始化、销毁。
-
Spring IoC容器启动时做的事情:
- 扫描指定包下所有类,通过反射读取类上的注解(
@Component、@Service等); - 对于每个带注解的类,通过反射实例化对象(
clazz.getDeclaredConstructor().newInstance()); - 再通过反射扫描每个对象内部的字段,如果字段上有
@Autowired,就通过反射把对应的依赖对象注入进去。
(3)反射。功能增强?
反射:运行时获取类的构造器、方法、字段(即使private也能拿到),并动态创建对象(clazz.newInstance())、调用方法(method.invoke(obj, args))、读写字段。
功能增强的列子:
AOP——通过反射调用目标方法,并在前后插入增强逻辑。
注解处理——扫描类上的注解,根据注解值动态生成Bean或改变行为。
ORM框架——根据实体类字段反射生成SQL并赋值。
对于IoC,AOP,反射,注解之间的关系还是有点混乱。
反射是底层能力(运行时窥探/操作类);
注解是元数据标记(贴在类/方法上的标签);
IoC利用反射+注解实现对象的创建与依赖注入;
AOP在IoC基础上,利用动态代理(底层也是反射)对Bean的方法进行增强。
(4)定时任务的用处,怎么实现的?
定时任务:是让代码在某个具体时间/每隔固定时间/周期性执行。
实现:Spring Boot注解 @Schedule。启动类加 @EnableScheduling,任务方法加 @Scheduled。
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@EnableScheduling // 开启定时任务
@Component
public class SpringScheduledDemo {
// 1. 固定间隔:每 5 秒执行一次
@Scheduled(fixedRate = 5000)
public void task1() {
System.out.println("固定间隔任务");
}
// 2. Cron 表达式:每天凌晨 0 点执行
@Scheduled(cron = "0 0 0 * * ?")
public void task2() {
System.out.println("每日凌晨执行任务");
}
}
// Cron 表达式(最常用):秒 分 时 日 月 周 年(可选)
// 优点:简单、无依赖、整合Spring、支持cron
// 缺点:不支持分布式(集群部署会重复执行)
底层:
线程休眠+轮询:后台开一个独立线程,循环判断时间,到点就执行任务
时间轮算法(高级方案):用环形队列管理任务,高效调度大量定时任务
项目是单体还是分布式?
单体。
我们是部署了几套,生产环境有好几套,如果有一套有问题,或者说有一套的一个节点能处理的请求是有限的,多几个节点可以处理更多的用户请求。如果多节点,怎么确保定时任务的一致性——分布式定时任务调度?
多节点问题:所有节点同时执行同一任务,导致数据重复处理、资源竞争。
解决方案:
分布式锁:如Redis的SET NX EX,抢到锁的节点才执行
分布式调度框架:XXL-JOB、Elastic-Job、SchedulerX。它们通过数据库/ ZK / Raft选举主节点,只有主节点执行定时任务。
@Scheduled+@RedisLock自定义,配合Redission实现看门狗自动续期。
(5)回收物品是可以退款到微信支付吗,可以讲讲微信支付怎么实现吗?
-
微信支付基本流程:统一下单--支付--回调--退款
-
退款实现关键点:
-
调用微信支付退款API(
secapi/pay/refund),需要商户证书(双向SSL)。 -
参数:
out_trade_no(商户订单号)、out_refund_no(退款单号,唯一)、refund_fee、total_fee。 -
签名:构造参数 → 按字典序排序 → MD5/HMAC-SHA256 → 放入请求。
-
微信会异步发送退款结果通知(可选),但退款通常不需要等待回调,可先更新本地退款状态为“处理中”,然后通过主动查询或回调确认。
-
-
安全注意:退款涉及资金,必须做幂等处理(同一退款单号多次调用只能退一次),用数据库唯一约束或分布式锁。
(6)redis分布式锁保证幂等性,用在哪里,怎么用,锁的时间怎么设置,大概几秒,如果过期业务还没执行完怎么办(Redission看门狗机制自动续期)
是用java自带的锁(有哪些 怎么用)还是用redis锁(怎么用)。单体用自带锁就可以。
-
用在哪里:如退款接口、下单接口、MQ消费端,防止重复提交/重复处理。
-
怎么用:
String lockKey = "lock:refund:" + refundNo; Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", Duration.ofSeconds(30)); if (Boolean.TRUE.equals(locked)) { try { // 业务逻辑 } finally { redisTemplate.delete(lockKey); } } -
锁时间设置:一般比业务平均执行时间长一点,比如10~30秒。但无法绝对避免业务超时。
-
过期业务未完成怎么办:
-
错误做法:锁时间设很长(影响性能,死锁风险)。
-
正确做法:用Redisson的看门狗机制(默认30秒,业务未完成自动续期,每
lockWatchdogTimeout/3检查一次)。 -
手动续期:用
while循环判断剩余时间,接近过期时expire延长。
-
-
与Java自带锁对比:
-
单体应用:
synchronized、ReentrantLock(JVM级,无法跨进程)。 -
分布式应用:必须用Redis锁或ZooKeeper锁。(Redis锁不是100%安全的(主从切换可能丢锁),极高要求可用RedLock或ZooKeeper)
-
(7)如果做的模块发展性能有问题,要优化性能,有什么思路?
a. 排查瓶颈:先压测(JMeter)+ APM(SkyWalking/Pinpoint)+ 日志分析,定位是CPU、内存、IO还是数据库慢。
b. 数据库优化:
慢SQL → 加索引、改写SQL、分页优化。
热点数据 → 加Redis缓存(注意穿透/击穿/雪崩)。
读写分离、分库分表。
c. 代码层面:减少不必要循环、批量操作(如批量insert)。使用线程池处理异步任务。避免大对象频繁GC。
d. 架构层面:加消息队列削峰(如RabbitMQ/Kafka)。 静态资源走CDN。 微服务拆分、水平扩容。
e. JVM调优:调整堆内存、GC算法(如G1)、元空间等。
3.个人项目
(1)分表一般怎么分,分表策略,在项目中你怎么分的?
-
为什么分表:单表数据量过大(超过500万~1000万)时,索引效率下降,写入锁竞争严重。分表后要解决跨表查询、全局ID生成(雪花算法)等问题。
-
分表策略:
-
垂直分表:将大字段或访问频率低的列 拆分到另一张表。
-
水平分表:按某种规则 将行分散到多张结构相同的表。
-
常用策略:取模(
user_id % 8)、范围(按时间/ID区间)、哈希(一致性哈希)。 -
选择依据:业务查询多数带哪个字段(如订单按
user_id分)。
-
-
项目中我用的不太对,不应该叫分表,简历要改。
扩展:
雪花算法(Snowflake)一套分布式全局唯一ID生成算法,用来在分布式系统里生成不重复、趋势递增、纯数字的ID,是Java后端、微服务里最常用的主键ID方案。
在分布式、多节点、高并发场景下,数据库自增ID——集群多台机器会重复;UUID——太长、无序、索引性能差、字符串存储浪费空间;号段模式——需要额外维护发号器(一个专门发编号的服务)。
雪花算法:全局唯一不重复;趋势递增,数据库索引友好;纯数字,长度固定,性能好;不依赖数据库,本地生成,高并发;可反向解析出 时间、机器、序列号。
缺点:强依赖机器时钟——服务器时间回拨会导致ID重复或生成失败,实际项目会拒绝生成,等待时间追上,改用改良算法;不是绝对连续,只是趋势递增。
使用场景:分布式系统订单 ID,用户 ID、商品 ID。日志 ID、消息 ID。高并发表主键。微服务之间唯一标识。Long id = IdWorker.getId();//框架内置了方法
标准的雪花算法生成一个时间+机器+序列号 拼成的 64bit的long型数字
(2)openclaw有没有玩过,项目中这个AI怎么用的
了解过,没玩过。
-
通过HTTP API,调用本地大模型的接口。
-
调用方式:
RestTemplate或WebClient发送POST请求,携带用户问题,模型返回答案。 -
如果需要流式输出(打字机效果),使用
SseEmitter(Spring)或WebSocket。
4.八股
(1)java内存模型一般分什么区域?
-
堆(Heap):所有对象实例、数组,线程共享,GC主要区域。分新生代和老年代。
-
方法区 / 元空间(Metaspace / PermGen):类信息、常量、静态变量、JIT编译后的代码。JDK8后改为元空间(本地内存)。
-
虚拟机栈(VM Stack):每个方法一个栈帧(局部变量表、操作数栈、动态链接、返回地址),线程私有。
-
本地方法栈:
native方法。 -
程序计数器:当前线程执行的字节码行号,线程私有。
(2)垃圾回收器,垃圾回收机制,jdk不同垃圾回收不同,项目用的什么版本的jdk?
-
垃圾回收机制:可达性分析(GC Roots)→ 标记不可达对象 → 回收(标记清除/复制/标记整理)。
-
常见垃圾回收器:
-
Serial / ParNew / Parallel Scavenge(年轻代)
-
CMS / Serial Old / Parallel Old(老年代)
-
G1(JDK9+默认):分区+可预测停顿。
-
ZGC(JDK11+):低延迟(<10ms),适合大堆。
-
Shenandoah。
-
-
项目用的JDK版本:比如JDK 8 → 默认用
Parallel Scavenge + Parallel Old;JDK 11 → 默认G1。
(3)线程的创建方式。线程池参数,线程的创建与销毁。线程池的好处,逻辑?
线程池是一种用于管理和复用线程的机制,它允许在应用程序中创建一组可用线程,并在需要执行
任务时将任务分配给这些线程。 优化线程的创建、销毁和管理,以提高多线程应用程序的性能和效率。
-
创建方式:
-
继承
Thread,重写run()。 -
实现
Runnable,传给Thread。 -
实现
Callable+FutureTask(有返回值,JDK>=1.5)。 -
线程池(推荐)。
-
>>>>>>>>>>>>>>>>>
自定义线程池的核心参数如下:
1. 核心线程数(corePoolSize):线程池中一直保持活动的线程数。可以使用corePoolSize方法
来设置。一般情况下,可以根据系统的资源情况和任务的特性来设置合适的值。
2. 最大线程数(maximumPoolSize):线程池中允许存在的最大线程数。可以使用
maximumPoolSize方法来设置。如果所有线程都处于活动状态,而此时又有新的任务提交,
线程池会创建新的线程,直到达到最大线程数。
3. 空闲线程存活时间(keepAliveTime):当线程池中的线程数量超过核心线程数时,如果这些
线程在一定时间内没有执行任务,则这些线程会被销毁。可以使用keepAliveTime和TimeUnit
方法来设置。
4. 阻塞队列(workQueue):用于存放等待执行的任务的阻塞队列。可以根据任务的特性选择
不同类型的队列,如LinkedBlockingQueue、ArrayBlockingQueue等。默认情况下,使用无
界阻塞队列,即LinkedBlockingQueue,但也可以根据需要设置有界队列。
5. 线程工厂(threadFactory):用于创建线程的工厂。可以通过实现ThreadFactory接口自定义
线程的创建逻辑。
6. 拒绝策略(rejectedExecutionHandler):当线程池无法接受新的任务时,会根据设置的拒绝
策略进行处理。常见的拒绝策略有 AbortPolicy、DiscardPolicy、DiscardOldestPolicy 和
CallerRunsPolicy。
线程池提交流程:
a.提交任务,判断是否达到核心线程数,没有则新建线程执行任务。
b.达到核心线程数,尝试放入任务队列。
(入队成功后,如果工作线程数为 0,会再创建一个非核心线程来消费队列任务)
c.队列满,判断是否达到最大线程数,没有则新建线程执行任务。
d.达到最大线程数,执行拒绝策略。
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
-
线程创建与销毁:
-
新任务提交时 → 核心线程数未满 → 新建线程。
-
队列满了 → 线程数<最大线程数 → 继续新建。
-
超出最大线程数 → 执行拒绝策略。
-
空闲线程超过
keepAliveTime且大于核心数 → 销毁。
-
-
线程池的好处:
-
减少线程创建销毁开销(复用)。
-
控制资源(限制并发数)。
-
提供定时、监控等管理能力。
-
5.反问:
1.想了解公司最小的一个技术团队的规模,负责的业务大概到哪里
3.项目迭代周期
2.对我有什么建议
hr面:
1.个人规划(纯技术还是技术管理),希望可以成长到什么
2.之前实习最大的收获
3.选择工作最注重的点
更多推荐




所有评论(0)