jvm面试题
jvm内存区域
Q:jvm内存区域怎么划分的?(高频)
答:
堆内存(线程共享):所有线程共享的一块区域,垃圾收集器管理的主要区域。主要存储对象、数组。
Java 堆中还可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等,默认情况下新生代按照8:1:1的比例来分配。
程序计数器: Java 线程私有,它可以看做是当前线程所执行的字节码的行号指示器。
虚拟机栈(栈内存):Java线程私有,虚拟机展描述的是Java方法执行的内存模型:每个方法在执行的时候,都会创建一个栈帧用于存储局部变量、操作数、动态链接、方法出口等信息;每个方法调用都意味着一个栈帧在虚拟机栈中入栈到出栈的过程;
本地方法栈:和Java虚拟机栈的作用类似,区别是该区域为 jvm提供使用 native 方法的服务。
方法区(线程共享):各个线程共享的一个区域,用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
运行时常量池:是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。
思考一下平常写代码时,一个类里面会用到哪些东西,就能大概记忆理解jvm内存区域了。
Q:在 String str = new String("abc"); 创建了几个对象?
如果 abc 字符串之前没有用过,那就是 创建了两个对象,一个是new String 创建的一个新的对象,一个是常量“abc”对象的内容创建出的一个新的String对象,
如果 java 的字符串常量缓冲区(字符串池,字符串常量池)里面有abc字符串,那就只创建一个新的对象 new String("abc")。
参考资料: https://blog.csdn.net/qq_36470686/article/details/83444483/
Q:讲一下栈帧的结构。
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
Q:成员变量,是在栈还是在堆里面?
类的成员变量都在堆上。
成员变量(也叫属性变量)是对象实例的一部分,对象存储在堆里面,因此成员变量也是存储在jvm堆里面的。
只有方法里面定义的基础变量、在方法里面定义的引用、其他对象的引用放在栈上
Q:局部变量,是存储在哪里的?
局部变量,是在虚拟机栈里面的。虚拟机栈包含栈帧,栈帧里面有局部变量表。
Q:为什么jdk8用元空间替换了永久代?
元空间与永久代之间最大的区别在于:元空间不再与堆连续,并且是存在于本地内存(Native memory)中的。
永久代是一段连续的内存空间,我们在JVM启动之前可以通过设置-XX:MaxPermSize的值来控制永久代的大小,32位机器默认的永久代的大小为64M,64位的机器则为85M。
永久代的垃圾回收和老年代的垃圾回收是绑定的,一旦其中一个区域被占满,这两个区都要进行垃圾回收。
显然这种设计并不是一个好的主意,由于我们可以通过‑XX:MaxPermSize设置永久代的大小,一旦类的元数据超过了设定的大小,程序就会耗尽内存,并出现内存溢出错误 (java.lang.OutOfMemoryError: PermGen space)。
元空间存在于本地内存,意味着只要本地内存足够,它就不会OOM,不会出现像永久代中的java.lang.OutOfMemoryError: PermGenspace。
参考资料: https://blog.csdn.net/fuzhongmin05/article/details/123878252
Q:垃圾回收主要是在哪个jvm区域?
堆。人称"垃圾堆"
垃圾收集器
Q:如何确定一个对象是不是垃圾?
可达性分析算法。垃圾收集器所面临的的第一个问题是如何确定一个对象是不是垃圾。通常认为一个不可能再经过任何途径被使用的对象是垃圾,目前常用的找出这一类无法使用的对象的方法是可达性分析算法。
Q:什么是Stop the World(STW)?
Stop一the一World,简称STW,指的是Gc事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。
停顿的原因:分析工作必须在一个能确保一致性的快照中进行。一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上。如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证。
Q:垃圾收集器有哪些?
垃圾收集器:Serial收集器、ParNew收集器、Parallel Scavenge收集器、Serial Old收集器、Parallel Old收集器、CMS(Concurrent Mark Sweep并发标记-清除)收集器、G1收集器。
缩写关联记忆法:GC3P3S,G:Garbage(垃圾), G1 , C:Collector(收集),P:Parallel(并行),S:Serial (顺序/连续)
Jdk11推出了ZGC,性能超强,号称可以达到10ms 以下的 GC 停顿,承诺在数TB的堆上具有非常低的暂停时间。
联想记忆法: ZGC, Z(最强)GC。
Q:这些垃圾收集器有什么不同,主要使用哪些垃圾收集算法 ?
Serial 垃圾收集器(单线程、复制算法), ParNew 垃圾收集器(Serial+多线程),Parallel Scavenge 收集器(多线程复制算法、高效),Serial Old 收集器(单线程标记整理算法 ),Parallel Old 收集器(多线程标记整理算法) ,CMS 收集器(多线程标记清除算法)。
CMS垃圾收集器
Q:CMS垃圾收集器采用了哪些算法?
标记清除算法。
Q:CMS垃圾收集器,有哪些缺陷?
(1)CMS收集器对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致引用程序变慢,总吞吐量下降。CMS默认启动的回收线程数是:(CPU数量+3) / 4
(2)CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,称为“浮动垃圾”,CMS 无法在本次收集中处理它们,只好留待下一次GC时将其清理掉。
(3)由于垃圾收集阶段会产生“浮动垃圾”,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分内存空间提供并发收集时的程序运作使用。在默认设置下,CMS收集器在老年代使用了68%的空间时就会被激活,也可以通过参数-XX:CMSInitiatingOccupancyFraction的值来提高触发百分比,以降低内存回收次数提高性能。要是CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以参数 -XX:CMSInitiatingOccupancyFraction 设置的过高将会很容易导致 “Concurrent Mode Failure” 失败,性能反而降低。
(4)CMS是基于“标记-清除”算法实现的收集器,会产生大量不连续的内存碎片。当老年代空间碎片太多时,如果无法找到一块足够大的连续内存存放对象时,将不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:UseCMSCompactAtFullCollection开关参数,用于在Full GC之后增加一个碎片整理过程,还可通过-XX:CMSFullGCBeforeCompaction参数设置执行多少次不压缩的Full GC之后,跟着来一次碎片整理过程。
参考资料: https://blog.csdn.net/a745233700/article/details/121724998
G1垃圾收集器
Q:G1 收集器,有哪些优势?
G1 收集器两个最突出的改进是:
(1)基于标记-整理算法,不产生内存碎片。
(2)可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
(3)在G1之前其他的收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。
G1收集器,它将整个java堆划分为多个大小相等的独立区域,虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是Region(不需要连续)的集合。
Q:讲一下G1 垃圾回收器工作流程。
初始标记(Initial Marking):这阶段仅仅只是标记GC Roots能直接关联到的对象并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确的可用的Region中创建新对象,这阶段需要停顿线程,但是耗时很短。而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
并发标记(Concurrent Marking):从GC Roots开始对堆的对象进行可达性分析,递归扫描整个堆里的对象图,找出存活的对象,这阶段耗时较长,但是可以与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
最终标记(Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB 记录。
筛选回收(Live Data Counting and Evacuation):负责更新 Region 的统计数据,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划。可以自由选择多个Region来构成会收集,然后把回收的那一部分Region中的存活对象复制到空的Region中,在对那些Region进行清空。
除了并发标记外,其余过程都要 STW(Stop The World).
Q:讲一下G1垃圾收集器的GC。
(1)年轻代GC(Young GC)
(2)全局并发标记(global concurrent marking)
(3)混合回收(Mixed GC)
G1 垃圾回收流程小结:Young CG 和 Mixed GC,是G1回收空间的主要活动。
当应用开始运行时,堆内存可用空间还比较大,只会在年轻代满时,触发年轻代收集;随着老年代内存增长,当到达 IHOP 阈值 -XX:InitiatingHeapOccupancyPercent(老年代占整堆比,默认45%) 时,G1开始着手准备收集老年代空间。
首先经历并发标记周期,识别出高收益的老年代分区。但随后G1并不会马上开启一次混合收集,而是让应用线程先运行一段时间,等待触发一次年轻代收集,在这次STW中,G1将开始整理混合收集周期。接着再次让应用线程运行,当接下来的几次年轻代收集时,将会有老年代分区加入到CSet中,即触发混合收集,这些连续多次的混合收集称为混合收集。
详情见: https://blog.csdn.net/a745233700/article/details/121724998
Q:G1垃圾收集器会不会发生 Full GC ?
G1在以下场景中会触发 Full GC,同时会在日志中记录to-space-exhausted以及Evacuation Failure:
(1)从年轻代分区拷贝存活对象时,无法找到可用的空闲分区
(2)从老年代分区转移存活对象时,无法找到可用的空闲分区
(3)分配巨型对象时在老年代无法找到足够的连续分区
由于G1的应用场合往往堆内存都比较大,所以Full GC的收集代价非常昂贵,应该避免Full GC的发生。
Q:讲一下G1垃圾收集器在并发收集阶段的算法。G1垃圾收集器,是怎么确认哪些对象是垃圾的?
三色标记算法。
三色标记算法是并发收集阶段的重要算法,它是描述追踪式回收器的一种有用的方法,利用它可以推演回收器的正确性。 首先,我们将对象分成三种类型的。
黑色:根对象,或者该对象与它的子对象都被扫描了。
灰色:对象本身被扫描,但还没扫描完该对象中的子对象。
白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象。
详情见:https://blog.csdn.net/a745233700/article/details/121724998
Q:CMS收集器和G1收集器的区别?
- 区别一:使用范围不一样
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用。
G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用 - 区别二: STW的时间
CMS收集器以最小的停顿时间为目标的收集器。
G1收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型) - 区别三: 垃圾碎片
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片。
G1收集器使用的是“标记-整理”算法,进行了空间整合,没有内存空间碎片。 - 区别四:垃圾回收的过程不一样
CMS收集器:1.初始标记 2.并发标记 3.重新标记 4.并发清除。
G1收集器:1.初始标记 2.并发标记 3.最终标记 4.筛选回收。(成语记忆:G1有始有终) - 区别五: CMS会产生浮动垃圾
CMS产生浮动垃圾过多时会退化为serial old,效率低,而G1没有浮动垃圾。
详情见: https://blog.csdn.net/qq_39552268/article/details/122584628
垃圾收集算法
Q:垃圾收集算法有哪些?(高频)
标记-清除算法、复制算法、标记-整理算法、分代收集算法
Q:标记清除算法和标记整理算法,有什么区别?(高频)
标记清除算法:标记出所有需要回收的对象,然后回收被标记的对象所占用的空间。
标记清除算法缺点是:清除后内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。
标记整理算法:标记整理和标记清除算法很像,唯一不同的是,当标记完成后,不是清理掉需要回收的对象,而是将所有存活的对象向一端移动,然后将边界以外的内存全部清理掉,这样可以有效避免空间碎片的产生。
内存分配与回收策略
Q:内存分配有哪些规则?
答:
有新生代和老年代。新生代又分为 Eden区、ServivorFrom、ServivorTo三个区。
注:Eden英文意指伊甸园。出生在伊甸园。
(1)新创建的对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。
(2)大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
(3)长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
(4)动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
(5)空间分配担保。每次进行Minor GC时,jvm会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Minor GC,如果false则进行Full GC。
Q:MinorGC(Young GC)、MajorGC(Full GC)的区别是什么?
Minor GC:简单理解就是发生在年轻代的GC。
Minor GC 和 Young GC, “新生代”也可以称之为“年轻代”, 这两个名词是等价的。
联想记忆法:小(min)年轻。
Minor GC的触发条件为:
当产生一个新对象,新对象优先在Eden区分配。
如果Eden区放不下这个对象,虚拟机会使用复制算法发生一次Minor GC,清除掉无用对象,同时将存活对象移动到Survivor的其中一个区(fromspace区或者tospace区)。
如果新生对象在Eden区无法分配空间时,此时发生Minor GC。
发生MinorGC,对象会从Eden区进入Survivor区,如果Survivor区放不下从Eden区过来的对象时,此时会使用分配担保机制将对象直接移动到年老代。
Major GC的触发条件:当年老代空间不够用的时候,虚拟机会使用“标记—清除”或者“标记—整理”算法清理出连续的内存空间,分配对象使用。出现Major GC通常会出现至少一次Minor GC?
Major GC又称为Full GC。
联想记忆法:老(年代)专家(Major)。
Q:Young GC和Full GC分别是什么?有什么区别?
Young GC(新生代GC):指发生在新生代的垃圾收集动作,新生代中的对象朝生夕死,所以 Young GC(Minor GC) 非常频繁,回收速度也比较快。
Full GC(老年代GC):指发生在老年代的GC,速度一般比 Young GC(Minor GC) 慢十倍以上。Full GC 会 Stop-The-World。
详情见: https://blog.csdn.net/dl674756321/article/details/108130050
Q:年轻代,老年代,分别采用哪些垃圾回收算法?为什么要采用这些算法??(高频)
年轻代的垃圾回收是Minor GC,采用的垃圾回收算法是复制算法。因为新生代中每次垃圾回收都要回收大部分对象,只有少量存活对象存活,也就是说要复制的存活对象比较少。
老年代的垃圾回收是Full GC,采用的垃圾回收算法是标记--清除算法。老年代因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存.
Q:讲一下MinorGC (Young GC)的过程。
年轻代的垃圾回收是Minor GC。
MinorGC 采用复制算法。
MinorGC 的过程(复制->清空->互换)。
(1):eden、servicorFrom 复制到 ServicorTo,年龄+1。
首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 ServicorTo 不够位置了就放到老年区);
(2):清空 eden、servicorFrom。
然后,清空 Eden 和 ServicorFrom 中的对象;
(3):ServicorTo 和 ServicorFrom 互换。
最后,ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。
FullGC
Q:发生FullGC的原因有哪些?
Full GC的触发条件大致情况有以下几种情况:
- 程序执行了System.gc(); //建议jvm执行fullgc,并不一定会执行
- 执行了jmap -histo:live pid命令。 //这个会立即触发fullgc
- 在执行minor gc的时候进行的一系列检查:
a. 执行Minor GC的时候,JVM会检查老年代中最大连续可用空间是否大于了当前新生代所有对象的总大小。
b. 如果大于,则直接执行Minor GC(这个时候执行是没有风险的)。
c. 如果小于了,JVM会检查是否开启了空间分配担保机制,如果没有开启则直接改为执行Full GC。
d. 如果开启了,则JVM会检查老年代中最大连续可用空间是否大于了历次晋升到老年代中的平均大小,如果小于则执行改为执行Full GC。
如果大于则会执行Minor GC,如果Minor GC执行失败则会执行Full GC - 使用了大对象。 //大对象会直接进入老年代
- 在程序中长期持有了对象的引用。 //对象年龄达到指定阈值也会进入老年代
Q:频繁发生FullGC,会有什么后果?
Stop The World。此时所有的用户线程都会暂停运行,会发生卡顿现象。
Q:如何分析频繁发生FullGC的原因,以及解决方案?
可以通过命令jmap -dump得到dump文件。
也可以设置jvm的参数-XX:+HeapDumpBeforeFullGC和 -XX:HeapDumpOnOutOfMemoryError ,自动生成dump文件。
然后对dump文件进行分析,看是哪些对象占用了太多的内存。
类的加载机制
Q:java 中都有哪些引用类型?
Q:ClassLoader类加载是发生在哪块内存区域?
答:将Java类的.class文件中的二进制数据读入到内存中,放置在运行时数据区的方法区内。(存疑)
Q:讲一下类加载的过程。(高频)
虚拟机类加载机制的生命周期:加载(Loading),连接(Link),初始化(Initialization),使用(Using),卸载(Unloading)。 (首字母记忆:LLIUU)
其中,验证(Verification),准备(Preparation),解析(Resolution),这三个过程称为“连接”(Link)。
Q:类的加载器ClassLoader有哪些?(高频)
JVM提供了3种类加载器: BootstrapClassLoader、 ExtClassLoader、 AppClassLoader分别加载Java核心类库、扩展类库以及应用的类路径( CLASSPATH)下的类库。
Q:类的加载机制是怎样的?(高频)
双亲委派机制。
当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父加载器无法完成加载任务时,才会尝试自己来加载。
采用双亲委派模型的一个好处是保证使用不同类加载器最终得到的都是同一个对象,这样就可以保证Java 核心库的类型安全,比如,加载位于rt.jar包中的 java.lang.Object类,不管是哪个加载器加载这个类,最终都是委托给顶层的BootstrapClassLoader来加载的,这样就可以保证任何的类加载器最终得到的都是同样一个Object对象。
Q:类加载机制中的验证的过程是怎样的?
(1)文件格式验证。
(2)元数据验证。
(3)字节码验证。
(4)符号引用验证。
Q:类加载机制中的解析的过程是怎样的?
(1)类或接口的解析
(2)字段解析
(3)类方法解析
(4)接口方法解析
jvm调优
Q:jvm有哪些参数?
-XX:+HeapDumpOnOutOfMemoryError 在出现异常的情况下将内存快照dump出来,这个参数非常有用。
-XmsSize(最小堆内存),m表示memory,s代表smallest
-XmxSize(最大堆内存),x代表max
-XmnSize(分配给年轻代) ,n可以理解成young
-XssSize 栈大小
-XX:+PrintGCDetails 输出详细GC日志
-XX:SurvivorRatio=ratio Eden区和survivor区的比例
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:-UseTLAB 是否使用本地线程分配缓冲
-XX:MaxPermSize 方法区的最大值(JDK<=1.7 1.8将方法区移去,增加了metaspaces)
-XX:PermSize 方法区的大小
java内存模型
Q:java的内存模型是怎样的?
Java 虚拟机规范中试图定义一种 Java 内存模型(Java Memory Model, JMM)来屏蔽掉各层硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
Java 内存模型规定了所有的变量都存储在主内存(Main Memory)中。每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在主内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间的变量值的传递均需要通过主内存来完成
待补充.
参考资料:
《深入理解java虚拟机》
《深入理解java虚拟机》学习笔记