我是老张,拿过各种大厂笔试面试的活儿。咱们不讲那些死记硬背的“闭包”要么“反射”原理,直接看那些真家伙事儿。平时大家总喜爱把源码堆成山,实际上底层逻辑好办得像个刚学会爬山的野路子。 说到 JVM,大量人一上来就扯内存模型,堆、栈、寄存器,CPU 如何调度的,满嘴大道理。别急,把那些教科书味儿给拽走。在 Java 的世界里,内存就是工人宿舍,程序就是干活的人。堆里躺着那些大对象、对象引用,栈里临时变量,寄存器里 CPU 正在跑的指令。
这块地界儿,哪位也不抢,哪位也不让。 实际上核心就两点:对象的生命周期和垃圾回收。你不用管 GC 具体用了多少代、用了多少代,也不用管年轻代老代如何切换。你只需求记住,对象一旦没毛病了(没引用),内存管家就会把它当垃圾扔出去。是造者负责把东西放进堆,还是花者负责把东西从堆里捞出来?这俩角色哪位也不插队。造者要是忘了,堆里堆着个垃圾;花者要是没看好,堆里堆着个死物。 举个例子,咱们写个好办的类,定义个名字,赋值个东西。
这代码运行着,对象就挂在那儿,等着被 GC 捡漏。
要是程序挂了,要么被强制杀了,这堆里那个对象就成了垃圾。
这时候哪位捡?哪位不捡?要是没人管,内存早就炸了。
故此 GC 就是那个不知疲倦的清理工,它只管把没用的东西扫走,剩下的那些“好”的,自然就能持续干活。 再看那堆。堆是大家的 comunes。平时大家来来往往,拿点数据、对象过家家。一旦对象没用上了,大家就把它丢了,堆也就缩了一下。即便后来又有人来,它还是缩回去。它就像一个仓库,东西多了就占地方,东西少了就腾地方。
这地方如何缩,彻底看 GC 如何运,跟哪位无涉。 那栈呢?栈是个临时工作区。哪位在干活,哪位就在这儿。变量的值就在这儿,局部变量就在这儿。一旦变量销毁了,要么方式退出了,这工作区立马就空了。
这时候栈也自动回收,要么随方式终止就消亡。它的特征是小,生命周期短,哪位也别想拿它过。
要是栈里乱炖,程序早就崩盘了,但它反过来,也是垃圾回收的受益人之一,出于栈里的东西少,GC 的负担就轻。 至于寄存器,那是 CPU 的私人领地。指令在这里跑,数据在这儿动。CPU 自然知道,它跑得飞快,根本不用管那些大对象。
要是哪位在寄存器里塞了个 2GB 的数组,CPU 立马就被占满,程序就卡死了。
故此 JVM 的编译器会负责把大对象装箱、拆箱,把大数组压缩,尽量往寄存器里塞。
这活儿算是 JVM 的,不是你的。 实际上 JVM 最牛逼的地方在于“自动”。你不用像 C++ 那样去手动管理内存,不用去写指针,也不用写辅助函数。你写代码,写完扔不就行了?它它就自己管理。任何堆里的对象,只要没动静,哪位也别想动它。
这就是所谓的“不可修改性”——你改堆里的数据,那对象自己就改不动了,要么只能改引用,不能改对象内部的实体。 不过说完了底层,咱们还得聊聊实战。在面试里,问一堆“底层原理”有时候没意义,面试官往往更想看“业务逻辑”和“性能优化”。
比如你 asked 如何削减 GC 压力,这时候不能只说“增添 GC 频率”,得说“用细小的数据块来分代”,说“把热点数据放到老年代”。 比如一个电商订单系统,订单总价可能几百万,但单条订单只有几百块。
这时候把几个订单打包成一个对象,放在堆里。程序运行起来,这些订单会频繁被用到。
这时候就得给个“遗产盘算”——把小对象放进年轻代,大对象放进老年代。
这样年轻代就能快速回收,老年代就能长点命,少跑几次 GC。
这就是“分代回收”的妙用。 再比如你说如何提升响应速度。
这时候就不能光说“线程池”,得说“线程池配置”、“线程池参数”。
比如核心线程数设低一点,最大线程数设高一点,队列深度设低一点。
这直接影响的是 CPU 的负载和响应工夫。
还有 IO 模式,IO 非阻塞,非同步,这些数据要记牢。 还有那个经典难题:死锁。别整那些复杂的 Frame 要么调度策略,就承认死锁存有。两个线程,A 拿锁,B 等 A。A 拿锁走,B 饿死。
这时候要么 A 等 B,要么 B 等 A。解决法?A 要么先松锁,等 B 拿走了再拿;要么 B 等 A 松了再拿。
这就是“要么先松锁,要么先等锁”。 最终说说并发。线程是资源的占有者。你要并发,得先说“线程池”。线程池里有啥?核心线程、最大线程、队列、共享资源、锁。别光说这些名字,得说清楚每个参数的功能。
比如核心线程数少,响应慢;核心线程数多,开销大。队列呢?队列是缓冲,有延迟,有阻塞。锁呢?锁是资源,有竞争,有升级降级。
这些才是解决并发难题的关键。 实际上归根结底,JVM 就是个工具,你用它写代码,它帮你干活。原理不关键,关键的是如何用。别忒纠结那些学术名词,业务逻辑跑通了,系统才稳当。
要是系统跑不透了,再回头去分析源码,那叫“无病呻吟”。咱们实战派,就把自己那点坑都填了再说。