面试总结之JVM(下篇)

148 阅读7分钟

JVM(下篇)

什么是指针碰撞?

一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。当类加载检查通过后,java虚拟机开始为新生对象分配内存。如果Java堆中内存是绝对规整的,所有被使用过的内存都被放到一边,空闲的内存放到另外一边,中间放着一个指针作为分界点的指示器,所分配内存仅仅是把指针向空闲方向挪动一段与对象大小相等的实例,这种分配方式就是指针碰撞。

什么是空闲列表?

如果java堆内存中的内存不是规整的,已经被使用的内存和空闲的内存相互交错在一起,不可以进行指针碰撞啦,虚拟机必须维护一个列表,记录哪些内存是可用的,在分配的时候从列表找到一块大的空间分配给对象实例,并更新列表上的记录,这种分配方式就是空闲列表

什么是TLAB?

可以把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在java堆中预先分配一小块内存,这就是TLAB(Thread Local Allocation Buffer,本地线程分配缓存)。虚拟机通过-XX:UseTLAB设定他的值。

对象头具体都包含哪些内容?

在我们常用的HotSpot虚拟机中,对象在内存中布局实际包含3个部分:对象头、实例数据、对齐填充。

而对象头包含两部分内容:

  1. 对象自身运行时所需的数据,也被称为Mark Word,也就是用于轻量级锁和偏向锁的关键点。具体的内容包含对象的hashcode、分代年龄、轻量级锁指针、重量级锁指针、GC标记、偏向锁线程ID、偏向锁时间戳。
  2. 存储类型指针,也就是指向类的元数据的指针,通过这个指针才能确定对象是属于哪个类的实例,

MarkWord中的内容会随着锁标志位而发生变化,所以只说存储结构就好了。如果是数组的话,还包含了数组的长度

企业微信截图_16772923058877.png

你知道哪些JVM调优参数?

堆栈内存相关

- -Xms 设置初始堆的大小
- -Xmx 设置最大堆的大小
- -Xmn 设置年轻代大小,相当于同时配置-XX:NewSize和-XX:MaxNewSize为一样的值
- -Xss 设置每个线程的堆栈大小
- -XX:NewSize 设置年轻代大小(for 1.3/1.4)
- -XX:MaxNewSize 设置年轻代最大值(for 1.3/1.4)
- -XX:NewRatio 年轻代与年老代的比值(除去持久代)
- -XX:SurvivoRatio Eden区与Survivor区的比值
- -XX:PretenureSizeThreshold 当创建的对象超过指定大小时,直接把对象分配在老年代。
- -XX:MaxTenuringThreshold 设定对象在Survivor复制的最大年龄阈值,超过阈值转移到老年代

垃圾收集器相关

  • -XX:+UseParallelGC 选择垃圾收集器为并行收集器

    • -XX:ParallelGCThreads=20 配置并行收集器的线程数
    • -XX:+UseConcMarkSweepGC 设置年老代为并发收集
    • -XX:CMSFullGCsBeforeCompaction=5 由于并发收集器不对内存空间进行压缩、整理。所以运行一段时间后会产生“碎片”,使得运行效率降低。此值设置运行5次GC之后对内存空间进行压缩、整理。
    • -XX:+UseCMSCompactAtFullCollection 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

辅助信息相关

- -XX:+PrintGCDetails  打印GC详细信息
- -XX:+HeapDumpOnOutOfMemoryErrorJVM在发生内存溢出的时候自动生成内存快照,排查问题用
- -XX:+DisableExplicitGC  禁止系统System.gc(),防止手动误触发FullGC造成问题
- -XX:+PrintTLAB  查看TLAB空间的使用情况

说一下JVM有哪些垃圾回收器?

如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现了。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、ParallelScavenge,回收老年代的收集器包括SerialOld、ParallelOld、CMS,还有用于回收整个java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。

企业微信截图_16773096064261.png

  • Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

  • ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程版本,在多核 CPU环境下有着比Serial更好的表现;

  • Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞 吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽 快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;

  • Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;

  • Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收 集器的老年代版本;

  • CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回 收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

  • G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一 个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收 集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年 代),而前六种收集器回收的范围仅限于新生代或老年代。

  • ZGC (Z Garbage Collector)是一款由Oracle公司研发的,以低延迟为首要目标的一款垃圾收 集器。它是基于动态Region内存布局,(暂时)不设年龄分代,使用了读屏障、染色指针和内 存多重映射等技术来实现可并发的标记-整理算法的收集器。在 JDK 11 新加入,还在实验阶 段,主要特点是:回收TB级内存(最大4T),停顿时间不超过10ms。

    优点:低停顿,高吞吐 量, ZGC 收集过程中额外耗费的内存小。

    缺点:浮动垃圾

    目前使用的非常少,真正普及还是需要写时间的

如何选择垃圾收集器?

1、如果你的堆大小不是很大(比如100MB),选择串行收集器一般是效率最高的。参数:-XX:+UseSerialGC

2、如果你的应用运行在单核的机器上,或者你的虚拟机核数只有单核,选择串行收集器依然是合适的,这时候启用一些并行收集器没有任何收益。

3、如果你的应用是“吞吐量”优先的,并且对较长时间的停顿没有什么特别的要求。选择并行收集器是比较好的。参数:-XX:+UseParallelGC

4、如果你的应用对响应时间要求较高,想要较少的停顿。甚至1秒的停顿都会引起大量的请求失败,那么选择G1、ZGC、CMS都是合理的。虽然这些收集器的GC停顿通常都比较短,但它需要一些额外的资源去处理这些工作,通常吞吐量会低一些。参数:

-XX:+UseConcMarkSweepGC

-XX:+UseG1GC

-XX:+UseZGC等。

从上面这些出发点来看,我们平常的web服务器,都是对响应性要求非常高的,选择性其实就集中在CMS、G1、ZGC上。而对于某些定时任务,使用并行收集器,是一个比较好的选择。

什么是类加载器?

类加载器是一个用来加载类文件的类。java源代码通过javac编译器编译成类文件。然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件、系统、网络或其它来源的类文件。

什么是tomcat类加载器?

在tomcat中类的加载稍有不同,如下图:

企业微信截图_16773114945715.png