大家好,我是二哥呀。
京东年终奖方案的出炉,意味着互联网大厂年终奖发放的第一枪已经打响。
讲句良心话,刚参加工作那几年,每年最大的期盼就是年终奖,平常的工资可能没啥感觉,但年终奖总给人一种沉甸甸的感觉。
不得不说,东哥还是有诚意的。
截图来自宫水三叶
我简单给大家拆解一下:
①、O 序列年前就发放,1 月 26 号刚好是春节前的倒数第二天。春节前能拿到一笔奖金,过年的味也能足不少。
O 序列一般代指基层员工,包括快递员、客服等;P 序列一般代指项目管理岗,要到年后 2 月份才能发放。
②、年终奖的结构分为固定+目标,目标能拉开多达 5 个月的月薪,对于大厂的员工来说,这可不是小数目。
-
绩效 A+:8 倍月薪 → 全年 20 薪(12 + 5 + 3 = 20)
-
-
-
8 倍月薪,意味着京东在 10 月份定下的目标,从 17 薪迈向 20 薪它实现了,采销部门更是要在 2026 年迈向 26 薪的新台阶。
同一个大厂,不同部门真的是天差地别,年终奖就是最好的体现
。京东零售和京东物流拿到 A+ 绩效的肯定是最多的两个部门,因为它们是京东的核心支柱产业。
照这个节奏,京东 25 届开出的 19 薪,基本上是能够落地的,拿到京东 offer 的小伙伴可以把心放到肚子里了。
接下来,我们就以《Java 面试指南》中收录的京东同学 9 技术一面为例,来看看京东的面试官都喜欢问哪些问题,如果打算实习、春招、秋招去京东的话,应该偏重哪个方向。
背八股就认准三分恶的面渣逆袭
-
-
三分恶面渣逆袭在线版:https://javabetter.cn/sidebar/sanfene/nixi.html
京东面经同学 9 技术一面
什么样的对象算作垃圾对象
Java 通过可达性分析算法来判断一个对象是否还存活。
通过一组名为 “GC Roots” 的根对象,进行递归扫“”无法从根对象到达的对象就是“垃圾”,可以被回收。
三分恶面渣逆袭:GC Root
问了垃圾回收算法,针对问了每个算法的优缺点
垃圾收集算法主要有三种,分别是标记-清除算法、标记-复制算法和标记-整理算法。
说说标记-清除算法?
标记-清除
算法分为两个阶段:
三分恶面渣逆袭:标记-清除算法
优点是实现简单,缺点是回收过程中会产生内存碎片。
说说标记-复制算法?
标记-复制
算法可以解决标记-清除算法的内存碎片问题,因为它将内存空间划分为两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后清理掉这一块。
三分恶面渣逆袭:标记-复制算法
缺点是浪费了一半的内存空间。
说说标记-整理算法?
标记-整理
算法是标记-清除复制算法的升级版,它不再划分内存空间,而是将存活的对象向内存的一端移动,然后清理边界以外的内存。
标记-整理算法
缺点是移动对象的成本比较高。
问了CMS垃圾回收器
三分恶面渣逆袭:Concurrent Mark Sweep收集器运行示意图
CMS 使用
标记-清除
算法进行垃圾收集,分 4 大步:
-
初始标记
:标记所有从 GC Roots 直接可达的对象,这个阶段需要 STW,但速度很快。
-
并发标记
:从初始标记的对象出发,遍历所有对象,标记所有可达的对象。这个阶段是并发进行的。
-
重新标记
:完成剩余的标记工作,包括处理并发阶段遗留下来的少量变动,这个阶段通常需要短暂的 STW 停顿。
-
并发清除
:清除未被标记的对象,回收它们占用的内存空间。
标记复制的标记过程和复制过程会不会停顿?
在标记-复制算法 中,标记阶段和复制阶段都会触发STW。
怎么理解并发和并行,然后顺势引申到并发垃圾回收器和并行垃圾回收器是怎么做到的:举了Parallel Old和CMS来问
-
并行:多核 CPU 上的多任务处理,多个任务在同一时间真正地同时执行。
-
并发:单核 CPU 上的多任务处理,多个任务在同一时间段内交替执行,通过时间片轮转实现交替执行。
三分恶面渣逆袭:并行和并发
就好像我们去食堂打饭,并行就是每个人对应一个阿姨,同时打饭;而并发就是一个阿姨,轮流给每个人打饭。
三分恶面渣逆袭:并行并发和食堂打饭
说说 Parallel Old 收集器?
Parallel Old 是 Parallel Scavenge 收集器的老年代版本,基于标记-整理算法实现,使用多条 GC 线程在 STW 期间同时进行垃圾回收。
三分恶面渣逆袭:Parallel Old收集器
说说 CMS 收集器?
CMS 在 JDK 1.5 时引入,JDK 9 时被标记弃用,JDK 14 时被移除。优点是垃圾回收线程和应用线程同时运行,停顿时间短,适合延迟敏感的应用,但容易产生内存碎片,可能触发 Full GC。
小潘:CMS
线程池的工作原理?
当应用程序提交一个任务时,线程池会根据当前线程的状态和参数决定如何处理这个任务。
-
如果线程池中的核心线程都在忙,并且线程池未达到最大线程数,新提交的任务会被放入队列中进行等待。
-
如果任务队列已满,且当前线程数量小于最大线程数,线程池会创建新的线程来处理任务。
空闲的线程会从任务队列中取出任务来执行,当任务执行完毕后,线程并不会立即销毁,而是继续保持在池中等待下一个任务。
当线程空闲时间超出指定时间,且当前线程数量大于核心线程数时,线程会被回收。
三分恶面渣逆袭:线程池执行流程
什么时候会执行拒绝策略?
当线程池无法接受新的任务时,也就是线程数达到 maximumPoolSize,任务队列也满了的时候,就会触发拒绝策略。
场景题:假如要查10万多条数据,用线程池分成20个线程去执行,怎么做到等所有的线程都查找完之后,即最后一条结果查找结束了,才输出结果?
为每个线程创建一个任务,使用 CountDownLatch 计数器控制线程同步。
每个线程任务完成后调用
countDown()
,主线程使用
await()
等待所有线程完成。
class DataQueryExample {
public static void main(String[] args) throws InterruptedException {
// 模拟10万条数据
int totalRecords = 100000;
int threadCount = 20;
int batchSize = totalRecords / threadCount; // 每个线程处理的数据量
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
// 模拟查询结果
ConcurrentLinkedQueue results = new ConcurrentLinkedQueue<>();
for (int i = 0; i int start = i * batchSize;
int end = (i == threadCount - 1) ? totalRecords : (start + batchSize);
executor.execute(() -> {
try {
// 模拟查询操作
for (int j = start; j results.add("Data-" + j);
}
System.out.println(Thread.currentThread().getName() + " 处理数据 " + start + " - " + end);
} finally {
latch.countDown(); // 线程任务完成,计数器减1
}
});
}
// 等待所有线程完成
latch.await();
executor.shutdown();
// 输出结果
System.out.println("所有线程执行完毕,查询结果总数:" + results.size());
}
}
怎么理解spring的bean
Bean 是指由 Spring 容器管理的对象,它的生命周期由容器控制,包括创建、初始化、使用和销毁。以通过三种方式声明:
注解方式
、
XML 配置
、
Java 配置
。
二哥的 Java 进阶之路:Bean 的声明方式
@Component 和 @Bean 的区别
@Component
是 Spring 提供的一个类级别注解,由 Spring 自动扫描并注册到 Spring 容器中。
@Bean
是一个方法级别的注解,用于显式地声明一个 Bean,当我们需要第三方库或者无法使用
@Component
注解类时,可以使用
@Bean
来将其实例注册到容器中。
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
依赖注入的时候,直接Autowired比较直接,为什么推荐构造方法注入呢
当使用
@Autowired
注解注入 Bean 时,IDEA 会提示“Field injection is not recommended”。
二哥的 Java 进阶之路:@Autowired
这是因为字段注入的方式:
-
不能像构造方法那样使用 final 注入不可变对象
-
隐藏了依赖关系,调用者可以看到构造方法注入或者 setter 注入,但无法看到私有字段的注入
在 Spring 4.3 及更高版本中,如果一个类只有一个构造方法,Spring 会自动使用该构造方法进行依赖注入,无需使用
@Autowired
注解。
技术派:构造方法注入
提到了byType,如果两个类型一致的发生了冲突,应该怎么处理
当容器中存在多个相同类型的 bean,编译器会提示
Could not autowire. There is more than one bean of 'UserRepository2' type.
@Component
public class UserRepository21 implements UserRepository2 {}
@Component
public class UserRepository22 implements UserRepository2 {}
@Component
public class UserService2 {
@Autowired
private UserRepository2 userRepository; // 冲突
}
这时候,就可以配合
@Qualifier
注解来指定具体的 bean 名称:
@Component("userRepository21")
public class UserRepository21 implements UserRepository2 {
}
@Component("userRepository22")
public class UserRepository22 implements UserRepository2 {
}
@Autowired
@Qualifier("userRepository22")
private UserRepository2 userRepository22;
或者使用
@Resource
注解按名称进行注入,指定 name 属性。
@Resource(name = "userRepository21")
private UserRepository2 userRepository21;
AOP相关原理说一下
AOP 是通过
动态代理
实现的,代理方式有两种:JDK 动态代理和 CGLIB 代理。
①、JDK 动态代理是基于接口的代理,只能代理实现了接口的类。
②、CGLIB 动态代理是基于继承的代理,可以代理没有实现接口的类。