[JVM full score summary]

Article directory

1. What is JVM?

JVM is a virtual machine, which is the English abbreviation of Java Virtual Machine. It is a part of the java operating environment, and it is a fictitious computer, which is realized by simulating various computing computer functions on an actual computer. It can interpret the compiled java bytecode file into machine code instructions on a specific platform for execution, so it also makes the java language cross-platform.

2. Can you tell me about the memory area of ​​the JVM?

1.程序计数器
程序计数器是线程私有的一块内存空间,用于存储当前运行的线程所执行的字节码的行号指示器,每一个运行的线程都有一个独立的程序计数器,是唯一没有内存溢出的一块区域。
2.虚拟机栈
虚拟机栈是描述java方法的执行过程的内存模型,它在当前栈帧存储了局部变量表、操作数栈、动态链接、方法出口以及运行时数据及其数据结构等信息。每个运行中的线程当前只有一个栈帧处于活动状态。
3.本地方法区
本地方法区也是线程私有的,与虚拟机栈的作用类似,区别是虚拟机栈为执行Java方法服务,本地方法栈为Native方法服务。
4.堆(运行数据区)
在JVM运行过程中创建的对象和产生的数据都被存储在堆中,是被线程共享的区域,也是垃圾收集器进行垃圾回收的最主要的内存区域。因此从垃圾回收(GC)的角度来看,Java堆还可以被细分为:新生代、老年代和永久代
5.方法区
方法区是线程共享的,也被称为永久代。方法区常被用来存储常量、静态变量、类信息、即时编译后机器码、运行时常量池等数据。
永久代的内存回收主要针对常量池的回收和类的卸载,因此可回收的对象很少

3. Tell me about the changes in the memory area of ​​JDK1.6, 1.7, and 1.8?

JDK1.6:有永久代,静态变量存放在永久代(方法区)。
JDK1.7:有永久代,但是已经把字符串常量池、静态变量存放到堆中,逐渐减少永久代的使用。
JDK1.8:无永久代,运行时常量池、类常量池都保存到元数据区,也就是常说的元空间。但字符串常量池仍存在堆中。

4. Why use metaspace instead of permanent generation as the implementation of the method area?

元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
移除PermGen(永久代)从从JDK7 就开始。例如,字符串内部池,已经在JDK7 中从永久代中移除。直到JDK8 的发布将宣告 PermGen(永久代)的终结。
元空间则是JDK1.8及之后,HotSpot虚拟机对方法区的新实现。但永久代仍存在于JDK7 中,并没完全移除,比如:字面量 (interned strings)转移到 Java heap;类的静态变量(class statics)转移到Java heap ;符号引用(Symbols) 转移到 Native heap ;

5. Do you understand the process of object creation?

1、虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、 解析和初始化过。 如果没有,那必须先执行相应的类加载过程。在类加载检查通过后,接下来虚拟机将为新生对象分配内存。 对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。
2、内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。
3、接下来,虚拟机要对对象进行必要的设置 .信息存放在对象的对象头(Object Header)之中 。
4、执行new指令之后会接着执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

6. What is pointer collision? What is a free list?

指针碰撞:假设Java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”。

空闲列表:如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。
两种方式的选择由Java堆是否规整决定,Java堆是否规整是由选择的垃圾收集器是否具有压缩整理能力决定的。

7. When a new object is created in JM, will the heap be robbed? How is the JVM designed to ensure thread safety?

会,假设JVM虚拟机上,
每一次new 对象时,指针就会向右移动一个对象size的距离,一个线程正在给A对象分配内存,指针还没有来的及修改,另一个为B对象分配内存的线程,又引用了这个指针来分配内存,这就发生了抢占。
堆抢占和解决方案
	采用CAS分配重试的方式来保证更新操作的原子性
	每个线程在Java堆中预先分配一小块内存,也就是本地线程分配缓冲(Thread Local AllocationBuffer,TLAB),要分配内存的线程,先在本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。

8. Can you tell me about the memory layout of the object?

在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头主要由两部分组成:
	第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,官方称它为Mark Word,它是个动态的结构,随着对象状态变化。
	第二部分是类型指针,指向对象的类元数据类型(即对象代表哪个类).此外,如果对象是一个Java数组,那还应该有一块用于记录数组长度的数据
实例数据用来存储对象真正的有效信息,也就是我们在程序代码里所定义的各种类型的字段内容,无论是从父类继承的,还是自己定义的。
对齐填充不是必须的,没有特别含义,仅仅起着占位符的作用。

9. How to access and locate objects?

Java程序会通过栈上的reference数据来操作堆上的具体对象。由于reference类型在《Java虚拟机规范》里面只规定了它是一个指向对象的引用,并没有定义这个引用应该通过什么方式去定位、访问到堆中对象的具体位置,所以对象访问方式也是由虚拟机实现而定的,主流的访问方式主要有使用句柄和直接指针两种:
	如果使用句柄访问的话,Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息,其结构如图所示:
通过句柄访问对象
	如果使用直接指针访问的话,Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销

这两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本。HotSpot虚拟机主要使用直接指针来进行对象访问。

10. What do memory overflow and memory leak mean?

内存泄露就是申请的内存空间没有被正确释放,导致内存被白白占用。
内存溢出就是申请的内存超过了可用内存,内存不够了。
两者关系:内存泄露可能会导致内存溢出。

11. Can you write an example of memory overflow by hand?

在JVM的几个内存区域中,除了程序计数器外,其他几个运行时区域都有发生内存溢出(OOM)异常的可能,重点关注堆和栈。
Java堆溢出
	Java堆用于储存对象实例,只要不断创建不可被回收的对象,比如静态对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常(OutOfMemoryError)
虚拟机栈.OutOfMemoryError
	JDK使用的HotSpot虚拟机的栈内存大小是固定的,我们可以把栈的内存设大一点,然后不断地去创建线程,因为操作系统给每个进程分配的内存是有限的,所以到最后,也会发生OutOfMemoryError异常。

12. What are the possible causes of memory leaks?

静态集合类引起内存泄漏
	静态集合的生命周期和 JVM 一致,所以静态集合引用的对象不能被释放。
单例模式
	和上面的例子原理类似,单例对象在初始化后会以静态变量的方式在 JVM 的整个生命周期中存在。如果单例对象持有外部的引用,那么这个外部对象将不能被 GC 回收,导致内存泄漏。
数据连接、IO、Socket等连接
	创建的连接不再使用时,需要调用 close 方法关闭连接,只有连接被关闭后,GC 才会回收对应的对象(Connection,Statement,ResultSet,Session)。忘记关闭这些资源会导致持续占有内存,无法被 GC 回收。
变量不合理的作用域
	一个变量的定义作用域大于其使用范围,很可能存在内存泄漏;或不再使用对象没有及时将对象设置为 null,很可能导致内存泄漏的发生。
hash值发生变化
	对象Hash值改变,使用HashMap、HashSet等容器中时候,由于对象修改之后的Hah值和存储进容器时的Hash值不同,所以无法找到存入的对象,自然也无法单独删除了,这也会造成内存泄漏。说句题外话,这也是为什么String类型被设置成了不可变类型。
ThreadLocal使用不当
	ThreadLocal的弱引用导致内存泄漏也是个老生常谈的话题了,使用完ThreadLocal一定要记得使用remove方法来进行清除。

13. How to judge whether the object is still alive?

有两种方式,**引用计数算法(reference counting)**和可达性分析算法。
引用计数器的算法是这样的:在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。

目前 Java 虚拟机的主流垃圾回收器采取的是可达性分析算法。这个算法的实质在于将一系列 GC Roots 作为初始的存活对象合集(Gc Root Set),然后从该合集出发,探索所有能够被该集合引用到的对象,并将其加入到该集合中,这个过程我们也称之为标记(mark)。最终,未被探索到的对象便是死亡的,是可以回收的。

14. What kinds of objects can be used as GC Roots in Java?

可以作为GC Roots的主要有四种对象:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象

15. What kind of references does the object have?

Java中的引用有四种,分为强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。
是最传统的引用的定义,是指在程序代码之中普遍存在的引用赋值,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。
弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用。
虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供了PhantomReference类来实现虚引用。

16. Do you understand the finalize (method? What does it do?

如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。如果对象在在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己 (this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它就”逃过一劫“;但是如果没有抓住这个机会,那么对象就真的要被回收了。

17. Do you understand the memory partition of the Java heap?

按照垃圾收集,将Java堆划分为**新生代 (Young Generation)和老年代(Old Generation)**两个区域,新生代存放存活时间短的对象,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。
而新生代又可以分为三个区域,eden、from、to,比例是8:1:1,而新生代的内存分区同样是从垃圾收集的角度来分配的。

18. Do you understand the garbage collection algorithm?

标记-清除算法比较基础,但是主要存在两个缺点:
	执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低。
	内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记-复制算法解决了标记-清除算法面对大量可回收对象时执行效率低的问题。
	过程也比较简单:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
标记-复制算法这种算法存在一个明显的缺点:一部分空间没有使用,存在空间的浪费。新生代垃圾收集主要采用这种算法,因为新生代的存活对象比较少,每次复制的只是少量的存活对象。当然,实际新生代的收集不是按照这个比例。
标记-整理算法为了降低内存的消耗,引入一种针对性的算法:标记-整理(Mark-Compact)算法。
	其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
标记-整理算法主要用于老年代,移动存活对象是个极为负重的操作,而且这种操作需要Stop The World才能进行,只是从整体的吞吐量来考量,老年代使用标记-整理算法更加合适。

19. Tell me about the regional division of the new generation?

新生代的垃圾收集主要采用标记-复制算法,因为新生代的存活对象比较少,每次复制少量的存活对象效率比较高。
基于这种算法,虚拟机将内存分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。默认Eden和Survivor的大小比例是8∶1。

20. What do Minor GC/Young GC, Major Gc/old GC, Mixed GC, and Full GC mean?

部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:
新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。

21. When will Minor GC/oung GC be triggered?

新创建的对象优先在新生代Eden区进行分配,如果Eden区没有足够的空间时,就会触发Young GC来清理新生代。

22. When will Full GC be triggered?

Young GC之前检查老年代:在要进行 Young GC 的时候,发现老年代可用的连续内存空间 < 新生代历次Young GC后升入老年代的对象总和的平均大小,说明本次Young GC后可能升入老年代的对象大小,可能超过了老年代当前可用内存空间,那就会触发 Full GC。
Young GC之后老年代空间不足:执行Young GC之后有一批对象需要放入老年代,此时老年代就是没有足够的内存空间存放这些对象了,此时必须立即触发一次Full GC
老年代空间不足,老年代内存使用率过高,达到一定比例,也会触发Full GC。
空间分配担保失败( Promotion Failure),新生代的 To 区放不下从 Eden 和 From 拷贝过来对象,或者新生代对象 GC 年龄到达阈值需要晋升这两种情况,老年代如果放不下的话都会触发 Full GC。
方法区内存空间不足:如果方法区由永久代实现,永久代空间不足 Full GC。
System.gc()等命令触发:System.gc()、jmap -dump 等命令会触发 full gc。

23. When will the object enter the old age?

在对象的对象头信息中存储着对象的迭代年龄,迭代年龄会在每次YoungGC之后对象的移区操作中增加,每一次移区年龄加一.当这个年龄达到15(默认)之后,这个对象将会被移入老年代。
大对象直接进入老年代有一些占用大量连续内存空间的对象在被加载就会直接进入老年代.这样的大对象一般是一些数组,长字符串之类的对。
动态对象年龄判定为了能更好地适应不同程序的内存状况,HotSpot虚拟机并不是永远要求对象的年龄必须达到- XX:MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。
空间分配担保假如在Young GC之后,新生代仍然有大量对象存活,就需要老年代进行分配担保,把Survivor无法容纳的对象直接送入老年代。

24. Do you know which garbage collectors exist?

Serial收集器Serial收集器是最基础、历史最悠久的收集器。
	如同它的名字(串行),它是一个单线程工作的收集器,使用一个处理器或一条收集线程去完成垃圾收集工作。并且进行垃圾收集时,必须暂停其他所有工作线程,直到垃圾收集结束——这就是所谓的“Stop The World”。
ParNew
	ParNew收集器实质上是Serial收集器的多线程并行版本,使用多条线程进行垃圾收集。
Parallel Scavenge
	Parallel Scavenge收集器是一款新生代收集器,基于标记-复制算法实现,也能够并行收集。和ParNew有些类似,但Parallel Scavenge主要关注的是垃圾收集的吞吐量——所谓吞吐量,就是CPU用于运行用户代码的时间和总消耗时间的比值,比值越大,说明垃圾收集的占比越小。
Serial Old
	Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
Parallel Old
	Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。
CMS收集器
	CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,同样是老年代的收集器,采用标记-清除算法。
Garbage First收集器
	Garbage First(简称G1)收集器是垃圾收集器的一个颠覆性的产物,它开创了局部收集的设计思路和基于Region的内存布局形式。

25. What is Stop The World? What is 0opMap? What is a safe point?

进行垃圾回收的过程中,会涉及对象的移动。为了保证对象引用更新的正确性,必须暂停所有的用户线程,像这样的停顿,虚拟机设计者形象描述为Stop The World。也简称为STW。
在HotSpot中,有个数据结构(映射表)称为OopMap。一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,记录到OopMap。在即时编译过程中,也会在特定的位置生成 OopMap,记录下栈上和寄存器里哪些位置是引用。这些特定的位置主要在:
1.循环的末尾(非 counted 循环)
2.方法临返回前 / 调用方法的call指令后
3.可能抛异常的位置
这些位置就叫作安全点(safepoint)。 用户程序执行时并非在代码指令流的任意位置都能够在停顿下来开始垃圾收集,而是必须是执行到安全点才能够暂停。

26. Can you talk about the garbage collection process of the CMS collector in detail?

CMS收集齐的垃圾收集分为四步:
初始标记(CMS initial mark):单线程运行,需要Stop The World,标记GC Roots能直达的对象。	
并发标记((CMS concurrent mark):无停顿,和用户线程同时运行,从GC Roots直达对象开始遍历整个对象图。
重新标记(CMS remark):多线程运行,需要Stop The World,标记并发标记阶段产生对象。
并发清除(CMS concurrent sweep):无停顿,和用户线程同时运行,清理掉标记阶段标记的死亡的对象。

27. Do you understand the G1 garbage collector?

Garbage First(简称G1)收集器是垃圾收集器的一个颠覆性的产物,它开创了局部收集的设计思路和基于Region的内存布局形式。
虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异。以前的收集器分代是划分新生代、老年代、持久代等。
G1把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理。
G1 Heap Regions
	这样就避免了收集整个堆,而是按照若干个Region集进行收集,同时维护一个优先级列表,跟踪各个Region回收的“价值,优先收集价值高的Region。
G1收集器的运行过程大致可划分为以下四个步骤:
	初始标记(initial mark),标记了从GC Root开始直接关联可达的对象。STW(Stop the World)执行。
	并发标记(concurrent marking),和用户线程并发执行,从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象、
	最终标记(Remark),STW,标记再并发标记过程中产生的垃圾。
	筛选回收(Live Data Counting And Evacuation),制定回收计划,选择多个Region 构成回收集,把回收集中Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。需要STW。

28. With CMS, why introduce G1?

优点: CMS最主要的优点在名字上已经体现出来——并发收集、低停顿。
缺点: CMS同样有三个明显的缺点。
	标记算法会导致内存碎片比较多。
	CMS的并发能力比较依赖于CPU资源,并发回收时垃圾收集线程可能会抢占用户线程的资源,导致用户程序性能下降。
	并发清除阶段,用户线程依然在运行,会产生所谓的“浮动垃圾”(Floating Garbage),本次垃圾收集无法处理浮动垃圾,必须到下一次垃圾收集才能处理。如果浮动垃圾太多,会触发新的垃圾回收,导致性能降低。G1主要解决了内存碎片过多的问题。

29. What garbage collector do you use online? Why use it?

Parallel(Throughput)收集器:这是一个并行的垃圾回收器,适用于具有多个CPU核心的服务器应用程序。Parallel收集器旨在提高吞吐量,即在单位时间内完成的有效工作。Parallel收集器在年轻代和老年代都进行并行垃圾回收。由于Parallel收集器可以充分利用多核CPU资源,因此它在服务器端应用程序中非常常用。
Concurrent Mark-Sweep(CMS)收集器:这是一个并发的垃圾回收器,适用于对延迟敏感的应用程序,如网站后台服务、交互式应用等。CMS收集器的目标是减少垃圾回收引起的应用程序线程暂停时间。CMS收集器在回收老年代垃圾时,大部分工作与应用程序线程并发执行。因此,CMS收集器在延迟敏感型应用程序中非常常用。
Garbage First(G1)收集器:这是一个面向区域的垃圾回收器,适用于具有大内存容量的服务器应用程序。G1收集器将堆内存划分为多个区域,并优先回收垃圾最多的区域。G1收集器旨在降低延迟,同时保持较高的吞吐量。由于G1收集器在处理大内存和高吞吐量场景下表现良好,因此它在企业级应用程序中非常常用。

30. How should the garbage collector be selected?

可以根据自己的业务情况具体分析。

31. Must objects be allocated in the heap? Have you learned about escape analysis techniques?

JVM中对象是可以在栈中进行分配,但是前提是需要判断逃逸状态。
逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他方法或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。通俗点讲,如果一个对象的指针被多个方法或者线程引用时,那么我们就称这个对象的指针发生了逃逸

おすすめ

転載: blog.csdn.net/weixin_47068446/article/details/132176880