23. 什么是指针碰撞?
一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。当类加载检查通过后,Java虚拟机开始为新生对象分配内存。如果Java堆中内存是绝对规整的,所有被使用过的的内存都被放到一边,空闲的内存放到另外一边,中间放着一个指针作为分界点的指示器,所分配内存仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的实例,这种分配方式就是 指针碰撞。
24. 什么是空闲列表?
如果Java堆内存中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,不可以进行指针碰撞啦,虚拟机必须维护一个列表,记录哪些内存是可用的,在分配的时候从列表找到一块大的空间分配给对象实例,并更新列表上的记录,这种分配方式就是空闲列表。
25. 什么是TLAB?
可以把内存分配的动作按照线程划分在不同的空间之中进行,每个线程在Java堆中预先分配一小块内存,这就是TLAB(Thread Local Allocation Buffffer,本地线程分配缓存) 。虚拟机通过 -XX:UseTLAB 设定它的。
26. 对象头具体都包含哪些内容?
在我们常用的Hotspot虚拟机中,对象在内存中布局实际包含3个部分:
- 对象头
- 实例数据
- 对齐填充
而对象头包含两部分内容,Mark Word中的内容会随着锁标志位而发生变化,所以只说存储结构就好了。
- 对象自身运行时所需的数据,也被称为Mark Word,也就是用于轻量级锁和偏向锁的关键点。具体的内容包含对象的hashcode、分代年龄、轻量级锁指针、重量级锁指针、GC标记、偏向锁线程ID、偏向锁时间戳。
- 存储类型指针,也就是指向类的元数据的指针,通过这个指针才能确定对象是属于哪个类的实例。
27. 你知道哪些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:SurvivorRatio 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:+HeapDumpOnOutOfMemoryError让JVM在发生内存溢出的时候自动生成内存快照,排查问题用
- -XX:+DisableExplicitGC禁止系统System.gc(),防止手动误触发FGC造成问题.
- -XX:+PrintTLAB 查看TLAB空间的使用情况
28. 如何排查线上系统CPU飙高
在工作中,当一个系统发生 OOM 的时候,这种问题可能会让大家很烦恼困惑,因为故障排查起来是一个综合技术的考量,在平时工作中要增加自己的知识广度,多学习,多总结,多思考,多做笔记,这才是真正的王道。
尤其是在线上(Linux环境)中,如何分析是哪个线程导致的CPU飙高的问题,通常大致有几个差不多固定的步骤。这个问题也是面试频率非常之高的问题之一,很多人也是靠回答这个问题而加薪。
常用套路步骤:
- 使用top命令
- 使用 top -H pid
- 使用 prinf %x tid
- 使用 jsack pid >pid.log
- 查阅 less pid.log
下面就来说一下这几个步骤。
top
我们可以使用top命令来查找对应使用CPU最多的进程,找到后,先记录下对应的pid(后面要用到)。
top -H pid
再次使用top名,但是这次增加一个参数-H,可以查看上面找出来的pid进程中对应的线程tid,记住这时候的线程tid得记住。
printf
但是此时的tid是十进制的,我们需要把这个tid转成16进制。然后使用printf %x tid。
stack
使用jstack工具把线程信息输出到对应的日志文件中,后面使用这个日志文件内容进行分析。jstack pid >pid.log
less
上面已经生成日志文件了,这时候可以使用less命令来查找上面已经转换好的16进制的线程tid。
29. 如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
不会立即释放对象占用的内存。 如果对象的引用被置为null,只是断开了当前线程栈帧中对该对象的引用关系,而 垃圾收集器是运行在后台的线程,只有当用户线程运行到安全点(safe point)或者安全区域才会扫描对象引用关系,扫描到对象没有被引用则会标记对象,这时候仍然不会立即释放该对象内存,因为有些对象是可恢复的(在 fifinalize方法中恢复引用 )。只有确定了对象无法恢复引用的时候才会清除对象内存。
30. 说说内存泄漏和内存溢出
内存泄露就是申请的内存空间没有被正确释放,导致内存空间就这么一点点被蚕食,最后就算再多的内存也被用光光。
内存溢出就是申请的内存超过了可用内存,内存不够用了,只有10MB的内存了,但现在需要使用20MB的内存
两者关系:内存泄露 → 剩余内存不足 → 后续申请不到足够内存 →内存溢出。
31. 什么是Stop The World ? 什么是OopMap?什么是安全点?
进行垃圾回收的过程中,会涉及对象的移动。为了保证对象引用更新的正确性,必须暂停所有的用户线程,像这样的停顿,虚拟机设计者形象描述为「Stop The World」。也简称为STW。
在HotSpot中,有个数据结构(映射表)称为「OopMap」。一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,记录到OopMap。在即时编译过程中,也会在「特定的位置」生成 OopMap,记录下栈上和寄存器里哪些位置是引用。
这些特定的位置主要在:
- 循环的末尾(非 counted 循环)
- 方法临返回前 / 调用方法的call指令后
- 可能抛异常的位置
这些位置就叫作「安全点(safepoint)。」 用户程序执行时并非在代码指令流的任意位置都能够在停顿下来开始垃圾收集,而是必须是执行到安全点才能够暂停。
32. 说一下 JVM 有哪些垃圾回收器?
如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。下图展示了7种作用于不同分代的收集器,其中用于回收新生代的收集器包括Serial、PraNew、ParallelScavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器之间的连线表示它们可以搭配使用。
- 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 收集过程中额外耗费的内存小。缺点:浮动垃圾
目前使用的非常少,真正普及还是需要写时间的。
新生代收集器:Serial、 ParNew 、 Parallel Scavenge
老年代收集器: CMS 、Serial Old、Parallel Old
整堆收集器: G1 , ZGC (因为不涉年代不在图中)。