JVM面试突击

参考

  • https://www.freesion.com/article/42891183904/

内存结构

  • https://blog.csdn.net/aajjw/article/details/115672226
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区:
    • 如果是共享空间(堆上的其他区域)分配空间必须要有同步机制,
    • TLAB是给本线程在堆空间上开辟出一块独享区域,因此无需同步
    • TLAB空间中无法分配的对象,JVM会尝试在Eden空间中进行分配。如果Eden空间无法容纳该对象,就只能在老年代中进行分配空间。

垃圾收集器

  • 内存分配的方式
    • 碰撞指针
    • 空闲列表
  • 垃圾收集器处理回收的是堆内存和方法区的内存
  • 如何判断对象存活
    • 引用计数法
    • 可达性算法
      • GC Roots
        • 栈,本地方法栈中引用的对象
        • 方法区中引用,如类静态属性引用的对象,常量池引用
        • JVM内部的引用
          • 基本数据类型对应的Class对象,
          • 常驻对象:异常类
          • 同步锁持有的对象
          • 代码缓存
  • 常用垃圾回收的算法
    • 标记-清除
    • 复制
    • 标记-整理
    • 分代思想

分代算法与GC过程

  • 分代
    在这里插入图片描述
  • GC过程
    在这里插入图片描述
  • 堆内存管理-对象内存空间分配与转移
    • 对象优先在Eden分配
    • 大对象直接分配到Old
    • 长期存活进入Old
      • Eden,S0,S1,from to。过程如下
      • 年龄大于15进入Old,也可以提前
      • 空间分配担保
        • Old区域连续空间大于新生代所有对象空间 或者 大于历次晋升到老年代对象的平均大小,则进行Minor GC。
        • 否则进行Full GC在这里插入图片描述
  • GC日志打印:https://blog.csdn.net/x763795151/article/details/89981686
    • 使用jvm命令,可以打印GC的信息(GC类型,GC开始时间,CG耗时,GC前后的内存空间统计情况),并可以指定文件
  • GC类型:
    • Minor GC:新生代GC,
      • 一般采用复制算法回收垃圾,
      • 一般回收速度也比较快
      • 触发条件
        • eden区满时,触发MinorGC。即申请一个对象时,发现eden区不够用,则触发一次MinorGC。
        • 新创建的对象大小 > Eden所剩空间时触发Minor GC
    • Major GC:老年代GC,
      • 通常执行Major GC会连着Minor GC一起执行
      • Major GC的速度要比Minor GC慢的多
      • 可采用标记清楚法和标记整理法
    • Full GC:整个堆空间
      • 触发情况和Major GC相似
        1. 每次晋升到老年代的对象平均大小>老年代剩余空间
        2. MinorGC后存活的对象超过了老年代剩余空间
        3. 永久代空间不足
        4. 执行System.gc()
        5. CMS GC异常
        6. 堆内存分配很大的对象

机制

  • STW:stop the world
  • OopMap:对象表,记录了内存中哪些位置是引用
  • 安全点:不是所有的指令都记录Oop,而是在“需要长时间执行的指令处”记录Oop,如:方法调用,循环跳转,异常跳转等。
  • 主动式抢占:GC需要中断时,设置标记,线程执行时主动轮询标记,如果是真就挂起。主动轮询的时点和记录Oop的时机重合并多一个创建对象分配内存时。
  • 安全区:
    • 以上机制在线程是运行状态是可以完美运行的。但是如果线程挂起,就不能了。
    • 安全区:在这个区域内,引用关系不发生改变
      • 线程进入安全区时,标识自己进入了安全区。
      • GC时不用管在安全区内的线程,因为引用状态不发生改变。
      • 线程离开安全区时,检查是否完成根节点枚举,如果完成就可以继续执行,否则等待GC完成根节点枚举。

垃圾收集器类型

在这里插入图片描述

  • CMS
    *
    • 目标:最短回收停顿时间为目标的收集器
    • 过程:
      • 初始标记(CMS initial mark):需要Stop The World,标记GC Roots能直接关联到的对象,速度很快
      • 并发标记(CMS concurrent mark):耗时长,但是可以和用户线程并发
      • 重新标记(CMS remark):需要Stop The World,标记并发标记期间变动的对象,时间较短
      • 并发清除(CMS concurrent sweep):不需要移动对象,可以并发
    • 缺点:
      • 阈值不好确定
        • 因为GC过程中可能产生新的垃圾,因而不能老年区满了再收集
        • 阈值小,浪费空间,频繁GC
        • 阈值大,回收失败就用单线程的垃圾收集器兜底,慢。
      • 标记清除算法,内存碎片。可以配置参数,每几次Full GC后的Full GC先整理碎片。
      • 核心数少时,应用性能降低很厉害。
  • G1
    在这里插入图片描述
    • 参考
      • https://www.cnblogs.com/sidesky/p/10797382.html
      • https://www.cnblogs.com/aspirant/p/8663872.html
    • 目标:停顿时间模型。不是完全低延时,而是延时可控下的最高吞吐,因此是全功能。
    • 特点:
      • 每次根据用户设定允许的收集停顿时间,默认值是200毫秒,优先处理回收价值收益最大的那些Region
      • 分块+分区:
        在这里插入图片描述
        • Humongous与大对象:当新建对象大小超过Region大小一半时,直接在新的一个或多个连续Region中分配,并标记为H。
      • 回收算法:整体是标记-整理。局部是标记-复制。不产生碎片
      • 全年龄段收集
    • 实现:
      • 跨块引用:记忆集中存双向指针,因此(垃圾收集器本身)内存占用高,经验值10%-20%堆空间
      • 分块监控,获取预期值。算法:衰减均值
    • GC模式:
      • young gc:
        • 触发:Eden空间耗尽
        • 回收对象:新生代
        • 回收方式:
          • 因为复制对象,所以stop the world
          • 逻辑上参考分代算法
            • Eden->Survivor
            • Eden->Old
            • Survivor->Survivor
            • Survivor->Eden
          • 物理上是通过标记复制完成
            在这里插入图片描述
      • mixed gc
        • 回收对象:新生代和部分老年代
        • 过程
          • 初始标记(Initial Marking)
          • 并发标记(Concurrent Marking)
          • 最终标记(Final Marking)
          • 筛选回收(Live Data Counting and Evacuation):
            • 更新Region的统计数据,根据用户期望和统计数据制定回收计划
            • 并发处理:块间复制,涉及对象移动,必须暂停线程
      • G1命令优化参考:https://www.cnblogs.com/aspirant/p/8663872.html
  • ZGC
  • 选择
    • JDK版本新:ZGC,至少JDK11,性能优秀,特别是延时方面
    • JDK版本老:
      • CMS:4GB到6GB以下的堆内存
      • G1:更大的内存

内存模型

  • 并发编程的三个概念
  • 多核CPU:
    • 多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致
  • 解决方式
    • 锁总线:效率低
    • 缓存一致性协议:效率高,单各家CPU的协议不一样
  • JMM将不同(CPU)平台的内存模型统一
    • 逻辑模型和操作
      • 主内存,每个线程都有自己的工作内存
      • 8种操作及基本规则
    • JMM主要规定了一个线程对共享变量的写入何时对其他线程是可见的
  • 通过三大特性对比volatile,synchronized和final
    • 原子性:对于基本数据类型的读取和赋值操作都是原子性操作
    • 可见性:一个线程修改了共享变量的值,其他线程能够立即得知这个修改
      • volatile:通过添加lock指令实现可见性
        * lock指令在多核的情况下会做两件事:
        * 将当前处理器缓存行的数据写回到系统内存
        * 写回内存的操作会使其他CPU里缓存了该内存地址的数据无效
      • synchronized
        • 当线程获取锁时会从主内存中获取共享变量的最新值
        • 释放锁的时候会将共享变量同步到主存中
      • final
        • 被final关键字修饰的字段在构造器中一旦初始化完成
        • 并且没有发生this逃逸(其他线程通过this引用访问到初始化了一半的对象)
        • 那么其他线程就能看见final字段的值
    • 有序性:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的
      • 允许编译器和处理器对指令进行重排序,重排序的过程不会影响到单线程程序的执行
      • 无序是因为发生了指令重排序和工作内存与主内存同步延迟
        • 指令重排序
          • as-if-serial:不管怎么进行重排序,单线程程序的执行结果不能被改变。
          • happens-before:先行发生原则
          • 不违背两个原则,爱怎么排序就怎么排序
      • volatile关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前
        • 内存屏障的4种类,在每家CPU上情况可能不同
      • synchronized关键字同样可以保证有序性,它保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码
  • 有用的事例
    • 读取和赋值操作是原子的,但是运算操作不是
  • volatile正确使用:https://zhuanlan.zhihu.com/p/112742540
    • 状态标记立刻被感知
      • 多线程下,不加volatile,状态标记的改变不被其他线程看到
    • 一改多读
    • 原则
      • 对变量的写操作不依赖于当前值
      • 该变量没有包含在具有其他变量的不变式中。

synchronized机制

java工具及命令

  • 命令行工具
    在这里插入图片描述
  • 图形化工具
    • jvisualvm:
      • 图形化监控,可以远程,但是需要应用打开对应端口
      • 插件丰富
        • Buffer Pools:可以看直接内存大小
    • arthas
      • 监控内容丰富
        • dashboard:线程信息,内存情况(包括直接内存)
        • 获取反编译文件,确认线上代码版本
        • watch:查看方法的调用:https://www.cnblogs.com/alisystemsoftware/p/13087196.html
        • 使用Instrument代码热替换
          • JVMTI,JVM层级的接口,

Java 动态字节码

  • 动态生成字节码ASM等,但无法做到替换
  • Instrument
  • BTrace

常见JVM内存问题的定位

  • 事前配置
    • 异常日志记录,主要是OOM这类,通常和应用日志一起记录
    • 指定jvm参数,在OOM时,转存dump文件,方便定位内存泄露的问题
    • GC日志记录,方便还原GC过程。
      • 参考:https://blog.csdn.net/x763795151/article/details/89981686
      • 使用jvm启动参数,可以打印GC的信息(GC类型,GC开始时间,CG耗时,GC前后的内存空间统计情况),并可以指定文件
    • 监控,如arthas,或者监控平台
  • 常见问题
    • OOM
      • 先分析异常信息,哪个内存区域的内存溢出
      • 堆内存一般要分析dump文件
      • 比如,线上hibernate级联查询
    • 监控显示内存占用大,但堆内存空间不大
      • 查看jvm启动参数,是否配置了其他内存空间大小,针对没配置大小的一一排查,
        • 优先排查直接内存,因为直接内存默认和堆内存一样大
      • 通常是堆外内存
        • 直接内存,如NIO
        • 元空间中的类信息,如使用字节码技术大量生成类(如:cglib动态代理)
    • 应用进程被杀死,但是没有dump文件
      • 没有dump文件,证明没有OOM
      • 极有可能是Linux OOM killer,查linux的messages日志,确认是否是Linux OOM killer

JVM调优

  • 主要是选择垃圾收集器,配置垃圾收集器参数
  • 经验:
    • 堆内存大小最大最小一致,避免内存缩扩容
    • 调整年轻代和老年代比例
      • 根据应用特点
      • 根据GC日志
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
建议阅读本文档的方式 本文档提供详细的目录,建议大家使用电脑阅读。如果大家用手机阅读的话,可以下载一个不错的PDF阅读器,比如 很多人常用的福昕PDF阅读器。 本文档提供详细的目录,大家可以根据自己的实际需要选择自己薄弱的知识章节阅读。 前言 不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有 章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 运筹帷幄之后,决胜千里之外!不打毫无准备的仗,我觉得大家可以先从下面几个方面来准备面试: 1. 自我介绍。(你可千万这样介绍:“我叫某某,性别,来自哪里,学校是那个,自己爱干什么”,记住:多说点简 历上没有的,多说点自己哪里比别人强!) 2. 自己面试中可能涉及哪些知识点、那些知识点是重点。 3. 面试中哪些问题会被经常问到、面试中自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多 少?能记住多久?第二:背题的方式的学习很难坚持下去!) 4. 自己的简历该如何写。 “80%的offer掌握在20%的人手中” 这句话也不是不无道理的。决定你面试能否成功的因素中实力固然占有很大一部 分比例,但是如果你的心态或者说运气不好的话,依然无法拿到满意的 offer。运气暂且不谈,就拿心态来说,千万 不要因为面试失败而气馁或者说怀疑自己的能力,面试失败之后多总结一下失败的原因,后面你就会发现自己会越来 越强大。 另外,大家要明确的很重要的几点是: 1. 写在简历上的东西一定要慎重,这可能是面试官大量提问的地方; 2. 大部分应届生找工作的硬伤是没有工作经验或实习经历; 3. 将自己的项目经历完美的展示出来非常重要。 笔主能力有限,如果有不对的地方或者和你想法不同的地方,敬请雅正、不舍赐教。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值