大家好,我是二哥呀。
真热闹啊,小红书、美团、京东接连开奖,就连腾讯这个海王也开了一小撮。
有同学爆料说,腾讯没有发意向直接打电话开了,后端开发,给了 31kx15,还有股票和签字费,算下来逼近 60 万了。算是非常有诚意的薪资了。
有鹅选鹅,无鹅延毕。
能拿到腾讯 offer 的,手头上一般都会有同等级的其他 offer,如果薪资上和腾讯拉不开差距,(基本)都会一选腾讯。
- 产品岗,开了 23k,学历没说,有 5 万的签字费,没有 a,ieg,算是很满意了。
- 后台,硕士 985,开了 24k,直接拒绝了,这种情况肯定是和其他家 offer 差距有点大,至少 10 万年包起步。
- 后台开发,31k,有 3 万的股票和 5 万的签字费,还有项目分红。
从我掌握的情况来看,鹅厂开的不多,星球里不少球友都还在翘首以盼。
接下来,我们就来看看腾讯的面试强度,27 届打算冲鹅厂的同学可以估个提前量,尤其是寒假前的日常实习和春节后的暑期实习一定要把握住。
鹅厂很大一部分 HC 都会给到暑期转正,并且会评估有没有多段实习经历。
以下是 26 届鹅厂秋招正式面经,Java 后端二面,我已收录在 Java 面试指南专栏里,我把答案完整复刻分享给大家。
八股带背-腾讯篇
类的加载到垃圾回收整个底层原理讲一遍?
JVM 把 Class 文件中描述类的数据结构加载到内存中,并对数据进行校验、解析和初始化,最终转化成可以被 JVM 直接使用的类型,这个过程被称为类加载机制。
一旦类被初始化完毕,就可以创建对象、调用对象的方法,对象是在堆内存里被分配的。
垃圾回收就是对堆内存中已经死亡的或者长时间没有使用的对象进行清除或回收。
JVM 在做 GC 之前,会先搞清楚什么是垃圾,什么不是垃圾,通常会通过可达性分析算法来判断对象是否存活。
在确定了哪些垃圾可以被回收后,垃圾收集器(如 CMS、G1、ZGC)要做的事情就是进行垃圾回收,可以采用标记清除算法、复制算法、标记整理算法、分代收集算法等。
技术派项目使用的 JDK 8,采用的是 CMS 垃圾收集器。
类加载谁来执行?类加载器是什么东西,和进程的关系?Java虚拟机是什么东西,和进程的关系
类加载由类加载器来执行,JVM 有三个重要的类加载器:启动类加载器、扩展类加载器、应用类加载器。它们遵循双亲委派机制。
类加载器不是一个独立的进程,而是 JVM 进程内部的一个组件。当 JVM 启动时,会在内存里创建几个类加载器实例,这些类加载器的作用就是根据类名去找到对应的 .class 文件,读取字节码内容,然后交给 JVM 进行解析和初始化。
JVM 本质上就是一个进程。当我们执行 java -jar application.jar 命令时,操作系统会创建一个名为 JVM 的进程。这个进程在内存里运行着一个虚拟机,这个虚拟机有自己的指令集、内存模型、执行引擎等等。
如果我们要执行hello world,那虚拟机干了什么呢?谁把字节码翻译成机器码,操作时机是什么?Java虚拟机是一个执行单元吗
当我们写好一个 HelloWorld 程序,编译成 .class 文件后,执行 java HelloWorld 这条命令时,操作系统会创建一个 JVM 进程,JVM 进程启动起来后,会先初始化运行环境,包括创建类加载器、初始化内存空间、启动垃圾回收线程等等。
接下来,JVM 的类加载器会去找 HelloWorld.class 这个文件,读取它的字节码内容。
JVM 的执行引擎会将这些字节码翻译成机器码,有两种方式:
- 第一种是解释执行。JVM 的解释器会逐条读取字节码指令,然后把它翻译成机器码执行。这个过程是动态的,每次执行都要翻译一遍。所以解释执行的速度相对较慢。
- 第二种是即时编译。JVM 里有一个 JIT 编译器,他发现某段代码被频繁执行(比如循环里的代码或者热点方法)时,JIT 编译器就会把这段字节码直接编译成本地机器码并缓存到 codeCache 中,下次执行的时候不用再一行一行的解释,而是直接执行缓存后的机器码,执行效率会大幅提高。
至于执行的时机,解释器的执行是立即的,当 JVM 启动后,就开始解释执行字节码了。JIT 编译器的执行时机是延后的,它需要先监控哪些代码是热点代码(通常需要执行数千次或者数万次),然后才会启动编译。
操作系统层面,JVM 进程是一个执行单元,由操作系统调度执行。
Java虚拟机和操作系统的关系到底什么,假如我是个完全不懂技术的人,举例说明让我明白
想象操作系统是一个大剧院。这个剧院有舞台、灯光、音响、观众座位等各种资源。剧院的管理员负责分配这些资源,决定谁在什么时候用哪个舞台。
现在,一个话剧团想在这个剧院里演一场就在河南。话剧团就向剧院管理员说:"我需要租用你的舞台、灯光、音响,从下午 2 点到 5 点。"剧院管理员就给他们分配了一个舞台和相关资源。
操作系统是底层,它直接控制硬件资源,比如 CPU、内存、磁盘、网络等。操作系统的工作就是把这些硬件资源分配给各个应用程序使用。
JVM 是运行在操作系统上的一个应用程序。JVM 本身是一个进程,也就是说,从操作系统的角度看,JVM 就是一个普通的应用程序,并没有什么特别的。操作系统不知道也不关心 JVM 里面运行的是什么,它只是按照进程管理的规则来管理 JVM 这个进程。
一个操作系统有两个Java程序的话,有几个虚拟机?有没有单独的JVM进程存在?启动一个hello world编译的时候,有几个进程
1、每一个 Java 程序都需要一个独立的 JVM 进程来运行,两个 Java 程序就需要两个 JVM。这两个 JVM 进程是完全独立的,互不干扰。
2、JVM 不存在什么公共的、被所有 Java 程序共享的进程。每一个 Java 程序都必须启动自己的 JVM 进程。
3、如果只是编译,不运行,至少有两个进程。一个是 javac 的编译器进程。当执行 javac HelloWorld.java 时,操作系统会创建一个 javac 进程,这个进程会读取 .java 源文件,生成 .class 字节码文件。完成后 javac 进程就退出了。另一个是当前的 shell 进程,运行 javac 进程的终端,这是一直存在的。
JVM什么时候启动 比如执行一条Java命令的时候对应一个进程,然后这个JVM虚拟机到底是不是在这个进程里面,还是说要先启动一个JVM虚拟机的进程
当执行 java HelloWorld 这条命令的时候,操作系统就会创建 JVM 进程。没有"先启动 JVM"然后"再运行程序"这种操作。而是一步到位:操作系统启动了 JVM 进程,JVM 进程同时运行 Java 程序。
垃圾回收机制的时机?能手动触发垃圾回收吗?垃圾回收会抢占业务代码的CPU吗?
1、JVM 会监控堆内存的使用情况,当内存快要不够用时,就会自动触发垃圾回收。
2、理论上能手动触发垃圾回收,JVM 提供了 System.gc() 这个方法,可以手动调用它来触发垃圾回收,但一般不建议这么做。
3、垃圾回收会抢占 CPU,垃圾回收时会触发 STW,暂停所有业务线程。Minor GC 的 STW 时间较短,Major GC 的 STW 时间较长。
垃圾回收算法简单说说?垃圾回收机制的stop the world存在于哪些时机
垃圾收集算法主要有三种,分别是标记-清除算法、标记-复制算法和标记-整理算法。
说说标记-清除算法?
标记-清除算法分为两个阶段:
- 标记:标记所有需要回收的对象
- 清除:回收所有被标记的对象
优点是实现简单,缺点是回收过程中会产生内存碎片。
说说标记-复制算法?
标记-复制算法可以解决标记-清除算法的内存碎片问题,因为它将内存空间划分为两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后清理掉这一块。
缺点是浪费了一半的内存空间。
说说标记-整理算法?
标记-整理算法是标记-清除复制算法的升级版,它不再划分内存空间,而是将存活的对象向内存的一端移动,然后清理边界以外的内存。
缺点是移动对象的成本比较高。
哪些阶段会触发 STW?
STW 是 JVM 为了垃圾回收而暂停所有用户线程的机制。
总结一下:在标记阶段,STW 是必需的。无论是哪种垃圾回收算法,标记阶段都需要准确找出所有存活对象。如果标记的时候业务线程还在运行,就会标记不准确。因为业务线程可能会在 GC 标记某个对象为垃圾的同时,访问这个对象。为了避免这种冲突,标记阶段必须暂停所有业务线程。
在清除阶段,是否需要 STW 取决于算法的实现。
如果使用的是复制算法或标记-整理算法(需要移动对象),那就必须 STW。因为对象被移动后,所有指向这个对象的引用都要更新,这个过程中不能让业务线程访问这些对象。
如果使用的是标记-清除算法(不移动对象),理论上可以不 STW,业务线程和 GC 线程可以并发执行。
对于新生代的垃圾收集器 Serial 和 ParNew,整个垃圾回收过程都会触发 STW,但因为年轻代小,通常很快(几毫秒到几十毫秒)。
CMS 在此基础上进行了改进,通过并发执行来减少 STW 时间,会在初始标记和重新标记阶段触发 STW。
G1 采用的是增量回收的思想,不是一次性把整个堆都清理,而是把堆分成很多小的区域,每次只清理一部分区域。这样就可以分散 STW 的时间,单次 STW 的时间就短了。
ZGC 采用了并发整理的技术,可以在应用线程运行的同时进行对象的整理和移动,几乎消除了 STW 时间。ZGC 的 STW 时间通常在 10 毫秒以内。
垃圾回收中的计算Region的时候怎么和业务代码并行执行
在 G1 中,堆被划分成很多个大小相等的区域,每个区域叫做 Region。G1 的工作方式是,每次不对整个堆进行垃圾回收,而是计算哪些 Region 中的垃圾最多、回收效率最高,然后只回收这些 Region。
┌─────────────────────────────────────────────────┐
│ Region1 │ Region2 │ Region3 │ Region4 │ Region5 │
│ 年轻代 │ 年轻代 │ 老年代 │ 老年代 │ 空闲 │
│ 垃圾: 10%│ 垃圾: 5% │ 垃圾: 70%│ 垃圾: 15%│ │
└─────────────────────────────────────────────────┘
第一步,G1 会启动一些 GC 标记线程,这些线程和业务线程并发运行。业务线程执行代码,标记线程同时遍历堆中的对象,找出哪些对象是活的,哪些是垃圾。
这里需要用到写屏障(Write Barrier)技术,每当业务线程改变对象的引用时,写屏障就会记录下来:"有个引用被改变了"。GC 线程会根据这些记录来调整标记结果。
// 简化的写屏障概念示意
class Object {
Object ref;
// 业务线程执行:obj.ref = newRef;
// 实际执行过程:
public void setRef(Object newRef) {
// 原始操作
this.ref = newRef;
// 写屏障操作(由 JVM 自动插入)
writeBarrier(newRef); // 通知 GC:"这个对象的引用被改变了"
}
}
第二步,并发计算每个 Region 的垃圾占比。
第三步,G1 根据计算结果选择垃圾最多的几个 Region 进行回收。这时候才会触发 STW,暂停业务线程,对这些 Region 进行垃圾清理。
因为只清理少数几个 Region,而不是整个堆。所以 STW 的时间很短。
ending
一个人可以走得很快,但一群人才能走得更远。二哥的编程星球已经有 10300 多名球友加入了(马上涨价),如果你也需要一个优质的学习环境,戳链接 🔗 加入我们吧。这是一个 简历精修 + 编程项目实战(RAG 派聪明 Java 版/Go 版本、技术派、微服务 PmHub)+ Java 面试指南的私密圈子,你可以阅读星球专栏、向二哥提问、帮你制定学习计划、和球友一起打卡成长。
最后,把二哥的座右铭送给大家:没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。共勉 💪。
回复