JVM之(内存结构、字节码结构、内存分配与回收)-总结

JVM之(内存结构、字节码结构、内存分配与回收)-总结


如想了解更多更全面的Java必备内容可以阅读:所有JAVA必备知识点面试题文章目录:


注意: 本篇主要以HotSpot虚拟机为主,主要涉及JDK1.8,涵盖(JDK1.6~JDK13)
Java8虚拟机规范官方地址: https://docs.oracle.com/javase/specs/jvms/se8/html/
Java8官网参数配置说明地址: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html


来…直接进入主题:
JVM入门到精通学习视频:https://study.163.com/course/courseMain.htm?courseId=1209670826

文章目录


1、什么是Java虚拟机?

Java虚拟机是一台执行字节码的虚拟计算机,它拥有独立的运行机制。Java技术的核心就是Java虚拟机。
作用:Java虚拟机就是二进制字节码的运行环境。负责装载字节码其内部。
特点:一次编译到处运行内存自动管理垃圾自动回收

2、JVM的架构模型分别有什么?
  • 基于栈的指令集架构:不需要硬件,可移植性更好,更好实现跨平台。
  • 基于寄存器的指令集架构:依赖硬件,可移植性差,性能优秀和执行效率高。
3、JVM生命周期是什么?
  1. 虚拟机的启动:通过引导类加载器(Bootstrap ClassLoader)创建一个初始类(initial class),这个类是由虚拟机的具体实现指定的。
  2. 虚拟机的运行:随着程序开始而开始,随着程序结束而结束,程序的执行是由虚拟机的进程完成的。
  3. 虚拟机的退出:退出的场景主要有:
    1. 程序正常执行结束
    2. 程序在执行过程中遇到了异常或者错误而异常终止。
    3. 由于操作系统出现错误而导致Java虚拟机进程终止。
    4. 某线程调用Runtime类或者System类的exit方法(System的exit方法调用的是Runtime的exit方法,Runtime的exit方法底层调用Shutdown的exit方法),或者Runtime类的halt方法(Runtime的halt方法底层调用Shutdown的halt方法),并且java安全管理器也允许这次exit或halt操作。
    5. 除此之外,JNI(Java Native Interface)规范描述了用JNI invocation API来加载或者卸载Java虚拟机时,Java虚拟机退出
4、你了解的JVM虚拟机都有哪些?

JVM虚拟机有很多,这里列举几个比较有名的分别是:

  • Oracle公司的 HotSpot VM:前身是SUN公司。不管是JDK6还是JDK8,都默认采用HotSpot。HotSpot指的是它的热点代码探测技术。通过解释器编译器协同工作,在对应最优的程序响应时间最佳执行性能中间取得平衡。
  • Oracle公司的 JRockit:前身是BEA公司。专注于服务器端应用,它可以不太关注程序启动速度,因此JRockit内部不包含解析器的实现,全部代码都通过编译器编译后执行
  • IBM公司的 J9 VM:2007年左右,IBM发布了开源J9 VM,命名为OpenJ9。市场定位于HotSpot接近,在服务器端、桌面应用、嵌入式等多用途。广泛用于IBM的各种Java产品。
  • TaobaoJVM:由AliJVM团队发布。TaobaoJVM 基于 OpenJDK HotSpot VM发布的国内第一个优化、定制且开源的服务器版Java虚拟机。特点:将生命周期较长的Java对象从堆(heap)中移到堆(head)外,并且GC不能管理GCIH(GC invisible heap)内部的Java对象。GCIH中的对象还能够在多个Java虚拟机进程中实现共享
  • #############################再说一款比较牛逼的JVM虚拟机
  • Oracle公司的 Graal VM“Run Programs Faster Anywhere 在任何地方执行程序都快”,在Hotspot VM基础之上增强而成的跨语言全栈虚拟机,可作为任何语言运行平台。包括:Java、Scala、Groovy、Kotlin、C、C++、JavaScript、Ruby、Python等等。
5、JVM整体结构

简略图:
在这里插入图片描述
详细图:
在这里插入图片描述

6、简述一下类加载子系统都有哪些阶段,每个阶段都分别做了什么?

在这里插入图片描述

  1. 加载阶段

    1. 通过类名获取定义此类的二进制字节流。
    2. 将字节流的 静态存储结构 转化为 ---->方法区的运行时数据结构
    3. 在内存中生成一个代表类的java.lang.Class对象,通过这个对象对该类数据的访问。

    加载.class文件的方式,例如:本地系统直接加载、通过网络获取(web applet)、从zip压缩包中读取(jar/war等)、运行时计算生成(动态代理技术)等等。

  2. 链接阶段

    1. 验证(verify): 确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不危害虚拟机安全。
    2. 准备(Prepare): 为类变量(即static修饰)分配内存并设置该类变量的默认值(这里不包括final修饰的static,因为final在编译时就分配了)。比如:int为0、string为空。这里不会为实例变量分配初始化。
    3. 解析(Resolve): 将常量池中的符号应用转换为直接引用的过程。
  3. 初始化阶段
    初始化阶段就是执行类构造器方法< clinit >() 的过程。此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作静态代码块 中语句合并而来。并且JVM保证了< clinit >()在多线程下被加同步锁
    < clinit >()不是类的构造方法,类的构造方法对应的是jvm下的< init >()
    若类有父类,这JVM会保证先执行父类的< clinit >()再执行该类的< clinit >()方法
    例如如下字节码:
    在这里插入图片描述

7、在加载阶段用到的类加载器有哪些?

JVM支持两种类型的类加载器。分别为 引导类加载器(Bootstrap ClassLoader)自定义类加载器(User-Defined ClassLoader)

  • 引导类加载器(Bootstrap ClassLoader):使用C/C++实现的,所以并不继承ClassLoader。嵌套在JVM内部,用来加载Java的核心库。也负责加载扩展类加载器和应用程序类加载器。能够加载的API路径如下:

    //获取Bootstrap ClassLoader 能够加载的API路径
    URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
    for (URL urL : urLs) {
    	System.out.println(urL.toExternalForm());
    }
    
    输出结果:
    file:/D:/JDK/jdk8/jre/lib/resources.jar
    file:/D:/JDK/jdk8/jre/lib/rt.jar
    file:/D:/JDK/jdk8/jre/lib/sunrsasign.jar
    file:/D:/JDK/jdk8/jre/lib/jsse.jar
    file:/D:/JDK/jdk8/jre/lib/jce.jar
    file:/D:/JDK/jdk8/jre/lib/charsets.jar
    file:/D:/JDK/jdk8/jre/lib/jfr.jar
    file:/D:/JDK/jdk8/jre/classes
    
  • 自定义类加载器(User-Defined ClassLoader):是Java语言编写。是派生(直接或间接继承)于ClassLoader的类加载器。

    • 扩展类加载器:加载系统属性指定的目录下的类库,或从JDK的安装目录的jre/lib/ext子目录下的类库。如果用户创建jar放在这目录下也会被扩展类加载器加载。
      //获取扩展类加载器能加载的API路径
      String [] paths = System.getProperty("java.ext.dirs").split(";");
      for (String path : paths){
          System.out.println(path);
      }
      
      输出结果:
      D:\JDK\jdk8\jre\lib\ext
      C:\Windows\Sun\Java\lib\ext
      
    • 应用程序类加载器:是程序中默认的加载器。负责加载环境变量classpath或系统属性java.class.path指定路径下的类库。
      //系统默认的加载器--应用程序加载器sun.misc.Launcher$AppClassLoader@18b4aac2
      ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
      
      //自定义ClassLoaderTest加载器--默认使用应用程序加载器sun.misc.Launcher$AppClassLoader@18b4aac2
      ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
      
    • 用户自定义加载器:在某些业务场景(如:隔离加载类、防止源码泄露等)可以自己定义类加载器。也须派生于ClassLoader类。
8、说说双亲委派机制的工作原理?
  1. 如果一个类加载器收到了类的加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行。
  2. 如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最后达到顶层的启动类加载器
  3. 如果父类加载器可以完成类的加载任务,就成功返回;如果父类加载器无法完成此加载任务,子加载器才会尝试去加载,这就是双亲委派机制
    在这里插入图片描述

优点:

  • 避免类的重复加载。
  • 保护程序的安全,防止核心API被随意篡改。(举例说明:我们知道JDK原生API中的java.lang.String(引导类加载器加载)。如果我们也同样创建一个java.lang包,并在包下创建一个String类(应用程序加载器加载),自定义String通过双亲委派到引导类加载器加载,而引导类加载器加载的是JDK原生的String,避免了加载一个错误的String类,防止了String API被篡改。这就是所谓的沙箱安全机制)。
9、在JVM中表示两个class对象是否为同一个类的必然条件是什么?
  • 类的完成类名、包名必须完全一致
  • 加载这个类的ClassLoader具体加载器(引导类、扩展类、应用程序类)必须相同
10、说说什么是PC寄存器(程序计数器)?

PC寄存器 用来存储指向下一条指令的地址,也即将要执行的指令代码,由执行引擎取下一条指令。
是一块很小的内存空间,也是运行数据最快的存储区域。
在JVM规范中,每一个线程都有它自己的程序计数器,是线程私有的,生命周期与线程生命周期一致。
它是程序控制流的指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
它是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError(OOM)情况的区域不考虑垃圾回收(GC)
在这里插入图片描述

11、为什么要使用PC寄存器记录当前线程的执行地址?

因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接下来从哪里开始继续执行
在这里插入图片描述

12、PC寄存器为什么会被设定为线程私有?

CPU需要不停的切换各个线程。为了能够准确记录各个线程正在执行的当前字节码指令地址,最好的办法就是为每个线程都分配一个PC寄存器,这样一个线程的执行地址改变不影响其他线程

13、说说什么是虚拟机栈?

首先:栈是运行的单位(管运行)堆是存储的单位(管存储)
Java虚拟机栈:每一个线程创建时都会创建一个虚拟机栈,线程私有的,生命周期与线程一致。
Java虚拟机允许栈的大小是动态或者固定不变的。

  • 固定不变时,可以通过参数【-Xss】选项来设置线程的最大栈空间,当请求分配的栈容量超过Java虚拟机允许的最大容量,Java虚拟机将会抛出一个StackOverflowError栈溢出异常;
  • 动态时,当创建线程没有足够的内存去创建虚拟机栈时,并且尝试扩展无法申请得到足够的内存时,Java虚拟机将会抛出OutOfMemoryError(OOM)异常。所以存在OOM问题,但不存在GC问题。

栈中的数据都是以栈帧的格式存在,栈帧是一个内存区块,也是一个数据集,维护方法执行过程中的各种数据信息,每一个方法的执行或结束都伴随着入栈和出栈。执行引擎运行的所有字节码指令只针对当前栈帧进行操作。并且不同的线程所包含的栈帧不允许存在相互引用。如下:方法1—>方法2—>方法3—>方法4(当前处理方法4,结束后出栈,接着执行方法3,依次执行)
在这里插入图片描述

14、说说虚拟机栈中的栈帧内部结构都有哪些?

在这里插入图片描述
每一个栈帧中包含:

  • 局部变量表(Local Variables):也被称为局部变量数组,定义一个数组,主要存储方法参数很定义在方法内的局部变量(8种基本数据类型对象引用returnAddress类型),存放由数组下标为index0开始,到数组长度-1的索引结束。是线程私有数据,因此不存在数据安全问题。大小在编译期间确定下来,并且在运行期间不会被改变。
    Slot(变量槽) 是局部变量表最基本的存储单元。
    在栈帧中,与性能调优关系最密切的部分就是局部变量表。局部变量表中的变量也是很重要的垃圾回收根节点,只要被局部变量表中直接或者间接引用的对应都不会被回收

  • 操作数栈(Operand Stack)(或叫表达式栈):主要用于保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间
    当方法开始时,新的栈帧也会被创建,这时这个方法的操作数栈是空的。
    操作数栈的最大深度在编译器就已经定义。32位的类型占用一个栈单位的深度64位的类型占用两个栈单位的深度

  • 动态链接(Dynamic Linking):每一个栈帧内部都包含一个指向 运行时常量池 中该栈帧所属方法的引用,目的是为了支持当前方法代码能够实现动态链接。
    在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(symbolic Reference) 保存在字节码文件的常量池里。动态链接就是将这些符号引用转换成直接引用
    在这里插入图片描述

  • 方法返回地址(Return Address):无论是方法正常退出和异常退出,在方法退出后都会返回到该方法被调用的位置。
    当方法正常退出时调用者的PC寄存器的值作为返回地址,即调用发方法指令的下一条指令的地址。
    当方法异常退出时返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。
    在字节码中,返回指令包含:ireturn(boolean/byte/short/char/int)、lreturn(long)、dreturn(double)、return(申明返回viod/实例化方法/类和接口的初始化方法)等等。执行引擎遇到任意一个方法返回的字节码指令。将返回值传递给上层的方法调用者。

  • 一些附加信息

15、对于局部变量表中Slot(变量槽)的理解有多少?

Slot(变量槽) 是局部变量表最基本的存储单元。在局部变量表中:32位以内的类型占用一个slot(包括returnAddress类型)64位类型(long和double)占用两个slot,访问时只需要使用前一个所有即可
JVM会为局部变量表中的每一个slot都分配一个访问索引。如果当前帧由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0(第一个)的slot处,其余参数按照顺序继续排列。如下实例方法对应的局部变量表:
在这里插入图片描述

Slot(变量槽)可复用,如果一个局部变量过了作用域,那么在作用域之后申明的新及局部变量就很有可能会复用过期的slot位置,可以实现节省资源的目的。
在这里插入图片描述

16、举例说明PC寄存器、局部变量表、操作数栈的变化关系?
public void testAddOperation(){
      byte i = 15;
      int j = 8;
      int k = i+j;
}

通过 javap -v 类名.class;或者通过idea 的jclasslib-bytecode-viewer集成软件打开字节码如下:

 0 bipush 15
 2 istore_1
 3 bipush 8
 5 istore_2
 6 iload_1
 7 iload_2
 8 iadd
 9 istore_3
10 return

运行结果如下,理解其原理即可非死记硬背:
说明:如果被调用的方法有返回值的话,返回值会被压入当前栈帧的操作数栈中。并更新PC寄存器中下一条执行的字节码指令。
操作数栈中的数据类型必须与字节码指令的序列严格匹配。这由编译器在编译期间进行验证。
###PC寄存器:指向下一条指定指令的地址。###局部变量表:存储局部变量。###操作数栈:保存计算过程的中间结果。
在这里插入图片描述

17、什么是虚方法?什么是非需方法?

非需方法:方法在编译期就能确定了具体的调用版本,这个版本运行时是不可变的。包括:静态方法私有方法final修饰的方法实例构造器父类方法
虚方法:在运行期间才能确定调用具体的版本,体现了Java的多态性。

18、虚拟机中提供的方法调用指令有哪些?
  • 普通调用指令:

    • invokestatic:调用静态方法,解析阶段确定唯一方法版本。
    • invokespecial:调用< init >方法、私有及父类方法,解析阶段确定唯一方法版本。
    • invokevirtual:调用所有虚方法。
    • invokeinterface:调用接口方法。
  • 动态调用指令:

    • invokedynamic:动态解析出需要调用的方法。 JDK1.7增加的,实现动态类型语言,在JDK1.8的lambda表达式中有具体使用。
19、什么是本地方法(Native Method)?为什么要使用本地方法?

本地方法就是一个Java调用非Java代码的接口。一个本地方法的具体业务逻辑实现由非Java语言实现,比如C。标识符native可以与所有其它的Java标识符连用,但是abstract除外。因为native暗示这些方法是有实现体的,只不过这些实现体是非java语言编写的,但是abstract却显然的指明这些方法无实现体。

为什么要有本地方法:

  • 与Java环境外交互:有时Java应用需要与Java外面的环境交互。本地方法它为我们提供了一个非常简洁的接口,而且我们无需去了解Java应用之外的繁琐的细节。
  • 与操作系统交互:JVM是Java程序赖以生存的平台,它毕竟不是一个完整的系统,它经常依赖于一些底层系统的支持。这些底层系统常常是强大的操作系统。
  • Sun’s Java:Sun的解释器是用C语言实现的,这使得它能与外部交互。
20、什么是本地方法栈(Native Method Stack)

Java虚拟机栈用于管理Java方法的调用;本地方法栈用于管理本地方法的调用。
本地方法栈每个线程一份,线程私有的。当某个线程调用本地方法时,它就进入一个全新并且不再受JVM虚拟机限制。并不是所有的JVM都支持本地方法。

本地方法栈允许栈的大小是动态或者固定不变的。

  • 固定不变时,当请求分配的栈容量超过Java虚拟机允许的最大容量,Java虚拟机将会抛出一个StackOverflowError栈溢出异常;
  • 动态时,当创建线程没有足够的内存去创建虚拟机栈时,并且尝试扩展无法申请得到足够的内存时,Java虚拟机将会抛出OutOfMemoryError(OOM)异常。所以存在OOM问题,但不存在GC问题。
21、简单说说什么是堆?

是Java内存管理的核心区域。JVM启动的时候被创建,是JVM管理最大的一块内存区域几乎有是有的对象实例和数组都应当在运行时分配在堆上
可以物理上不连续的内存空间中,但是逻辑上它应该是被视为连续的。
一个JVM实例(进程)只存在一个堆内存,所有线程共享堆数据,这里还可以划分线程私有的缓冲区TLAB(Thread Local Allocation Buffer)
方法结束后,堆中的的对象不会被移除,仅仅在垃圾回收的时候才有可能被移除,是GC(Garbage Collection,垃圾收集器)执行垃圾回收的重点区域。是一个既会发生GC,也会发生OOM的区域。
默认情况下,初始内存大小:物理电脑内存大小 / 64最大内存大小:物理电脑内存大小 / 4。也可以通过【-Xms】和【-Xmx】进行设置。

  • 【-Xms】 memory start用于表示堆区的起始内存,等价于-XX:InitialHeapSize。
  • 【-Xmx】 memory max用于表示堆区的最大内存,等价于-XX:MaxHeapSize。一旦堆区中内存大小操作【-Xmx】指定的最大内存值时,将抛出OutOfMemoryError异常。
    通常【-Xms】和【-Xmx】两个参数配置相同的值,目的是为了能够在Java垃圾回收机制清理堆区后不需要重新分隔计算堆区的大小,从而提高性能。
22、基于分代收集理论堆内存细分为哪些?

JDK7及之前版本,堆内存逻辑上分为三部分:新生代(Young/New) + 老年代(Old/Tenure) + 永久代(Permanent Space)
JDK8及以上版本,堆内存逻辑上分为三部分:新生代(Young/New) + 老年代(Old/Tenure) + 元空间(Meta Space)
新生代又被划分为:Eden区Survivor区
Survivor区又分为:S0(from)区S1(to)区

配置新生代与老年代在堆结构的占比:默认 【-XX:NewRatio=2】 代表新生代占1份(1/3),老年代占2份(2/3)。
设置新生代Eden区 与 S0(from)区 和 S1(to)区的比例:默认 【-XX:SurvivorRetio=8】 代表Eden区占8份(8/10),S0(from)区占1份(1/10) ,S1(to)区占1份(1/10),几乎所有的Java对象都是在Eden区被new出来的,同时绝大部分Java对象的销毁都在新生代进行了。
在这里插入图片描述
分区名的等价叫法有: 新生区< === >新生代 < === >年轻代 ; 养老区< === >老年区< === >老年代 ; 永久代< === >永久区。
在这里插入图片描述

23、结合JVM层面说说对象分配的过程?
  1. new 的对象先放Eden区,
  2. 当Eden区的空间满时,程序又需要创建对象,JVM的垃圾回收器将对新生代进行垃圾回收(Minor GC),将新生代中不在被其他对象引用的对象进行销毁,在将新的对象加载到Eden区。
    在这里插入图片描述
  3. 然后将Eden区中剩余的对象移到S0区或S1区,并设置阙值,每次垃圾回收如果不被销毁阙值自增1,S0区和S1区每次垃圾回收将会把有数据的区域复制到空的区域,即S0和S1同一时刻有一个是空的。当阙值为15(默认)时将其放入老年代,阙值可以通过 【-XX:MaxTenuringThreshold=15】 进行设置。
    在这里插入图片描述
  4. 养老区内存不足时,会进行垃圾回收(Major GC),若养老区执行了GC之后还是无足够的空间存放对象,就会抛出OOM异常。【java.lang.OutOfMemoryError:Java heap space】
    在这里插入图片描述
    特殊情况:当new对象足够大的时,如果经过Minor GC之后如果Eden区还是存不下对象,则直接存放在老年代中,如果老年代也存不下则进行Major GC,GC之后如果还存不下,则抛出OOM异常。简单流程图如下:
    在这里插入图片描述
    通过可视化工具,监测简单例子的 Eden区、S0区、S1区、老年代的空间变化情况如下:
    在这里插入图片描述
24、简单说说Minor GC、Major GC、Full GC的区别?

JVM在进行GC时,并非每次都对(新生代、老年代、方法区)区域一起回收。在GC时会触发 STW ,暂停其他用户线程,等垃圾回收结束后用户线程才能恢复。HotSpot VM的GC按照回收区域分为:

  • 部分收集:不是完整收集整个Java对的垃圾收集,又分为:
    • 新生代收集(Minor GC):对新生代(Eden、S0、S1)进行垃圾收集。【当Eden区满时触发,Surcivor满不会触发GC,】
    • 老年代收集(Major GC):对老年代进行垃圾收集。【Major GC的速度一般比Minor GC慢10倍以上,STW时间更长,内存不足会抛出OOM】
    • 混合收集(Mixed GC):对整个新生代和部分老年代进行收集。
  • 整堆收集(Full GC):将整个Java堆和方法区进行垃圾收集。【Full GC开发和调优中要尽量避免】触发机制如下等场景:
    • 调用System.gc()。
    • 老年代空间不足。
    • 方法区空间不足。
    • Minor GC后进入老年代平均大小大于老年代最大可用连续空间。
    • Eden区、S0区、S1区将对象转存到老年代,并且老年代的可用内存小于该对象大小。
25、为什么需要把堆分代?不分代就不能正常工作了吗?

其实不分代完全可以,分代的唯一理由就是优化GC性能。如果没有分代,所有对象都放在一块,GC每次都将会对整个区域都进行扫描,STW时间更长。如果将对象的生命周期划分开,对不同的区域使用不同的GC策略。这样就会大大提升GC的性能。

26、为什么会有TLAB?什么是TLAB?

堆区的线程共享区域,任何线程都可以访问到堆区的共享数据。由于对象实例的创建在JVM中非常频繁,因此并发环境下堆区中内存空间是线程不安全的。为了避免多线程操作同一个地址,需要使用加锁等机制,进而影响分配速度。
对Eden区域继续划分,JVM为每个线程分配了一个私有缓存区域TLAB(Thread Local Allocation Buffer),包含在Eden区。在多线程同时分配内存时,使用TLAB可以避免线程安全问题。JVM将TLAB作为内存分配的首选。TLAB空间的内存非常小,仅占整个Eden空间的1%,也可以通过 【-XX:TLABWasteTargetPercent】 设置TLAB空间占Eden空间的百分比大小。如果TLAB内存分配失败,JVM会尝试通过加锁机制确保数据操作的原子性。
在这里插入图片描述

27、什么是逃逸分析(Escape analysis)?

在Java虚拟机中,对象是在堆中分配内存的。但是有一种特殊情况,那就是经过逃逸分析(Escape analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这无需分配在堆上也无需垃圾回收。

  • 当一个对象在方法中被定义后,对象只在方法内部使用,则没有发生逃逸
  • 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸

JDK7之前的版本可以使用【-XX:+DoEscapeAnalysis】显示开启逃逸分析。通过【-XX:+PrintEscapeAnalysis】查看逃逸分析的筛查结果。
JDK7及之后,HotSpot中默认开启了逃逸分析。
参数【-server】 启动Server模式,因为在Server模式下才可以启用逃逸分析。

###简单例子如下:
public static StringBuffer appendStr(String s1){
	StringBuffer sb = new StringBuffer();
	sb.append(s1);
	return sb;//new StringBuffer()被外部使用,发生逃逸
}
//******上述代码如果想要new StringBuffer()不发生逃逸,可以改成如下:
public static StringBuffer appendStr(String s1){
	StringBuffer sb = new StringBuffer();
	sb.append(s1);
	return sb.toString();//new StringBuffer()没有发生逃逸
}
28、结合逃逸分析可以从哪些方面进行代码优化?
  1. 栈上分配:JIT编译器发现一个对象没有放生逃逸。就有可能被优化栈上分配。常见的发生逃逸的场景:给成员变量赋值、方法返回值、实例引用传递等。
  2. 同步省略:JIT借助逃逸分析来判断同步块所使用的所对象是否只能够被一个线程访问,而没有其他线程与其产生线程安全问题。JIT就会取消同步的过程,也叫锁消除
    在这里插入图片描述
  3. 分离对象或标量替换标量(Scalar) 指一个无法分解成更小的数据,比如Java的原始数据类型就是一个标量不可再分。聚合量(Aggregate) 指可以分解的数据。JIT逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替,也称标量替换。可以设置【-XX:EliminateAllocations】参数开启标量替换(默认打开)。
    如下简单例子:
    private static void main(String[] args){
    	User user = User(1001,“精彩猿笔记”);
    }
    calss User{
    	private int id;
    	private String name;
    	public User(int id,String name){
    		this.id = id;
    		this.name= name;
    	}
    }
    
29、堆空间有哪些常用参数设置?
  • 【-Xms 大小】 设置堆的初始大小(以字节为单位)。此值必须是1024的倍数且大于1 MB。在字母后面加上k或K表示千字节,m或M表示兆字节,g或G表示千兆字节。
  • 【-Xmx 大小】 指定内存分配池的最大大小(以字节为单位)。此值必须是1024的倍数且大于2 MB。在字母后面加上k或K表示千字节,m或M表示兆字节,g或G表示千兆字节。
  • 【-Xmn 大小】 设置新生代的堆的初始大小和最大大小(以字节为单位)。在字母后面加上k或K表示千字节,m或M表示兆字节,g或G表示千兆字节。
  • 【-XX:NewRatio = 比率】 设置新生代与老年代之间的比率。默认情况下,此选项设置为2。
  • 【-XX:SurvivorRatio = 比率】 设置新生代Eden区与S0/S1空间比例,如果禁用了自适应大小调整(使用该-XX:-UseAdaptiveSizePolicy选项【-表示关闭】)
  • 【-XX:MaxTenuringThreshold = 阈值】 设置用于自适应GC大小调整的最大使用期限阈值。最大值为15。并行(吞吐量)收集器的默认值为15,而CMS收集器的默认值为6。
  • 【-XX:+PrintGCDetails】 打印详细的GC日志。【+表示开启】
  • ……等等

java8官网参数说明地址:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html

30、举例说明栈、堆、方法区的交互关系?

在这里插入图片描述在这里插入图片描述

31、说说什么是方法区(Method Area)?

方法区(Method Area) 是各个线程共享的区域。它存储每个类的结构,例如运行时常量池字段和方法数据,以及方法和构造函数的代码,包括用于类和实例初始化以及接口初始化的特殊方法。
方法区域是在虚拟机启动时创建的。尽管方法区域在逻辑上是 的一部分,即可看成永久代或元空间的具体实现,但是简单的实现可以选择不进行垃圾回收或压缩。大小可以是固定的也可以是动态的。方法区域的内存不必是连续的。
JVM关闭会释放方法区内存。如果无法提供方法区域中的内存来满足分配请求:

  • JDK7及以前版本将抛出一个【OutOfMemoryError:PermGen space】
  • JDK8及之后版本将抛出一个【OutOfMemoryError:Metaspace】

永久代与元空间的本质是:永久代是使用JVM内存元空间是使用本地内存
在这里插入图片描述
在这里插入图片描述

32、说说怎么设置方法区的大小?

方法区大小是可以固定的也可以动态调整的
JDK7及之前版本:

  • 【-XX:PermSize】 来设置永久代初始分配空间。默认:20.75M。
  • 【-XX:MaxPermSize】 来设置永久代最大分配空间。32bit默认:64M;64bit默认:82M。
  • 当超过【MaxPermSize】设置的值,会报:【OutOfMemoryError:PermGen space】

JDK8及之后版本:

  • 【-XX:MetaspaceSize】 来设置永久代初始分配空间。windows下默认:约21M。一旦触及这个值,将会执行Full GC,为了避免平凡GC,可以将该值适当调高一点。
  • 【-XX:MaxMetaspaceSize】 来设置永久代最大分配空间。默认:-1(没有限制,即最大为本地内存)。
  • 当超过【MaxMetaspaceSize】设置的值,会报:【OutOfMemoryError:Metaspace】
33、什么是常量池?什么是运行时常量池?

常量池:在编译期间生成。包含在字节码文件中;常量池包括自变量、类型、域、方法的符号引用。可以看做一张符号引用表,虚拟机指令根据查找常量表中要执行的类、方法、参数、自变量等类型。
运行时常量池:在加载字节码期间创建运行时常量池,JVM为每一个已加载的类型(类或接口)都维护一个常量池。包含在方法区中;常量池这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池内容比常量池更丰富,其中还包含加载器等等,此时将常量池符号引用转换成直接引用存放在运行时常量池中
如果运行时常量池所需的内存空间超过方法区提供的最大值时,则JVM或抛出OOM。

34、在不同JDK版本中,方法区有哪些变化?JDK8及之后版本对方法区做了哪些改进?

说明:只有Hotspot虚拟机JDK8之前才有永久代,对于其他虚拟机如JRockit或J9虚拟机来说不存在永久代的概念。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

35、为什么要将永久代改进成元空间?

删除永久代,引进元空间官方说明地址:http://openjdk.java.net/jeps/122

动机:这是为了JRockit和Hotspot融合工作的一部分。JRockit客户不需要配置永久代(因为JRockit没有永久代),并且习惯于不配置永久代。
描述:随着JDK8的到来,HotSpot虚拟机就没有永久代了,意味着将类的元数据信息移到一个与堆不相连的本地内存区域,这个本地内存区域叫做元空间。实现将在本机内存中分配类的元数据,并将字符串常量池和类静态变量移动到Java堆。类的元数据的分配将受可用本机内存量的限制,而不是由-XX:MaxPermSize的值固定的。

改进的原因:

  • 永久代的空间大小很难确定,在某些业务场景下不断的加载类,一旦过多JVM将会抛出:OutOfMemoryError:PermGen space。
  • 对永久代进行调优也很困难。
36、方法区(永久代/元空间)会发生垃圾回收吗?

Java虚拟机规范提到可以不要求虚拟机在方法区中实现垃圾收集。但是这部分区域的回收有时又确实是必要的。一般方法区回收效果不是很好,尤其是类型的卸载条件比较苛刻。有时花费大量的资源进行GC,但是GC的结果却不是很好。

方法区垃圾收集主要回收:

  • 运行时常量池中废弃的常量:只要常量没有被任何地方引用就会被回收。
  • 不再使用的类型:判断一个类型不再被使用必须同时满足三个条件,分别是:1. 该类的所有实例都已经被回收;2. 加载该类的加载器已经被回收;3. 该类对应的java.lang.Class对象没有在任何地方被引用,并且无法在任何地方通过反射访问该类。满足以上3点之后关于是否对类型进行回收,HotSpot虚拟机提供了【-Xnoclassgc】参数进行控制,还可以使用【-verbose:calss】以及【-XX+TraceClass-Loading】查看类加载,【-XX:TraceClassUnLoading】查看类卸载信息。
    通常Java虚拟机具备类型卸载能力,以保证不会对方法区造成内存压力。
37、举例说明方法区、程序计数器、虚拟机栈的变化关系?

在这里插入图片描述
详细变化细节如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

38、关于JVM的一些常见的面试题!

在这里插入图片描述
在这里插入图片描述


后续更多关于JVM的相关内容会不断更新中……

······
帮助他人,快乐自己,最后,感谢您的阅读!
所以如有纰漏或者建议,还请读者朋友们在评论区不吝指出!

个人网站…知识是一种宝贵的资源和财富,益发掘,更益分享…

SDS(SQL Data Security)是一款专门用于SQL数据库文件加密的保密软件。通过加密和访问控制技术,可控制SQL数据库文件无法拷贝,禁止导出,备份加密,远程销毁,脱离环境无法打开等等。 SDS分为管理程序和控制程序,需要在安装SQL Server数据库的机器上安装控制程序和管理程序,可在局域网内安装管理程序对SDS软件进行远程操控,安装后,所有数据库文件将处于加密状态,即使将文件拷贝出去也无法使用。同时,通过SQL企业管理器备份出来的数据也是加密的,离开本机环境将无法使用,SDS可禁止SQL组件导出数据。 SDS与数据库应用程序及数据库大小无关,后台实时监控数据的写入和读取,不会影响应用程序正常调用数据。 SDS的特点如下: 1.安装,维护简单。一键式安装,配套安装使用教程,专业的售后维护团队。 2.后台运行,实时监控数据库的读写。 3.数据库数据拷贝离开本机环境无法使用。本机环境内则不受影响。 4.与应用程序无关,不影响应用程序使用。ERP、PDM、等管理系统可以正常调用数据库文件,和正常操作一样。 5.与数据库大小无关。 6.通过设置,可本地或管理端临时禁用数据库。黑客来袭或者紧急情况可以通过本软件提供的禁用数据库功能使数据库文件无法打开,即使在本机环境依然无法正常打开使用,之后可恢复正常状态。 7.可设定云端验证。当服务器被盗时,可以是服务器电脑在本公司环境外部无法正常开机,卸载硬盘更换电脑依然无法正常开机,即数据库文件不会被外人获取。 SDS目前支持SQL2000、SQL2005、SQL2008、SQL2012。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值