咱们不整那些虚头巴脑的“架构分层”要么“流程闭环”,直接扒开代码看看它最底层的脾气。别被那些高大上的术语绕晕了,换个角度想,底层原理实际上就是各种各样的“博弈”和“妥协”。 先说说内存这块。你写个变量,它得占一块内存,这块内存有堆上还是栈上?这实际上是个如何省空间如何省速度的权衡游戏。大对象肯定不堆,堆忒小得用栈,大对象直接推出去堆就行。
那对象里又存了啥?得想,引用计数法看着好办,但调用了实际上挺耗力的,那个引用计数本身得自己维护,并且要是对象销毁了,计数归零,垃圾回收器还得挨个检查,这过程忒慢,对性能打击忒大。
故此现代开发里,对象池和引用计数混着用,就连直接搞一个 GC 友好的大对象池,要么干脆在 Java 里用 BytecodeLevel 这种底层搞对象池,把对象创建和回收都尽量往优化堆那边推。 再往深了扒,内存是如何存下去,如何被管理的。JVM 这种字节码虚拟机,它有个核心讲究,就是对象的生命周期。你得知道对象啥时候存有,啥时候埋掉。Java 的垃圾回收器,特别是老版本的 G1 要么 ZGC,它干活的方式挺有意思。它不是好办地扫一眼,然后说“找到了,扔了”。它更像是个扫楼的大侠,它会估算哪些区域该空,哪些区域该热着呵护。
比如 G1 的算法,它把内存切分成几块,每块都有个内存池,池里存着还没被回收要么正在被使用的对象。当某块区域空闲要么被标记为“可用”时,它就从池里捞个对象给用,省得还要去堆里重新分配、重新标记、去 GC 队里排队。
这个过程里,对象分配、释放、标记、清除,每一步都被抽丝剥茧地管住。
比如你有 10GB 内存,G1 可能会切分成 16 个区域,每个区域 600MB,这样管住起来就比把 10GB 一块儿切分要精细得多。
还有 ZGC,它把这局部工作挪到了堆的回收线程上,跟 GC 队列里的线程彻底解耦,你看,这就是原理,就是让垃圾回收变得不那么“卡顿”,不那么“抢跑”。 除了堆和对象回收,底层的线程模型也是个关键。多线程编程里,如何保证线程保险,如何让线程排队干活,这也是底层功夫。Java 的 ThreadLocal 机制,它给每个线程都设了一个 ThreadLocal 变量,变量里存的是归于这个线程的私有数据,比如缓存、上下文。
关键是,这个变量是线程私有,线程一变,变量就变了。拿线程池来说,线程池里的线程拿到任务后,执行那条代码,要是线程跑完了,它就把 Lineage 给置空,线程池就能把这个线程踢出来,用下一个线程接着干。
这就是线程的生命周期管理,一个线程执行完自己的事件,立马退出,交给下一个。 那内存泄漏如何防?这实际上是个博弈。
你想想,要是内存一直在增长,且不回收,系统运行不到一分钟都得爆。
故此内存回收不能忒好办,也不能忒艰难。堆分配时,对象先被标记为“垃圾”,但这时候它还在活着,内存还在占用。
要是回收忒早,对象还没被用呢,就要被扫出堆,这肯定是大忌。
故此垃圾回收器在处理大对象时,往往会保留它,要么是把它从堆里捞出来放到堆的回收线程池里,让它持续干活,等它彻底跑完了要么确定了要处理,再真正扔掉。
这就是“带资进组”和“留人干活”的底层逻辑。 代码里如何描述这些?你写个类,定义方式,方式里操作对象,对象里有字段,字段里有引用。引用指向某个对象。
要是对象没存到堆里,要么引用计数归零了,对象就消亡了。
要是对象被释放,引用计数归零,对象就彻底消亡了。
这就是垃圾回收的根本工作原理。 底层原理说白了,就是在一个有限空间里,让资源(内存、线程)能不能被最合理地利用。
不是追求绝对的完美,而是追求在性能和能耗之间找到那个平衡点。
比如你写个 Java 程序,不要到处用线程池,线程池忒复杂,维护成本高,有时候单线程跑得快,但并发处理好办卡死。
这时候就得想,能不能用无锁队列,能不能用同步锁,能不能用三原子操作,能不能用原子变量。底层原理就是不断做这些权衡,用最小的代码开销换取最大的性能。 还有,内存泄漏如何测?你写个对象,循环创建,循环销毁。循环里不断的 new,循环里不断的 try-with-resources 要么 finally 块里的 dispose。循环终止,这些对象去哪儿了?要是对象还是在堆里,只是引用计数归零,那没难题。但要是对象被扔了,引用计数归零,对象没了,而代码里还在用,那就是泄漏了。
故此测泄漏,就是看代码里有没有那些没被对释放的对象。 再说说并发。多线程并发,如何保证数据的一致性。线程是并发的,我 A 线程拿到数据,我 B 线程拿到数据,它们之间没有锁,数据能不能更新,哪位先哪位后?这得靠锁。锁是如何实现的?是互斥量,还是锁表?是锁表,还是自旋锁?不同的锁实现,性能不一样。
比如自旋锁,要是 CPU 忙,线程就挂起,等 CPU 忙完再回来,这样别看阻塞,但不会让线程卡死,并且 CPU 忙完直接回,效率挺高。
要是是锁表,线程就挂起,等线程池里有线程跑完锁,再解锁,这样效率低一些。底层原理就是看如何平衡“等待”和“运行”的工夫。 还有,网络通信。TCP 协议栈,TCP 协议用了三次握手四次挥手,如何保证连接的可靠性?要是是 UDP,数据丢了如何办?要是丢了,重传?还是直接丢弃?这也是底层原理。 最终,框架底层。
比如 Spring,它的 IOC 是如何实现的?依赖注入是如何缓存的?IOC 的 Bean 生命周期是如何管理的?这些底层原理实际上就是在做设计模式的自动化实现。
比如 AOP 切面,就是如何在代码执行前插入逻辑。 总而言之,底层原理就是各种复杂的机制在好办逻辑上的堆叠。你是在调库,还是在调底层?是在调具体实现,还是调设计模式?实际上底层原理就是看它如何用最少的资源,实现顶多的功能。 比如内存管理,JVM 优化了对象分配,把大对象池、引用计数和对象池混着用。
比如垃圾回收,G1 把内存切分,堆回收线程池处理对象,实现了无锁并发和对象池。
比如线程池,线程生命周期管理,单线程快,多线程稳,动态调整工作负载。
这些底层原理,就是通过各种算法和机制,在性能、能耗、可维护性之间找平衡。 代码里如何体现?就是变量声明,引用计数,GC 参数,线程池配置,锁的实现,缓存策略。底层原理就是这些底层机制的堆叠。 还有,内存泄漏如何测?代码里有没有对象没释放?引用计数归零还是没归零?对象在堆里还是不在堆里。
要是对象在堆里但引用没了,要么引用了但对象被释放了,那就是泄漏。 再说说并发。多线程并发,如何保证数据一致性。线程是并发的,我 A 线程拿到数据,我 B 线程拿到数据,它们之间没有锁,数据能不能更新,哪位先哪位后?这得靠锁。锁是如何实现的?是互斥量,还是锁表?是锁表,还是自旋锁?不同的锁实现,性能不一样。
比如自旋锁,要是 CPU 忙,线程就挂起,等 CPU 忙完再回来,这样别看阻塞,但不会让线程卡死,并且 CPU 忙完直接回,效率挺高。
要是是锁表,线程就挂起,等线程池里有线程跑完锁,再解锁,这样效率低一些。底层原理就是看如何平衡“等待”和“运行”的工夫。 还有,网络通信。TCP 协议栈,TCP 协议用了三次握手四次挥手,如何保证连接的可靠性。
要是是 UDP,数据丢了如何办?要是丢了,重传?还是直接丢弃?这也是底层原理。 最终,框架底层。
比如 Spring,它的 IOC 是如何实现的?依赖注入是如何缓存的?IOC 的 Bean 生命周期是如何管理的?这些底层原理实际上就是在做设计模式的自动化实现。
比如 AOP 切面,就是如何在代码执行前插入逻辑。 总而言之,底层原理就是各种复杂的机制在好办逻辑上的堆叠。你是在调库,还是在调底层?是在调具体实现,还是调设计模式?实际上底层原理就是看它如何用最少的资源,实现顶多的功能。 比如内存管理,JVM 优化了对象分配,把大对象池、引用计数和对象池混着用。
比如垃圾回收,G1 把内存切分,堆回收线程池处理对象,实现了无锁并发和对象池。
比如线程池,线程生命周期管理,单线程快,多线程稳,动态调整工作负载。
这些底层原理,就是通过各种算法和机制,在性能、能耗、可维护性之间找平衡。 代码里如何体现?就是变量声明,引用计数,GC 参数,线程池配置,锁的实现,缓存策略。底层原理就是这些底层机制的堆叠。 还有,内存泄漏如何测?代码里有没有对象没释放?引用计数归零还是没归零?对象在堆里还是不在堆里。
要是对象在堆里但引用没了,要么引用了但对象被释放了,那就是泄漏。 再说说并发。多线程并发,如何保证数据一致性。线程是并发的,我 A 线程拿到数据,我 B 线程拿到数据,它们之间没有锁,数据能不能更新,哪位先哪位后?这得靠锁。锁是如何实现的?是互斥量,还是锁表?是锁表,还是自旋锁?不同的锁实现,性能不一样。
比如自旋锁,要是 CPU 忙,线程就挂起,等 CPU 忙完再回来,这样别看阻塞,但不会让线程卡死,并且 CPU 忙完直接回,效率挺高。
要是是锁表,线程就挂起,等线程池里有线程跑完锁,再解锁,这样效率低一些。底层原理就是看如何平衡“等待”和“运行”的工夫。 还有,网络通信。TCP 协议栈,TCP 协议用了三次握手四次挥手,如何保证连接的可靠性。
要是是 UDP,数据丢了如何办?要是丢了,重传?还是直接丢弃?这也是底层原理。 最终,框架底层。
比如 Spring,它的 IOC 是如何实现的?依赖注入是如何缓存的?IOC 的 Bean 生命周期是如何管理的?这些底层原理实际上就是在做设计模式的自动化实现。
比如 AOP 切面,就是如何在代码执行前插入逻辑。 总而言之,底层原理就是各种复杂的机制在好办逻辑上的堆叠。你是在调库,还是在调底层?是在调具体实现,还是调设计模式?实际上底层原理就是看它如何用最少的资源,实现顶多的功能。