jvm面试题_jvm面试题及答案2020 - CSDN
精华内容
参与话题
  • 24个Jvm面试题总结及答案

    千次阅读 2019-11-04 19:51:21
    1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在...
      

    1.什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

    Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

    2.Java内存结构?


    v2-0b7ead804a1c0ad99a5c72d53775227f_b.jpg


    方法区和对是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。

    • Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
    • 方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    • 程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。
    • JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
    • 本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

    3.解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法

    通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。

            String str = new String("hello");
          

    上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量是放在方法区的。

    补充1:较新版本的Java(从Java 6的某个更新开始)中,由于JIT编译器的发展和”逃逸分析”技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一定分配在堆上这件事情已经变得不那么绝对了。

    补充2:运行时常量池相当于Class文件常量池具有动态性,Java语言并不要求常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String类的intern()方法就是这样的。 看看下面代码的执行结果是什么并且比较一下Java 7以前和以后的运行结果是否一致。

            String s1 = new StringBuilder("go")
        .append("od").toString();
    System.out.println(s1.intern() == s1);
    String s2 = new StringBuilder("ja")
        .append("va").toString();
    System.out.println(s2.intern() == s2);
          

    4.对象分配规则

    • 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
    • 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
    • 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
    • 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
    • 空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。

    5.什么是类的加载

    类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

    6.类加载器


    v2-b1eea510d18bb0f0254d251be6ee5127_b.jpg


    • 启动类加载器:Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库
    • 扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
    • 应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器

    7.描述一下JVM加载class文件的原理机制?

    答:JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。 由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。 类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:

    • Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
    • Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
    • System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

    8.描述一下JVM加载class文件的原理机制?

    JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。

    由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后JVM对类进行初始化,包括:

    • 1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;
    • 2)如果类中存在初始化语句,就依次执行这些初始化语句。

    类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。

    从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。下面是关于几个类加载器的说明:

    • Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar);
    • Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
    • System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。

    9.Java对象创建过程

    1.JVM遇到一条新建对象的指令时首先去检查这个指令的参数是否能在常量池中定义到一个类的符号引用。然后加载这个类(类加载过程在后边讲)

    2.为对象分配内存。一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法“本地线程缓冲分配(TLAB)”

    3.将除对象头外的对象内存空间初始化为0

    4.对对象头进行必要设置

    10.类的生命周期

    类的生命周期包括这几个部分,加载、连接、初始化、使用和卸载,其中前三部是类的加载的过程,如下图;

    v2-8343b3bcad60d84e3a74d2f922c84003_b.jpg


    • 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
    • 连接,连接又包含三块内容:验证、准备、初始化。 1)验证,文件格式、元数据、字节码、符号引用验证; 2)准备,为类的静态变量分配内存,并将其初始化为默认值; 3)解析,把类中的符号引用转换为直接引用
    • 初始化,为类的静态变量赋予正确的初始值
    • 使用,new出对象程序中使用
    • 卸载,执行垃圾回收

    11.Java对象结构

    Java对象由三个部分组成:对象头、实例数据、对齐填充。

    对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。

    实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)

    对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)

    12.Java对象的定位方式

    句柄池、直接指针。

    13.如何判断对象可以被回收?

    判断对象是否存活一般有两种方式:

    • 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法解决对象相互循环引用的问题。
    • 可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,不可达对象。

    14.JVM的永久代中会发生垃圾回收么?

    垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)

    15.引用的分类

    • 强引用:GC时不会被回收
    • 软引用:描述有用但不是必须的对象,在发生内存溢出异常之前被回收
    • 弱引用:描述有用但不是必须的对象,在下一次GC时被回收
    • 虚引用(幽灵引用/幻影引用):无法通过虚引用获得对象,用PhantomReference实现虚引用,虚引用用来在GC时返回一个通知。

    ###GC是什么?为什么要有GC? 答:GC是垃圾收集的意思,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法。Java程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显示的垃圾回收调用。 垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在Java诞生初期,垃圾回收是Java最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过境迁,如今Java的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常觉得iOS的系统比Android系统有更好的用户体验,其中一个深层次的原因就在于android系统中垃圾回收的不可预知性。

    补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量垃圾回收等方式。标准的Java进程既有栈又有堆。栈保存了原始型局部变量,堆保存了要创建的对象。Java平台对堆内存回收和再利用的基本算法被称为标记和清除,但是Java对其进行了改进,采用“分代式垃圾收集”。这种方法会跟Java对象的生命周期将堆内存划分为不同的区域,在垃圾收集过程中,可能会将对象移动到不同区域:

    • 伊甸园(Eden):这是对象最初诞生的区域,并且对大多数对象来说,这里是它们唯一存在过的区域。
    • 幸存者乐园(Survivor):从伊甸园幸存下来的对象会被挪到这里。
    • 终身颐养园(Tenured):这是足够老的幸存对象的归宿。年轻代收集(Minor-GC)过程是不会触及这个地方的。当年轻代收集不能把对象放进终身颐养园时,就会触发一次完全收集(Major-GC),这里可能还会牵扯到压缩,以便为大对象腾出足够的空间。 与垃圾回收相关的JVM参数:
    -Xms / -Xmx — 堆的初始大小 / 堆的最大大小 -Xmn — 堆中年轻代的大小 -XX:-DisableExplicitGC — 让System.gc()不产生任何作用 -XX:+PrintGCDetails — 打印GC的细节 -XX:+PrintGCDateStamps — 打印GC操作的时间戳 -XX:NewSize / XX:MaxNewSize — 设置新生代大小/新生代最大大小 -XX:NewRatio — 可以设置老生代和新生代的比例 -XX:PrintTenuringDistribution — 设置每次新生代GC后输出幸存者乐园中对象年龄的分布 -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:设置老年代阀值的初始值和最大值 -XX:TargetSurvivorRatio:设置幸存区的目标使用率

    16.判断一个对象应该被回收

    1.该对象没有与GC Roots相连

    2.该对象没有重写finalize()方法或finalize()已经被执行过则直接回收(第一次标记)、否则将对象加入到F-Queue队列中(优先级很低的队列)在这里finalize()方法被执行,之后进行第二次标记,如果对象仍然应该被GC则GC,否则移除队列。 (在finalize方法中,对象很可能和其他 GC Roots中的某一个对象建立了关联,finalize方法只会被调用一次,且不推荐使用finalize方法)

    17.回收方法区

    方法区回收价值很低,主要回收废弃的常量和无用的类。

    如何判断无用的类:

    1.该类所有实例都被回收(Java堆中没有该类的对象)

    2.加载该类的ClassLoader已经被回收

    3.该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方利用反射访问该类

    18.垃圾收集算法

    GC最基础的算法有三种: 标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。

    • 标记 -清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
    • 复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
    • 标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
    • 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

    19.垃圾回收器

    • Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。
    • ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
    • Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。
    • Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
    • CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
    • G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征

    20.GC日志分析

    摘录GC日志一部分(前部分为年轻代gc回收;后部分为full gc回收):

            2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 
    2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
          

    通过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen属于Parallel收集器。其中PSYoungGen表示gc回收前后年轻代的内存变化;ParOldGen表示gc回收前后老年代的内存变化;PSPermGen表示gc回收前后永久区的内存变化。young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少full gc的次数

    21.调优命令

    Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo

    • jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
    • jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
    • jmap,JVM Memory Map命令用于生成heap dump文件
    • jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看
    • jstack,用于生成java虚拟机当前时刻的线程快照。
    • jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。

    22.调优工具

    常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。

    • jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控
    • jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。
    • MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
    • GChisto,一款专业分析gc日志的工具

    23Minor GC与Full GC分别在什么时候发生?

    新生代内存不够用时候发生MGC也叫YGC,JVM内存不够的时候发生FGC

    24.你知道哪些JVM性能调优

    • 设定堆内存大小

    -Xmx:堆内存最大限制。

    • 设定新生代大小。 新生代不宜太小,否则会有大量对象涌入老年代

    -XX:NewSize:新生代大小

    -XX:NewRatio 新生代和老生代占比

    -XX:SurvivorRatio:伊甸园空间和幸存者空间的占比

    • 设定垃圾回收器 年轻代用 -XX:+UseParNewGC 年老代用-XX:+UseConcMarkSweepGC

    原文:Java架构笔记

    免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
    传送门: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q
    展开全文
  • JVM之JVM面试题整理(长期更新)

    千次阅读 2019-08-01 18:15:09
    1、详细介绍一下JVM内存模型 2、说一下JVM内存结构(Java内存结构/Java内存区域)、Java内存模型区别与关系 3、讲讲什么情况下会出现内存溢出,内存泄漏? 4、说说线程栈 5、JVM 年轻代到年老代的晋升过程的判断...

    目录

    1、详细介绍一下JVM内存模型

    2、说一下JVM内存结构(Java内存结构/Java内存区域)、Java内存模型区别与关系

    3、讲讲什么情况下会出现内存溢出,内存泄漏?

    4、说说线程栈

    5、JVM 年轻代到年老代的晋升过程的判断条件是什么呢?

    6、JVM 出现 fullGC 很频繁,怎么去线上排查问题

    7、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?

    8、类的实例化顺序

    9、JVM垃圾回收机制,何时触发MinorGC等操作

    10、JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的

    11、各种回收算法

    12、各种回收器,各自优缺点,重点CMS、G1

    13、stackoverflow错误,permgen space错误


    1、详细介绍一下JVM内存模型

    根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

    具体可能会聊聊jdk1.7以前的PermGen(永久代),替换成Metaspace(元空间)

    • 原本永久代存储的数据:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap
    • Metaspace(元空间)存储的是类的元数据信息(metadata)
    • 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
    • 替换的好处:一、字符串存在永久代中,容易出现性能问题和内存溢出。二、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

    图片来源:https://blog.csdn.net/tophawk/article/details/78704074

    参考资料:

    2、说一下JVM内存结构(Java内存结构/Java内存区域)、Java内存模型区别与关系

    参考:https://blog.csdn.net/Soinice/article/details/98038991

    3、讲讲什么情况下会出现内存溢出,内存泄漏?

    内存泄漏的原因很简单:

    • 对象是可达的(一直被引用)
    • 但是对象不会被使用

    常见的内存泄漏例子:

        public static void main(String[] args) {
            Set<Object> set = new HashSet<>();
    
            for (int i = 0; i < 10; i++) {
                Object object = new Object();
                set.add(object);
    
                // 设置为空,该对象不再使用
                object = null;
            }
    
            // 但是set集合中还维护object的引用,gc不会回收object对象
            System.out.println(set);
            System.out.println(set.size());
        }
    }

    输出结果

    [java.lang.Object@74a14482, 
    java.lang.Object@677327b6, 
    java.lang.Object@6d6f6e28, 
    java.lang.Object@4554617c, 
    java.lang.Object@45ee12a7, 
    java.lang.Object@1b6d3586, 
    java.lang.Object@7f31245a,
    java.lang.Object@135fbaa4,
    java.lang.Object@1540e19d, 
    java.lang.Object@14ae5a5]
    10
    
    Process finished with exit code 0

    解决这个内存泄漏问题也很简单,将set设置为null,那就可以避免上述内存泄漏问题了。其他内存泄漏得一步一步分析了。

    内存泄漏参考资料:

    内存溢出的原因:

    • 内存泄露导致堆栈内存不断增大,从而引发内存溢出。
    • 大量的jar,class文件加载,装载类的空间不够,溢出
    • 操作大量的对象导致堆内存空间已经用满了,溢出
    • nio直接操作内存,内存过大导致溢出

    解决:

    • 查看程序是否存在内存泄漏的问题
    • 设置参数加大空间
    • 代码中是否存在死循环或循环产生过多重复的对象实体、
    • 查看是否使用了nio直接操作内存。

    参考资料:

    4、说说线程栈

    这里的线程栈应该指的是虚拟机栈吧...

    JVM规范让每个Java线程拥有自己的独立的JVM栈,也就是Java方法的调用栈。

    当方法调用的时候,会生成一个栈帧。栈帧是保存在虚拟机栈中的,栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息

    线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素

    通过jstack工具查看线程状态

    参考资料:

    5、JVM 年轻代到年老代的晋升过程的判断条件是什么呢?

    1. 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。
    2. 如果对象的大小大于Eden的二分之一会直接分配在old,如果old也分配不下,会做一次majorGC,如果小于eden的一半但是没有足够的空间,就进行minorgc也就是新生代GC。
    3. minor gc后,survivor仍然放不下,则放到老年代
    4. 动态年龄判断 ,大于等于某个年龄的对象超过了survivor空间一半 ,大于等于某个年龄的对象直接进入老年代

    6、JVM 出现 fullGC 很频繁,怎么去线上排查问题

    这题就依据full GC的触发条件来做:

    • 如果有perm gen的话(jdk1.8就没了),要给perm gen分配空间,但没有足够的空间时,会触发full gc。
      • 所以看看是不是perm gen区的值设置得太小了。
    • System.gc()方法的调用
      • 这个一般没人去调用吧~~~
    • 统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间,则会触发full gc(这就可以从多个角度上看了)
      • 是不是频繁创建了大对象(也有可能eden区设置过小)(大对象直接分配在老年代中,导致老年代空间不足--->从而频繁gc)
      • 是不是老年代的空间设置过小了(Minor GC几个对象就大于老年代的剩余空间了)

    7、类加载为什么要使用双亲委派模式,有没有什么场景是打破了这个模式?

    双亲委托模型的重要用途是为了解决类载入过程中的安全性问题

    • 假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编写的java.lang.Object类。
    • 然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader的路径下找到java.lang.Object类,并载入它

    Java的类加载是否一定遵循双亲委托模型?

    参考资料:

    8、类的实例化顺序

    • 1. 父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
    • 2. 子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
    • 3. 父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
    • 4. 父类构造方法
    • 5. 子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
    • 6. 子类构造方法

    检验一下是不是真懂了:

    public class Base {
        private String name = "博客:Soinice";
    
        public Base() {
            tellName();
            printName();
        }
    
        public void tellName() {
            System.out.println("Base tell name: " + name);
        }
    
        public void printName() {
            System.out.println("Base print name: " + name);
        }
    }
    
    public class Dervied extends Base {
        private String name = "Java3y";
    
        public Dervied() {
            tellName();
            printName();
        }
    
        @Override
        public void tellName() {
            System.out.println("Dervied tell name: " + name);
        }
    
        @Override
        public void printName() {
            System.out.println("Dervied print name: " + name);
        }
    
        public static void main(String[] args) {
            new Dervied();
        }
    }
    

    输出数据:

    Dervied tell name: null
    Dervied print name: null
    Dervied tell name: Java3y
    Dervied print name: Java3y
    
    Process finished with exit code 0

    第一次做错的同学点个赞,加个关注不过分吧(hahaha。

    9、JVM垃圾回收机制,何时触发MinorGC等操作

    当young gen中的eden区分配满的时候触发MinorGC(新生代的空间不够放的时候).

    10、JVM 中一次完整的 GC 流程(从 ygc 到 fgc)是怎样的

    这题不是很明白意思(水平有限...如果知道这题的意思可在评论区留言呀~~)

    • 因为按我的理解:执行fgc是不会执行ygc的呀~~

    YGC和FGC是什么

    • YGC :对新生代堆进行gc。频率比较高,因为大部分对象的存活寿命较短,在新生代里被回收。性能耗费较小。
    • FGC :全堆范围的gc。默认堆空间使用到达80%(可调整)的时候会触发fgc。以我们生产环境为例,一般比较少会触发fgc,有时10天或一周左右会有一次。

    什么时候执行YGC和FGC

    • a.eden空间不足,执行 young gc
    • b.old空间不足,perm空间不足,调用方法System.gc() ,ygc时的悲观策略, dump live的内存信息时(jmap –dump:live),都会执行full gc

    11、各种回收算法

    GC最基础的算法有三种:

    • 标记 -清除算法
    • 复制算法
    • 标记-压缩算法
    • 我们常用的垃圾回收器一般都采用分代收集算法(其实就是组合上面的算法,不同的区域使用不同的算法)。

    具体:

    • 标记-清除算法,“标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
    • 复制算法,“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
    • 标记-压缩算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存
    • 分代收集算法,“分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

    12、各种回收器,各自优缺点,重点CMS、G1

    图来源于《深入理解Java虚拟机:JVM高级特效与最佳实现》,图中两个收集器之间有连线,说明它们可以配合使用.

    • Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,但可能会产生较长的停顿,只使用一个线程去回收。
    • ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本
    • Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量
    • Parallel Old收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程“标记-整理”算法
    • CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它需要消耗额外的CPU和内存资源,在CPU和内存资源紧张,CPU较少时,会加重系统负担。CMS无法处理浮动垃圾。CMS的“标记-清除”算法,会导致大量空间碎片的产生
    • G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征

    13、stackoverflow错误,permgen space错误

    stackoverflow错误主要出现:

    • 在虚拟机栈中(线程请求的栈深度大于虚拟机栈锁允许的最大深度)

    permgen space错误(针对jdk之前1.7版本):

    • 大量加载class文件
    • 常量池内存溢出

     

     

     

    未完待续,长期更新~

    如果喜欢,或者感觉对你有用的话,希望点个赞,点个关注。

    展开全文
  • 常见JVM面试题及答案整理

    万次阅读 多人点赞 2020-06-02 18:25:10
    总结了JVM一些经典面试题,分享出我自己的解题思路,希望对大家有帮助,有哪里你觉得不正确的话,欢迎指出,后续有空会更新。 1.什么情况下会发生栈内存溢出。 思路: 描述栈定义,再描述为什么会溢出,再说明一下...

    前言

    总结了JVM一些经典面试题,分享出我自己的解题思路,希望对大家有帮助,有哪里你觉得不正确的话,欢迎指出,后续有空会更新。

    1.什么情况下会发生栈内存溢出。

    思路: 描述栈定义,再描述为什么会溢出,再说明一下相关配置参数,OK的话可以给面试官手写是一个栈溢出的demo。

    我的答案:

    • 栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型
    • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果。
    • 如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)
    • 参数 -Xss 去调整JVM栈的大小

    2.详解JVM内存模型

    思路: 给面试官画一下JVM内存模型图,并描述每个模块的定义,作用,以及可能会存在的问题,如栈溢出等。

    我的答案:

    • JVM内存结构

     

    程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。

    Java虚拟栈:存放基本数据类型、对象的引用、方法出口等,线程私有。

    Native方法栈:和虚拟栈相似,只不过它服务于Native方法,线程私有。

    Java堆:java内存最大的一块,所有对象实例、数组都存放在java堆,GC回收的地方,线程共享。

    方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。(即永久带),回收目标主要是常量池的回收和类型的卸载,各线程共享

    3.JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor。

    思路: 先讲一下JAVA堆,新生代的划分,再谈谈它们之间的转化,相互之间一些参数的配置(如: –XX:NewRatio,–XX:SurvivorRatio等),再解释为什么要这样划分,最好加一点自己的理解。

    我的答案:

    1)共享内存区划分

    • 共享内存区 = 持久带 + 堆
    • 持久带 = 方法区 + 其他
    • Java堆 = 老年代 + 新生代
    • 新生代 = Eden + S0 + S1

    2)一些参数的配置

    • 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ,可以通过参数 –XX:NewRatio 配置。
    • 默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定)
    • Survivor区中的对象被复制次数为15(对应虚拟机参数 -XX:+MaxTenuringThreshold)

    3)为什么要分为Eden和Survivor?为什么要设置两个Survivor区?

    • 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
    • Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
    • 设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)

    4.JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代

    思路: 先描述一下Java堆内存划分,再解释Minor GC,Major GC,full GC,描述它们之间转化流程。

    我的答案:

    • Java堆 = 老年代 + 新生代
    • 新生代 = Eden + S0 + S1
    • 当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。
    • 大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态
    • 如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态
    • 老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代
    • Major GC 发生在老年代的GC清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上

    5.你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。

    思路: 一定要记住典型的垃圾收集器,尤其cms和G1,它们的原理与区别,涉及的垃圾回收算法。

    我的答案:

    1)几种垃圾收集器:

    • Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。
    • ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。
    • Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。
    • Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。
    • Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。
    • CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。
    • G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。

    2)CMS收集器和G1收集器的区别:

    • CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;
    • G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;
    • CMS收集器以最小的停顿时间为目标的收集器;
    • G1收集器可预测垃圾回收的停顿时间
    • CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
    • G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。

    6.JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存。

    思路: 先画出Java内存模型图,结合例子volatile ,说明什么是重排序,内存屏障,最好能给面试官写以下demo说明。

    我的答案:

    1)Java内存模型图:

     

    Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

    2)指令重排序。

    在这里,先看一段代码

    public class PossibleReordering {
    static int x = 0, y = 0;
    static int a = 0, b = 0;
    
    public static void main(String[] args) throws InterruptedException {
        Thread one = new Thread(new Runnable() {
            public void run() {
                a = 1;
                x = b;
            }
        });
    
        Thread other = new Thread(new Runnable() {
            public void run() {
                b = 1;
                y = a;
            }
        });
        one.start();other.start();
        one.join();other.join();
        System.out.println(“(” + x + “,” + y + “)”);
    }

    运行结果可能为(1,0)、(0,1)或(1,1),也可能是(0,0)。因为,在实际运行时,代码指令可能并不是严格按照代码语句顺序执行的。大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OoOE或OOE)的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待3。通过乱序执行的技术,处理器可以大大提高执行效率。而这就是指令重排

    3)内存屏障

    内存屏障,也叫内存栅栏,是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。

    • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
    • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
    • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
    • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

    4)happen-before原则

    • 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。 锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
    • volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
    • happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
    • 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
    • 线程中断的happen-before原则 :对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
    • 线程终结的happen-before原则: 线程中的所有操作都happen-before线程的终止检测。
    • 对象创建的happen-before原则: 一个对象的初始化完成先于他的finalize方法调用。

    7.简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。

    思路: 先说明一下什么是类加载器,可以给面试官画个图,再说一下类加载器存在的意义,说一下双亲委派模型,最后阐述怎么打破双亲委派模型。

    我的答案:

    1) 什么是类加载器?

    类加载器 就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。

    • 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。
    • 其他类加载器:由Java语言实现,继承自抽象类ClassLoader。如:
    • 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。
    • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

    2)双亲委派模型

    双亲委派模型工作过程是:

    如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

    双亲委派模型图:

     

    3)为什么需要双亲委派模型?

    在这里,先想一下,如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名类java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证,因此,为什么需要双亲委派模型?防止内存中出现多份同样的字节码

    4)怎么打破双亲委派模型?

    打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。

    8.说说你知道的几种主要的JVM参数

    思路: 可以说一下堆栈配置相关的,垃圾收集器相关的,还有一下辅助信息相关的。

    我的答案:

    1)堆栈配置相关

    java -Xmx3550m -Xms3550m -Xmn2g -Xss128k 
    -XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0

    -Xmx3550m: 最大堆大小为3550m。

    -Xms3550m: 设置初始堆大小为3550m。

    -Xmn2g: 设置年轻代大小为2g。

    -Xss128k: 每个线程的堆栈大小为128k。

    -XX:MaxPermSize: 设置持久代大小为16m

    -XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。

    -XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

    -XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。

    2)垃圾收集器相关

    -XX:+UseParallelGC
    -XX:ParallelGCThreads=20
    -XX:+UseConcMarkSweepGC 
    -XX:CMSFullGCsBeforeCompaction=5
    -XX:+UseCMSCompactAtFullCollection:

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

    -XX:ParallelGCThreads=20: 配置并行收集器的线程数

    -XX:+UseConcMarkSweepGC: 设置年老代为并发收集。

    -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。

    -XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片

    3)辅助信息相关

    -XX:+PrintGC
    -XX:+PrintGCDetails

    -XX:+PrintGC 输出形式:

    [GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

    -XX:+PrintGCDetails 输出形式:

    [GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs

    9.怎么打出线程栈信息。

    思路: 可以说一下jps,top ,jstack这几个命令,再配合一次排查线上问题进行解答。

    我的答案:

    • 输入jps,获得进程号。
    • top -Hp pid 获取本进程中所有线程的CPU耗时性能
    • jstack pid命令查看当前java进程的堆栈状态
    • 或者 jstack -l > /tmp/output.txt 把堆栈信息打到一个txt文件。
    • 可以使用fastthread 堆栈定位,fastthread.io/

    10.强引用、软引用、弱引用、虚引用的区别?

    思路: 先说一下四种引用的定义,可以结合代码讲一下,也可以扩展谈到ThreadLocalMap里弱引用用处。

    我的答案:

    1)强引用

    我们平时new了一个对象就是强引用,例如 Object obj = new Object();即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

    2)软引用

    如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。

    SoftReference<String> softRef=new SoftReference<String>(str);     // 软引用

    用处: 软引用在实际中有重要的应用,例如浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

    (1)如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建

    (2)如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

    如下代码:

    Browser prev = new Browser();               // 获取页面进行浏览
    SoftReference sr = new SoftReference(prev); // 浏览完毕后置为软引用        
    if(sr.get()!=null){ 
        rev = (Browser) sr.get();           // 还没有被回收器回收,直接获取
    }else{
        prev = new Browser();               // 由于内存吃紧,所以对软引用的对象回收了
        sr = new SoftReference(prev);       // 重新构建
    }

    3)弱引用

    具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

    String str=new String("abc");    
    WeakReference<String> abcWeakRef = new WeakReference<String>(str);
    str=null;
    等价于
    str = null;
    System.gc();

    4)虚引用

    如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。

    11.JVM知识点精华汇总

    用XMind画了一张思维导图(源文件对部分节点有详细备注和参考资料,需要的朋友可以关注我的微信公众号:Java团长,然后回复“JVM”获取):

    參考与感谢


    作者:Jay_huaxiao
    链接:https://juejin.im/post/5d35ca5b518825449c64bc31
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    展开全文
  • JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 Java语言的一个非常重要的特点就是与平台的...

    JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
    Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因

     

     

    问题清单

    1. 你知道哪些或者你们线上使⽤什么GC策略?它有什么优势,适⽤于什么场景?

     

    堆内存划分为 Eden、Survivor 和 Tenured/Old 空间,如下图所示:

    从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,对老年代GC称为Major GC,而Full GC是对整个堆来说的,在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的。Major GC的速度一般会比Minor GC慢10倍以上

    下边看看有那种情况触发JVM进行Full GC及应对策略。

     

    【System.gc()方法的调用】
    此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过

    -XX:+ DisableExplicitGC来禁止RMI调用System.gc。

    【老年代代空间不足】
    老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
    java.lang.OutOfMemoryError: Java heap space
    为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。


    【永生区空间不足】
    JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
    java.lang.OutOfMemoryError: PermGen space
    为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。


    【CMS GC时出现promotion failed和concurrent mode failure】
    对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。对措施为:增大survivor space、老年代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX: CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。

    【统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间】
    这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。
    例如程序第一次触发Minor GC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。
    当新生代采用PS GC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。

    除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过

     java -Dsun.rmi.dgc.client.gcInterval=3600000

    来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。

     

    【堆中分配很大的对象】
    所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。为了解决这个问题,CMS垃圾收集器提供了一个可配置的参数,

    即-XX:+UseCMSCompactAtFullCollection

    开关参数,用于在“享受”完Full GC服务之后额外免费赠送一个碎片整理的过程,内存整理的过程无法并发的,空间碎片问题没有了,但提顿时间不得不变长了,JVM设计者们还提供了另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。

     

    2.Java类加载器包括⼏种?它们之间的⽗⼦关系是怎么样的?双亲委派机制是什么意思?有什么好处?

    启动Bootstrap类加载、扩展Extension类加载、系统System类加载。

    父子关系如下:

    • 启动类加载器 ,由C++ 实现,没有父类;

    • 扩展类加载器,由Java语言实现,父类加载器为null;

    • 系统类加载器,由Java语言实现,父类加载器为扩展类加载器;

    • 自定义类加载器,父类加载器肯定为AppClassLoader。

    双亲委派机制:类加载器收到类加载请求,自己不加载,向上委托给父类加载,父类加载不了,再自己加载。

    优势避免Java核心API篡改

     

    3.如何⾃定义⼀个类加载器?你使⽤过哪些或者你在什么场景下需要⼀个⾃定义的类加载器吗?

    自定义类加载的意义:

    1. 加载特定路径的class文件

    2. 加载一个加密的网络class文件

    3. 热部署加载class文件

     

    4.堆内存设置的参数是什么?

    • -Xmx 设置堆的最大空间大小

    • -Xms 设置堆的最小空间大小

       

    5.Perm Space中保存什么数据?会引起OutOfMemory吗?

    加载class文件。

    会引起,出现异常可以设置 -XX:PermSize 的大小。JDK 1.8后,字符串常量不存放在永久带,而是在堆内存中,JDK8以后没有永久代概念,而是用元空间替代,元空间不存在虚拟机中,二是使用本地内存。

    补充:Java8内存模型—永久代(PermGen)和元空间(Metaspace)

    根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。

    绝大部分 Java 程序员应该都见过

    "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是 JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并没有“PermGen space”。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多的情况,容易出现永久代内存溢出。

    移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

     

    6.做GC时,⼀个对象在内存各个Space中被移动的顺序是什么?

    标记清除法,复制算法,标记整理、分代算法。

    新生代一般采用复制算法 GC,老年代使用标记整理算法。
    垃圾收集器:串行新生代收集器、串行老生代收集器、并行新生代收集器、并行老年代收集器。
    CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。

     

    如果确定某个对象是“垃圾”?既然垃圾收集器的任务是回收垃圾对象所占的空间供新的对象使用,那么垃圾收集器如何确定某个对象是“垃圾”?—即通过什么方法判断一个对象可以被回收了。

    在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。

     

    7.你有没有遇到过OutOfMemory问题?你是怎么来处理这个问题的?处理 过程中有哪些收获?

    permgen space、heap space 错误。

    常见的原因

    • 内存加载的数据量太大:一次性从数据库取太多数据;

    • 集合类中有对对象的引用,使用后未清空,GC不能进行回收;

    • 代码中存在循环产生过多的重复对象;

    • 启动参数堆内存值小。

       

    8.JDK 1.8之后Perm Space有哪些变动? MetaSpace⼤⼩默认是⽆限的么? 还是你们会通过什么⽅式来指定⼤⼩?

    JDK 1.8后用元空间替代了 Perm Space;字符串常量存放到堆内存中。

    MetaSpace大小默认没有限制,一般根据系统内存的大小。JVM会动态改变此值。

    • -XX:MetaspaceSize:分配给类元数据空间(以字节计)的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark)。此值为估计值,MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。

    • -XX:MaxMetaspaceSize:分配给类元数据空间的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。

       

    9.jstack 是⼲什么的? jstat 呢?如果线上程序周期性地出现卡顿,你怀疑可 能是 GC 导致的,你会怎么来排查这个问题?线程⽇志⼀般你会看其中的什么 部分?

    jstack 用来查询 Java 进程的堆栈信息。

    jvisualvm 监控内存泄露,跟踪垃圾回收、执行时内存、cpu分析、线程分析。

     

    10.StackOverflow异常有没有遇到过?⼀般你猜测会在什么情况下被触发?如何指定⼀个线程的堆栈⼤⼩?⼀般你们写多少?

    栈内存溢出,一般由栈内存的局部变量过爆了,导致内存溢出。出现在递归方法,参数个数过多,递归过深,递归没有出口。

     

     

    文章首发自公众号【Ahab杂货铺】关注公众号技术分享第一时间送达!

    展开全文
  • 面试阿里我竟然倒在这一JVM面试题下 前言: 面试阿里倒在了JVM垃圾回收机制,何时触发MinorGC等操作这道面试题下,回去重新刷了一遍常问的JVM面试题,特意整理出22道分享给大家。八月已经来临了,离跳槽高峰...
  • JVM相关面试题及答案

    万次阅读 多人点赞 2017-03-20 15:47:37
    1. 类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,他们的执行顺序 答:先静态、先父后子。 先静态:父静态 > 子静态 优先级:父类 > 子类 静态代码块 > 非静态代码块 > 构造...
  • Java面试题JVM、并发、集合)

    千次阅读 2018-09-22 11:45:42
    JVM:   Q:JVM内存是如何划分的? https://www.cnblogs.com/dolphin0520/p/3613043.html https://blog.csdn.net/zhangjianjaEE/article/details/78655783 Q:谈谈垃圾回收机制?为什么引用计数器判定对象...
  • JVM常见面试题及答案

    万次阅读 多人点赞 2018-06-06 10:51:36
    11.JVM内存分哪几个区,每个区的作用是什么?java虚拟机主要分为以下一个区:方法区:1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生GC,在这里进行的GC主要是对方法区里的常量池和对类型的...
  • jvm相关面试题及答案

    千次阅读 2019-07-01 16:11:52
    首先是加载阶段(Loading),它是Java将字节码数据从不同的数据源读取到JVM中,并映射为JVM认可的数据结构(Class对象),这里的数据源可能是各种各样的形态,如jar文 件、class文件,甚至是网络数据源等...
  • JVM面试题总结

    千次阅读 多人点赞 2019-01-16 12:21:39
    JVM在执行Java程序的过程中会把它管理的内存分为若干个不同的区域,这些组成部分有些是线程私有的,有些则是线程共享的: 线程私有的: 程序计数器 虚拟机栈 本地方法栈 线程共享的: 方法区 堆 直接内存 1. ...
  • JVM常见面试题

    万次阅读 2018-07-18 10:09:25
    1.说一下jdk的对空间的内存划分是怎样的? Jdk1.7堆空间划分如下 Jdk1.8堆空间将永久代取消,改为元空间 ...2.GC的回收流程是怎样的?...对于整个的GC流程里面,那么最需要处理的就是新生代和老年代的内存清理操作,...
  • 几率大的JVM面试题(含答案)

    万次阅读 多人点赞 2020-03-09 14:45:13
    其他类型面试题汇总目录:Java校招极大几率出的面试题(含答案)----汇总 本章面试题如下: JVM三大性能调优参数,JVM 几个重要的参数 JVM调优 JVM内存管理,JVM的常见的垃圾收集器,G1垃圾收集器。GC调优,Minor ...
  • Java虚拟机(JVM)面试题(2020最新版)

    万次阅读 多人点赞 2020-05-06 14:13:13
    文章目录Java内存区域说一下 JVM 的主要组成部分及其作用?说一下 JVM 运行时数据区深拷贝和浅拷贝说一下堆栈的区别?队列和栈是什么?有什么区别?HotSpot虚拟机对象探秘对象的创建为对象分配内存处理并发安全问题...
  • Java架构师面试题——JVM性能调优

    万次阅读 多人点赞 2019-03-15 10:38:36
    JVM系列 直通BAT必考题系列:7种JVM垃圾收集器特点,优劣势、及使用场景 直通BAT必考题系列:JVM的4种垃圾回收算法、垃圾回收机制与总结 直通BAT必考题系列:深入详解JVM内存模型与JVM参数详细配置 ...
  • jvm调优面试怎么答?

    千次阅读 2019-09-18 06:39:52
    一.jvm分为年轻代,年老代,持久代1.年轻代:年轻代主要存放新创建的对象,垃圾回收会比较频繁。(稍微讲细一点就是即可,年轻代分成Eden Space和Suvivor Space。当对象在堆创建时,将进入年轻代的Eden Space。垃圾...
  • 关于JVM的几道面试题

    千次阅读 2014-01-22 09:58:18
    刚刚在看书,关于虚拟机规则的,突然想到可以出如下面试题: 给出一段会产生堆内存溢出的代码;给出一段虚拟机栈和本地方法栈溢出的代码:分StackOverflowError和OutOfMemoryError给出程序;给出一段能使运行...
  • 最近看书的过程中整理了一些面试题面试题以及答案都在我的文章中有所提到,希望你能在以问题为导向的过程中掌握虚拟机的核心知识。面试毕竟是面试,核心知识我们还是要掌握的,加油~~~ 下面是按jvm虚拟机知识点分...
  • JVM内存模型(面试自用)

    万次阅读 多人点赞 2019-03-07 22:31:03
    请讲一讲你对JVM内存模型的了解? 首先要说一下JVM内存空间分为五部分,分别是:方法区、堆、Java虚拟机栈、本地方法栈、程序计数器 方法区主要用来存放类信息、类的静态变量、常量、运行时常量池等,方法区的大小...
  • 面试总问的jvm调优到底是要干什么

    万次阅读 多人点赞 2018-10-30 17:40:30
    面试总问的jvm调优到底是要干什么  请注意,jvm调优,调的是稳定,并不能带给你性能的大幅提升。服务稳定的重要性就不用多说了,保证服务的稳定,gc永远会是Java程序员需要考虑的不稳定因素之一。复杂和高并发下的...
  • Java面试系列:JVM与调优面试题

    万次阅读 2020-05-11 18:08:26
    一、JVM 内存区域划分 1.程序计数器(线程私有) 二、JVM 执行子系统 1.Class 类文件结构 三.垃圾回收器和内存分配策略 3.基本垃圾回收算法 四、编写高效优雅 Java 程序 1.面向对象 五、性能优化 1.常用的...
1 2 3 4 5 ... 20
收藏数 54,988
精华内容 21,995
关键字:

jvm面试题