96SEO 2026-02-20 06:23 10
前言面试Java虚拟机内存模型垃圾收集器与内存分配策略虚拟机性能监控、故障处理工具总结

回想当年在老家屯子里就经常给我大爷添堵现在被撵出来了还是改不了给别人添堵的毛病我可能就是你们口中所说的最忍受不了的同事。
。
。
在一个晴朗的周日我来到了一个陌生的园区坐在陌生的会议室等待HR小姐姐去叫面试官此时我的心情和各位小伙伴一样五味杂陈担心面试官问的会不会很难问到我的知识盲区我该怎么办
此时一位英俊潇洒眼神犀利的面试官走了进来看到他那犀利、仿佛能看穿一切的眼神
我在想要不然一会就不要30k了要8k得了这个面试官一看就不好糊弄啊但是我想起来我来之前刚看了小奇的大厂面试题系列我已经完全学会了小奇的精髓我顿时就来了底气决定一会先要个80k尝尝咸淡
我没带现在彩印两块一张我简历五张每次面试都要花费十块我朋友说了还没工作就先让你掏钱的工作不要去。
此时面试官并没有叫保安而是从门后拿出了恭候我多时的棍子我瞬间怂了
我只好从我的双肩包中拿出了我从上午没有面试通过的其他公司面试官手中要回的简历上午的情形是这样的上午的面试官今天的面试就到这吧回去等通知吧我面试官你好如果贵公司不打算录取我的话能不能把我的纸质简历还给我我下午还有一家面试。
上午的面试官我说你的简历怎么皱皱巴巴原来你一直在循环利用啊这个症状出现多久了我半拉月了。
。
。
此时我的内心非常紧张紧张的并不是面试官把我问住而是我如果虚拟机这方面回答的太专业了面试官听不懂怎么办他如果不信我回答的怎么办此时我偷偷看了一下我藏在桌下的《深入理解Java虚拟机》如果他不信我就拿出书来和他对峙
我JVM虚拟机中有一个运行时数据区里面主要分为程序计数器、虚拟机栈、本地方法栈、堆、方法区
面试官嗯。
小伙子真是惜字如金啊能不能详细介绍一下这几个区域都是干什么的吗
1.程序计数器简单来说每一个线程在执行代码的时候执行到哪一行是有一个记录的比如线程A执行到代码第10行了这个时候在线程A中是有一个程序计数器来记录10这一行。
程序计数器在线程中是私有的。
那么他有什么好处呢虽然我们开发的时候可以使用多线程来开发但是CPU在执行A线程的时候B线程就需要等待等到CPU去执行A线程的时候B线程又需要等待了所以说如果这个时候CPU去执行B线程那么执行完后再回来执行A线程的时候就知道之前执行到哪一行了可以从这一行接着执行。
2.虚拟机栈与程序计数器一样虚拟机栈也是线程私有的虚拟机是栈是存放执行方法的时候用到的一些信息例如在执行方法的时候虚拟机就会创建一个栈帧用于存储局部变量表表里是局部变量、操作数栈如果要进行一些数的计算那么会把数先读取到操作数栈中进行操作最后赋值到局部变量表中、动态链接、方法出口等信息。
3.堆堆是线程共享的堆是虚拟机所管理的内存中最大的一块一般优化就是优化这块内存比如我们Student
4.本地方法栈本地方法栈是用来执行本地方法的时候所使用的例如Java中我们会看到很多Native方法这些方法使用例如c语言写的Java中只是调用。
5.方法区方法区是线程共享的它用于存储已被虚拟机加载的类型信息、常量、静态变量等。
在方法区中还包含一个运行时常量池部分这一部分用于存放编译期生成的各种字面量与符号引用这部分内容将在类加载后存放到方法区的运行时常量池中所谓符号引用其实就是将一个例如main方法这个方法引用转化为指针应用可以更加快速的找到这个方法在磁盘中的真正位置
我还想歇会喝口水呢这么快就问下一个知识点了我偷偷翻书看一下。
。
。
Student()的命令后他会先去常量池中查看这个Student()类是否有相应的符号引用并且这个类是否被加载、解析、初始化过如果没有的话需要先进行类的加载、解析、初始化。
指针碰撞假如堆空间现在没有数据并且堆空间是一个方形的空间那么我们用一个指针放在起始位置也就是紧挨着边这个时候有一个占用1M的对象要创建了那么我们的指针就从初始位置开始从左向右走1M的距离这个时候又有一个10M的对象要创建了我们的指针从当前位置又向右走了10M的距离这个时候有一个1G的对象来了指针我淦。
。
。
指针向右走到头了也没有1G的距离这个时候就创建不了这个1G的对象了。
空闲列表指针碰撞的方式适用于堆空间连续的这种方式如果不连续的话就不能从左到右来分配空间了这个时候就需要用到空闲列表了使用一个空闲列表来记录哪些空间是空闲的新创建一个对象就放到那里去。
对象头对象头中存储了对象自身运行时的数据如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳以及指向它的类型元数据的指针通过这个指针来确定对象是哪个类的实例。
实例数据这一部分是我们真正在对象中定义的信息比如对象中的一些字段等内容还有继承父类的一些内容和在子类中定义的字段都在此记录。
对齐填充这一部分并不是每个对象都存在的因为虚拟机要求对象的起始地址必须是8字节的整数倍假如我们实例数据只有4字节那么我们需要另外填充4字节的数据来保证对象的起始位置是8字节的整数倍。
.
可以采用可达性分析算法和引用计数算法来判断对象是否是垃圾对象。
Root”根开始依据引用关系向下搜索如果不能搜索到的证明是垃圾对象。
引用计数算法当一个对象被引用的时候就会在这个对象中的引用计数器中加1如果引用失效时计数器的值就会减1当这个对象的引用计数器为0的时候就证明这个对象是垃圾对象不过这种算法有一个缺点就是两个对象之间相互引用的时候就会认为两个对象都不是垃圾对象但是这两个对象是因为循环依赖造成的问题理应被清理掉但是这种算法解决不了这种循环引用的问题。
我二分法、三分法、四。
。
。
不对怎么感觉背串了还是不编了偷偷看一下书吧
标记-清除算法此算法主要用于一块内存区的垃圾收集器在标记后直接做清除操作不会再做后续的操作。
标记-复制算法此算法主要用于两块内存区的垃圾收集器将存活对象标记然后将存活对象放入保留区域中然后将之前的一块区域全部清理掉作为下一次的保留区域。
标记-整理算法此算法主要用于一块内存区的垃圾收集器他与标记清除算法的区别在于他清除后会将内存区域中存活的对象重新整理到一起使得剩下的空间可以连续起来。
我三V肉、爬牛、爬V肉死砍胃汁、三V肉偶得、爬V肉偶得、CMS、G1、ZGC我想了想还是画出来吧毕竟我的英语水平读出来面试官可能会怀疑人生
Serial收集器是最基础、历史最悠久的收集器这个收集器是一个单线程工作的收集器。
ParNew收集器实质上是Serial收集器的多线程并行版本可以同时使用多条线程进行垃圾收集。
Scavenge收集器是一款新生代收集器它是基于标记-复制算法实现的收集器。
Parallel
Scavenge收集器的目标则是达到一个可控制的吞吐量所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值即。
Old是Serial收集器的老年代版本它同样是一个单线程收集器。
Scavenge收集器的老年代版本支持多线程并行收集基于标记-整理算法实现。
CMS收集器是一种以获取最短回收停顿时间为目标的收集器它的运作过程分为四个步骤包括
Roots的直接关联对象开始遍历整个对象图的过程这个过程耗时较长但是不需要停顿用户线程可以与垃圾收集线程一起并发运行。
重新标记重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录这个阶段的停顿时间通常会比初始标记阶段稍长一些但也远比并发标记阶段的时间短。
并发清除这个阶段清理删除掉标记阶段判断的已经死亡的对象由于不需要移动存活对象所以这个阶段也是可以与用户线程同时并发的。
G1不再坚持固定大小以及固定数量的分代区域划分而是把连续的Java堆划分为多个大小相等的独立区域Region每一个Region都可以根据需要扮演新生代的Eden空间、Survivor空间或者老年代空间。
如图。
Root开始对堆中对象进行可达性分析递归扫描整个堆里的对象图找出要回收的对象这阶段耗时较长但可与用户程序并发执行。
最终标记对用户线程做另一个短暂的暂停用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
筛选回收负责更新Region的统计数据对各个Region的回收价值和成本进行排序根据用户所期望的停顿时间来制定回收计划可以自由选择任意多个Region构成回收集然后把决定回收的那一部分Region的存活对象复制到空的Region中再清理掉整个旧Region的全部空间。
这里的操作设计存活对象的移动是必须暂停用户线程由多条收集器线程并行完成的。
堆内存模型分为年轻代和老年代其中年轻代中又分为Eden区和Survivor区Survivor区又分为S0和S1区。
我这可有点多了。
。
。
说了不知道你能不能听得懂啊算了不行就拿出我藏在桌下的书给你讲吧
对象优先在Eden分配new一个对象首先会放到Eden区中当Eden区域放满了后会将Eden区域中存活的对象放入到Survivor区中的S0区域然后将Eden区域清空这个时候新new的对象还是放入Eden区域中当Eden区域中再次满了的话就将Eden区域中的存活对象和S0中的存活对象都拿出来放入到S1区域中并将Eden区域和S0区域中都清理掉当Eden区域再次满了就向Eden区域中的存活对象和S1中的存活对象一起放入到S0中也就是循环将Eden区域中的存活对象和Survivor中的其中一块区域中的存活对象一块拿出来放入到Survivor中的另外一块区域中如此循环每循环一次对象的GC年龄加1当GC年龄到达15的时候就会移入到老年代。
放入老年代的时候的GC我们可以称他为轻GC这个GC的时间比较短当老年代满了的时候会进行重GC这个GC的时间比较长。
长期存活的对象进入老年代就是对象在年轻代来回循环到达15次默认这个数值可以设置就会将对象放入老年代。
大对象直接进入老年代当新创建的对象比较大的时候我们可以直接将他放入老年代这样可以避免在年轻代来回复制造成的额外开销具体多大的对象是大对象我们可以根据
-XX:PretenureSizeThreshold参数来设置。
动态对象年龄判定如果在Survivor空间中低于或等于某年龄的所有对象大小的总和大于Survivor空间的一半那么年龄大于或等于该年龄的对象就可以直接进入老年代无需等到年龄达到15假如现在最大的对象年龄为10但是Survivor空间以及使用一半了如果再往下走可能还没有对象达到15就造成Survivor区域满了所以就提前将大年龄的对象放入老年代了。
空间分配担保在发生轻GC之前虚拟机就会先检查老年代可用的空间是否大于新生代所有对象的总空间如果大于即便新生代所有的对象都不是垃圾对象那么老年代也放的下如果不大于呢虚拟机会先查看是否设置了允许担保失败的参数如果允许虚拟机会判断老年代的剩余空间是否大于历次从新生代到老年代里的对象的平均大小。
如果大于就会进行轻GC将新生代的存活对象放入老年代这一次是冒险的因为有可能这一次轻GC比之前轻GC的平均值存活的要多这样会造成老年代内存直接溢出。
如果小于就会先进行一次重GC将老年代的空间腾出来保证可以将年轻代的存活对象放进去。
如果配置的参数是不允许担保失败那么我们每一次到达老年代剩余的空间不够新生代所有对象的总空间的时候我们就会进行一次重GC将老年代的空间先腾出来。
面试官刚才都是一些概念性的东西现在问你点实操的说一下有哪些虚拟机性能监控方法呢
我刚想歇一会。
。
。
早知道简历上写精通Java虚拟机会被这么问我就只写了解Java虚拟机了哎。
。
。
-l命令可以查看主类全名如果进程执行的是jar包则输出jar路径
参数ms和count代表查询间隔和次数如果省略了这个2个参数说明只查询一次假设现在我们要查询66320的垃圾收集情况250毫秒查询一次一共查询20次
jhat命令与jmap搭配使用来分析jmap生成的堆转储快照。
面试官小伙子真厉害啊我这边没有什么要问的了你还有什么问题要问面试官两眼放光
我额。
。
。
面试官这个我的纸质简历可以给我吗可以不往我的简历上写写画画吗我明天的面试还要用。
Java虚拟机是即基础又有点深奥的东西所以大家要收藏后认真反复的去学习如果觉得我的文章还不错的话就点个赞吧另外可以微信搜索【小奇JAVA面试】的好文章获取我为大家准备的资料。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback