启动瞬间:内存里的混乱派对 JVM 启动的时候,实际上是一场有点吵的派对。

起初想到的肯定是那个著名的“新句柄表”,也就是 JHotSpot 出来的那个玩意儿。它干的活儿是把那些之前操作过的字段,比如对类做了某种修改,给重新分配个新地址。

这时候内存里也就只有它自己,占得挺小,也就 64 字节左右。但这还没完,紧接着就是 JNI 层了。 有时候你写代码,直接绑定了某个类的静态方式,这时候 JVM 得仔细想想,是不是确实能直接调用,还是得绕个弯。

要是没难题,那就直接去加载那个类,把对应的字段和参数塞进堆里。

这时候堆里还少东西,但早就堆得满满当当了。

不过这里有个小插曲,就是那些已经加载好的类,它们自己的元数据(比如访问权限、方式表)也得存有堆里。

这时候堆的内存占用就启动慢慢变大了。 接着到了代理类的处理环节。

要是是一般/平平对象,JVM 得去生成一个字节码文件,这个过程实际上挺耗资源,特别是代理类多的时候。

这时候内存里的对象数量就爆炸了,每个对象都有类加载器、元数据、方式表、字段和参数表,还有静态方式调用的静态代理对象什么的。

这时候堆的内存占用可能会达到几百兆就连上 gigabytes,彻底不一样了。最终才是那个最显眼的线程堆。 这就是著名的“新句柄表”带来的压力,它让堆内存瞬间暴涨。

这时候 JVM 得想办法清理,不然整个系统就会爆内存。便它会在堆的顶部设个内存块,专门用来回收那些不再需求的数据。

比方说,要是某个类的字段不再需求了,要么方式表也没用了,JVM 就会把它从堆里删掉,释放出来。

这时候堆的内存占用就降下来了。

要是堆里还有垃圾,JVM 会持续从堆顶往下找,尽量把内存回收干净利落,直到把堆顶那块内存块移走。 这时候你看到 JVM 启动时的堆内存,实际上全是这个“新句柄表”压上去的。它把之前加载过的类,一个个地给重新分配了内存,哪怕只是略微改个字段,也要重新分配。

这时候堆里不仅有对象,还有方式表、元数据,就连代理类的各种组件,每一种都有各自的开销。

要是堆里满了,JVM 就得找地方去回收那些不再需求的对象。 垃圾回收:清理懒汉与老鬼 JVM 启动过程中,要是堆被新句柄表撑爆了,它会启动干活了。

这时候它会在堆里设个位置作为回收站,专门用来存放那些暂时不需求的对象。

这时候堆顶那块内存块就是回收站,目前的对象都得倒腾到那里去。 回收是啥呢?就是对象丢了,内存就释放了,对象重新回到堆顶那块空地上,预备被下一个对象占用。

这时候就要想到垃圾回收器了。JVM 里最经典的就是引用计数法,但这个玩意儿有个毛病,就是只能处理单例,对多例对象分析起来忒复杂,故此 JVM 里用的是标记-清除要么标记-整理(标记 - 整理)法。 标记 - 清除法有点粗暴,先把所有对象都标记上,然后让回收器把它们一个个删掉。

这时候对象就彻底没了,内存就释放了。

可是这样有个难题,对象删了空了,内存碎片就形成了。

这时候要是下一个对象要进来,它可能得从碎片堆里找个位置,寻址的时候挺好办出错,故此 JVM 更喜爱用标记 - 整理法。 标记 - 整理法会把所有需求回收的对象先标记出来,然后再把一堆需求回收的对象搬到堆顶的回收站,最终把这些对象从回收站里按顺序搬出来。

这时候对象就全体释放了,内存碎片也被清理干净利落了。

这就好比你整理房间,把所有不需求的东西都找出来,再按顺序一个个收拾好,房间就干净利落了。 这时候 JVM 重点处理的对象类型呢?起初是懒汉根本类型。

比如 static final 字段,要是是个根本类型,JVM 会直接分配,不会去初始化,故此不需求回收。但要是是懒汉对象,比如 static final 的自定义类对象,JVM 就得先初始化,这时候内存占用就起来了。

要是初始化过程中黄了了,JVM 就得回滚,这时候内存占用可能就降下来了。 对于一般/平平对象,JVM 会分配它们,但这时候没有垃圾回收。

只有当对象被标记为能够回收,且没有引用指向它,JVM 才会在标记 - 整理阶段,把它搬到回收站,然后释放内存。

这时候要是你发现堆内存突然变小了,可能就是出于 JVM 在清理这些不再需求的对象。 垃圾收集:清理活跃与尸体 这时候 JVM 在处理垃圾回收的时候,会重点关切两类对象:活跃对象和尸体。活跃对象就是还在用的,正在被访问的,这时候它们肯定还在堆里,不会被回收。而尸体就是已经被触发回收机制,但还没有真正释放掉的对象,就是那些在回收站里排队等着搬出来的对象。 这时候要是 JVM 检测到某个对象被回收了,但它的引用还在,那这个对象就不能释放,务必暂时留在堆里,防止其他对象再引用它。

这时候 JVM 会先把这个对象重新分配一个新的位置,然后标记为活跃对象。

这时候要是 JVM 检测到某个对象被回收了,但它的引用还在,那这个对象就不能释放,务必暂时留在堆里,防止其他对象再引用它。

这时候 JVM 会先把这个对象重新分配一个新的位置,然后标记为活跃对象。 这时候要是 JVM 检测到某个对象被回收了,但它的引用还在,那这个对象就不能释放,务必暂时留在堆里,防止其他对象再引用它。

这时候 JVM 会先把这个对象重新分配一个新的位置,然后标记为活跃对象。 这时候要是 JVM 检测到某个对象被触发回收了,但它的引用还在,那这个对象就不能释放,务必暂时留在堆里,防止其他对象再引用它。

这时候 JVM 会先把这个对象重新分配一个新的位置,然后标记为活跃对象。 这时候要是 JVM 检测到某个对象被触发回收了,但它的引用还在,那这个对象就不能释放,务必暂时留在堆里,防止其他对象再引用它。

这时候 JVM 会先把这个对象重新分配一个新的位置,然后标记为活跃对象。 性能指标与调优:让系统更“宁静” 这时候 JVM 的性能指标如何调呢?要是堆内存不够用了,JVM 会报警,提示你增添堆内存。

这时候你能够试试削减 GC 的频率,要么增添新生代和老年代的比例。

比方说,增添新生代的比例,能够削减老年代的大小,进而削减老年代的大小。

这时候要是堆内存不够用了,JVM 会报警,提示你增添堆内存。 这时候要是堆内存不够用了,JVM 会报警,提示你增添堆内存。

这时候你能够试试削减 GC 的频率,要么增添新生代和老年代的比例。

比方说,增添新生代的比例,能够削减老年代的大小,进而削减老年代的大小。 这时候要是堆内存不够用了,JVM 会报警,提示你增添堆内存。

这时候你能够试试削减 GC 的频率,要么增添新生代和老年代的比例。

比方说,增添新生代的比例,能够削减老年代的大小,进而削减老年代的大小。 这时候堆内存的使用情况就挺清楚了。

要是堆内存不够用了,JVM 会报警,提示你增添堆内存。

这时候你能够试试削减 GC 的频率,要么增添新生代和老年代的比例。

比方说,增添新生代的比例,能够削减老年代的大小,进而削减老年代的大小。 这时候堆内存的使用情况就挺清楚了。

要是堆内存不够用了,JVM 会报警,提示你增添堆内存。

这时候你能够试试削减 GC 的频率,要么增添新生代和老年代的比例。

比方说,增添新生代的比例,能够削减老年代的大小,进而削减老年代的大小。 总结:JVM 的运行原理实际上是一个挺长的故事,从启动时的混乱派对,到垃圾回收时的清理工作,再到性能指标的调整。每个阶段都需求仔细维护,确保整个系统能稳定运行。厂商们一直在研究如何优化这些过程,让 JVM 变得更高效、更稳定。希望这些内容能帮你更深入地理解 JVM 的内部 workings。