杭州建设培训中心网站,济南建设工程有限公司,南阳网站推广公司,成都住房和城乡建设局 网站前言整理了10个经典又容易被疏忽的JVM面试题#xff0c;谢谢阅读#xff0c;大家加油哈.1. 对象一定分配在堆中吗#xff1f;有没有了解逃逸分析技术#xff1f;「对象一定分配在堆中吗#xff1f;」 不一定的#xff0c;JVM通过「逃逸分析」#xff0c;那些逃不出方法的… 前言整理了10个经典又容易被疏忽的JVM面试题谢谢阅读大家加油哈.1. 对象一定分配在堆中吗有没有了解逃逸分析技术「对象一定分配在堆中吗」 不一定的JVM通过「逃逸分析」那些逃不出方法的对象会在栈上分配。「什么是逃逸分析」逃逸分析(Escape Analysis)是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。❝逃逸分析是指分析指针动态范围的方法它同编译器优化原理的指针分析和外形分析相关联。当变量或者对象在方法中分配后其指针有可能被返回或者被全局引用这样就会被其他方法或者线程所引用这种现象称作指针或者引用的逃逸(Escape)。通俗点讲如果一个对象的指针被多个方法或者线程引用时那么我们就称这个对象的指针发生了逃逸。❞「一个逃逸分析的例子」/*** author 捡田螺的小男孩*/
public class EscapeAnalysisTest {public static Object object;//StringBuilder可能被其他方法改变逃逸到了方法外部。public StringBuilder escape(String a, String b) {//公众号捡田螺的小男孩StringBuilder str new StringBuilder();str.append(a);str.append(b);return str;}//不直接返回StringBuffer,不发生逃逸public String notEscape(String a, String b) {StringBuilder str new StringBuilder();str.append(a);str.append(b);return str.toString();}//外部线程可见object,发生逃逸public void objectEscape(){object new Object();}//仅方法内部可见,不发生逃逸public void objectNotEscape(){Object object new Object();}
}
「逃逸分析的好处」❝栈上分配可以降低垃圾收集器运行的频率。同步消除如果发现某个对象只能从一个线程可访问那么在这个对象上的操作可以不需要同步。标量替换把对象分解成一个个基本类型并且内存分配不再是分配在堆上而是分配在栈上。这样的好处有一、减少内存使用因为不用生成对象头。二、程序内存回收效率高并且GC频率也会减少。❞2.虚拟机为什么使用元空间替换了永久代「什么是元空间什么是永久代为什么用元空间代替永久代」 我们先回顾一下「方法区」吧,看看虚拟机运行时数据内存图如下:❝方法区和堆一样是各个线程共享的内存区域它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。❞「什么是永久代它和方法区有什么关系呢」❝如果在HotSpot虚拟机上开发、部署很多程序员都把方法区称作永久代。可以说方法区是规范永久代是Hotspot针对该规范进行的实现。在Java7及以前的版本方法区都是永久代实现的。❞「什么是元空间它和方法区有什么关系呢」❝对于Java8HotSpots取消了永久代取而代之的是元空间(Metaspace)。换句话说就是方法区还是在的只是实现变了从永久代变为元空间了。❞「为什么使用元空间替换了永久代」永久代的方法区和堆使用的物理内存是连续的。「永久代」是通过以下这两个参数配置大小的~-XX:PremSize设置永久代的初始大小-XX:MaxPermSize: 设置永久代的最大值默认是64M对于「永久代」如果动态生成很多class的话就很可能出现「java.lang.OutOfMemoryError: PermGen space错误」因为永久代空间配置有限嘛。最典型的场景是在web开发比较多jsp页面的时候。JDK8之后方法区存在于元空间(Metaspace)。物理内存不再与堆连续而是直接存在于本地内存中理论上机器「内存有多大元空间就有多大」。可以通过以下的参数来设置元空间的大小❝-XX:MetaspaceSize初始空间大小达到该值就会触发垃圾收集进行类型卸载同时GC会对该值进行调整如果释放了大量的空间就适当降低该值如果释放了很少的空间那么在不超过MaxMetaspaceSize时适当提高该值。-XX:MaxMetaspaceSize最大空间默认是没有限制的。-XX:MinMetaspaceFreeRatio在GC之后最小的Metaspace剩余空间容量的百分比减少为分配空间所导致的垃圾收集-XX:MaxMetaspaceFreeRatio在GC之后最大的Metaspace剩余空间容量的百分比减少为释放空间所导致的垃圾收集❞「所以为什么使用元空间替换永久代」❝表面上看是为了避免OOM异常。因为通常使用PermSize和MaxPermSize设置永久代的大小就决定了永久代的上限但是不是总能知道应该设置为多大合适, 如果使用默认值很容易遇到OOM错误。当使用元空间时可以加载多少类的元数据就不再由MaxPermSize控制, 而由系统的实际可用空间来控制啦。❞3.什么是Stop The World ? 什么是OopMap什么是安全点进行垃圾回收的过程中会涉及对象的移动。为了保证对象引用更新的正确性必须暂停所有的用户线程像这样的停顿虚拟机设计者形象描述为「Stop The World」。在HotSpot中有个数据结构映射表称为「OopMap」。一旦类加载动作完成的时候HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来记录到OopMap。在即时编译过程中也会在「特定的位置」生成 OopMap记录下栈上和寄存器里哪些位置是引用。这些特定的位置主要在1.循环的末尾非 counted 循环2.方法临返回前 / 调用方法的call指令后3.可能抛异常的位置这些位置就叫作「安全点(safepoint)。」 用户程序执行时并非在代码指令流的任意位置都能够在停顿下来开始垃圾收集而是必须是执行到安全点才能够暂停。4.说一下JVM 的主要组成部分及其作用JVM包含两个子系统和两个组件分别为❝Class loader(类装载子系统)Execution engine(执行引擎子系统)Runtime data area(运行时数据区组件)Native Interface(本地接口组件)。❞「Class loader(类装载)」 根据给定的全限定名类名(如java.lang.Object)来装载class文件到运行时数据区的方法区中。「Execution engine执行引擎」执行class的指令。「Native Interface(本地接口)」 与native lib交互是其它编程语言交互的接口。「Runtime data area(运行时数据区域)」即我们常说的JVM的内存。❝首先通过编译器把 Java源代码转换成字节码Class loader(类装载)再把字节码加载到内存中将其放在运行时数据区的方法区内而字节码文件只是 JVM 的一套指令集规范并不能直接交给底层操作系统去执行因此需要特定的命令解析器执行引擎Execution Engine将字节码翻译成底层系统指令再交由 CPU 去执行而这个过程中需要调用其他语言的本地库接口Native Interface来实现整个程序的功能。❞5. 守护线程是什么守护线程和非守护线程的区别是守护线程的作用是「守护线程」是区别于用户线程哈「用户线程」即我们手动创建的线程而守护线程是程序运行的时候在后台提供一种「通用服务的线程」。垃圾回收线程就是典型的守护线程。「守护线程和非守护线程的区别是」 我们通过例子来看吧~ /*** 关注公众号捡田螺的小男孩*/public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()- {while (true) {try {Thread.sleep(1000);System.out.println(我是子线程(用户线程.I am running);} catch (Exception e) {}}});//标记为守护线程t1.setDaemon(true);//启动线程t1.start();Thread.sleep(3000);System.out.println(主线程执行完毕...);}
运行结果可以发现标记为守护线程后「主线程销毁停止守护线程一起销毁」。我们再看下去掉 t1.setDaemon(true)守护标记的效果 public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()- {while (true) {try {Thread.sleep(1000);System.out.println(我是子线程(用户线程.I am running);} catch (Exception e) {}}});//启动线程t1.start();Thread.sleep(3000);System.out.println(主线程执行完毕...);}
所以当主线程退出时JVM 也跟着退出运行守护线程同时也会被回收即使是死循环。如果是用户线程它会一直停在死循环跑。这就是「守护线程和非守护线程的区别」啦。守护线程拥有「自动结束自己生命周期的特性」非守护线程却没有。如果垃圾回收线程是非守护线程当JVM 要退出时由于垃圾回收线程还在运行着导致程序无法退出这就很尴尬。这就是「为什么垃圾回收线程需要是守护线程啦」。6.WeakHashMap了解过嘛它是怎么工作的「WeakHashMap」 类似HashMap 不同点在WeakHashMap的key是「弱引用」的key。谈到「弱引用」在这里回顾下四种引用吧❝强引用Object objnew Object()这种只要强引用关系还存在垃圾收集器就永远不会回收掉被引用的对象。软引用: 一般情况不会回收如果内存不够要溢出时才会进行回收弱引用当垃圾收集器开始工作无论当前内存是否足够都会回收掉只被弱引用关联的对象。虚引用为一个对象设置虚引用的唯一目的只是为了能在这个对象被回收时收到一个系统的通知。❞正是因为WeakHashMap使用的是弱引用「它的对象可能随时被回收」。WeakHashMap 类的行为部分「取决于垃圾回收器的动作」,调用两次size()方法返回不同值调用两次isEmpty()一次返回true一次返回false都是「可能的」。WeakHashMap「工作原理」回答这两点❝WeakHashMap具有弱引用的特点随时被回收对象。发生GC时WeakHashMap是如何将Entry移除的呢❞WeakHashMap内部的Entry继承了WeakReference即弱引用所以就具有了弱引用的特点「随时可能被回收」。看下源码哈 private static class EntryK,V extends WeakReferenceObject implements Map.EntryK,V {V value;final int hash;EntryK,V next;/*** Creates new entry.*/Entry(Object key, V value,ReferenceQueueObject queue,int hash, EntryK,V next) {super(key, queue);this.value value;this.hash hash;this.next next;}......
「WeakHashMap是如何将Entry移除的」 GC每次清理掉一个对象之后引用对象会放到ReferenceQueue的接着呢遍历queue进行删除。WeakHashMap的增删改查操作就是直接/间接调用expungeStaleEntries()方法达到及时清除过期entry的目的。可以看下expungeStaleEntries源码哈 /*** Expunges stale entries from the table.*/private void expungeStaleEntries() {for (Object x; (x queue.poll()) ! null; ) {synchronized (queue) {SuppressWarnings(unchecked)EntryK,V e (EntryK,V) x;int i indexFor(e.hash, table.length);EntryK,V prev table[i];EntryK,V p prev;while (p ! null) {EntryK,V next p.next;if (p e) {if (prev e)table[i] next;elseprev.next next;// Must not null out e.next;// stale entries may be in use by a HashIteratore.value null; // Help GCsize--;break;}prev p;p next;}}}}
7. 是否了解Java语法糖嘛说下12种Java中常用的语法糖语法糖Syntactic Sugar也称糖衣语法让程序更加简洁有更高的可读性。Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等12种。语法糖一、switch 支持 String 与枚举语法糖二、 泛型语法糖三、 自动装箱与拆箱语法糖四 、 方法变长参数语法糖五 、 枚举语法糖六 、 内部类语法糖七 、条件编译语法糖八 、 断言语法糖九 、 数值字面量语法糖十 、 for-each语法糖十一 、 try-with-resource语法糖十二、Lambda表达式感兴趣的朋友可以看下这篇文章哈不了解这12个语法糖别说你会Java8. 什么是指针碰撞什么是空闲列表什么是TLAB❝一般情况下JVM的对象都放在堆内存中发生逃逸分析除外。当类加载检查通过后Java虚拟机开始为新生对象分配内存。如果Java堆中内存是绝对规整的所有被使用过的的内存都被放到一边空闲的内存放到另外一边中间放着一个指针作为分界点的指示器所分配内存仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的实例这种分配方式就是“「指针碰撞」”。❞❝如果Java堆内存中的内存并不是规整的已被使用的内存和空闲的内存相互交错在一起不可以进行指针碰撞啦虚拟机必须维护一个列表记录哪些内存是可用的在分配的时候从列表找到一块大的空间分配给对象实例并更新列表上的记录这种分配方式就是“「空闲列表」”❞对象创建在虚拟机中是非常频繁的行为可能存在线性安全问题。如果一个线程正在给A对象分配内存指针还没有来的及修改同时另一个为B对象分配内存的线程仍引用这之前的指针指向这就出「问题」了。❝可以把内存分配的动作按照线程划分在不同的空间之中进行每个线程在Java堆中预先分配一小块内存,这就是「TLABThread Local Allocation Buffer本地线程分配缓存」 。虚拟机通过-XX:UseTLAB设定它的。❞9.CMS垃圾回收器的工作过程CMS收集器和G1收集器的区别。CMS(Concurrent Mark Sweep) 收集器是一种以获得最短回收停顿时间为目标的收集器标记清除算法运作过程「初始标记并发标记重新标记并发清除」收集结束会产生大量空间碎片。如图下图来源互联网「CMS收集器和G1收集器的区别」CMS收集器是老年代的收集器可以配合新生代的Serial和ParNew收集器一起使用G1收集器收集范围是老年代和新生代不需要结合其他收集器使用CMS收集器以最小的停顿时间为目标的收集器G1收集器可预测垃圾回收的停顿时间CMS收集器是使用“标记-清除”算法进行的垃圾回收容易产生内存碎片G1收集器使用的是“标记-整理”算法进行了空间整合降低了内存空间碎片。10.JVM 调优JVM调优其实就是通过调节JVM参数即对垃圾收集器和内存分配的调优以达到更高的吞吐和性能。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:ParallelGCThreads20配置并行收集器的线程数-XX:UseConcMarkSweepGC设置年老代为并发收集。-XX:CMSFullGCsBeforeCompaction5 由于并发收集器不对内存空间进行压缩、整理所以运行一段时间以后会产生“碎片”使得运行效率降低。此值设置运行5次GC以后对内存空间进行压缩、整理。-XX:UseCMSCompactAtFullCollection打开对年老代的压缩。可能会影响性能但是可以消除碎片❞「辅助信息相关」❝-XX:PrintGCDetails 打印GC详细信息-XX:HeapDumpOnOutOfMemoryError让JVM在发生内存溢出的时候自动生成内存快照,排查问题用-XX:DisableExplicitGC禁止系统System.gc()防止手动误触发FGC造成问题.-XX:PrintTLAB 查看TLAB空间的使用情况❞参考与感谢[JVM的逃逸分析] (https://segmentfault.com/a/1190000023475016)[面试官 | JVM 为什么使用元空间替换了永久代] (https://my.oschina.net/u/3471412/blog/4426430)[Metaspace 之一Metaspace整体介绍永久代被替换原因、元空间特点、元空间内存查看分析方法] (https://www.cnblogs.com/duanxz/p/3520829.html)[深入理解WeakHashmap] (https://blog.51cto.com/mikewang/880775)[一文搞懂WeakHashMap工作原理] (https://baijiahao.baidu.com/s?id1666368292461068600wfrspiderforpc)[谈谈什么是守护线程及作用] (https://www.cnblogs.com/quanxiaoha/p/10731361.html)[浅析java中的TLAB] (https://www.jianshu.com/p/8be816cbb5ed)《深入理解Java虚拟机》
往期推荐
1.3w字一文详解死锁聊聊Spring事务失效的12种场景太坑了不重启JVM替换掉已经加载的类偷天换日