资讯详情

JVM学习记录

JVM推荐硅谷康师傅的学习视频JVM合集 我刷的P1-P一些参数和日志分析些参数和日志分析 主要学习JVM的整体概念 收获很多,懒得上传图片。 祝各位在校招收理想。offer

JVM整体结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7rOArHvP-1626592537326)(JVM.assets/1625039943704.png)]

线程私有:java虚拟机栈、本地方法栈、程序计数器、生命周期与线程一致

线程共享:方法区,堆

java程序编译为字节码文件

JVM架构模型

java编译器输入的指令流

  1. 基于栈的指令集架构
    • 设计更简单,适用于资源有限的系统
    • 零地址指令分配
    • 指令集较小,依赖于操作栈
    • 不需要硬件支持,跨平台
  2. 基于寄存器的指令架构
    • 性能优秀,执行搞笑
    • 依赖硬件,可移植性差

JVM生命周期

启动虚拟机

通过引导加载器创建初始类来完成

执行虚拟机

执行一个java执行一个程序java虚拟机的过程

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-tO7cYBG5-1626592537328)(JVM.assets/1625042565308.png)]

  1. 正在执行
  2. 执行完成

退出虚拟机

  • 正常执行程序结束

  • 程序执行过程中晕倒异常或错误终止

  • 操作系统错误导致操作系统错误java终止虚拟机进程

  • 调用了某个线程runtime类或System类的exit方法,runtime类的halt方法,并且java这次安全管理区运行exit/halt操作

    [外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-S2hgFDT0-1626592537329)(JVM.assets/1625043119108.png)]

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-keDIM66O-1626592537330)(JVM.assets/1625043132456.png)]

执行引擎

执行引擎

  • 解释器
  • 即时编译器JIT
  • 垃圾回收器

类加载器系统的作用

  • 从文件系统或网络中加载类加载器子系统Class文件(物理磁盘上的文件),class文件在文件开头和具体文件中表示
  • ClassLoader只负责class询问您的加载价格是否可以运行ExecutionEngine(执行引擎)决定
  • 加载的类信息存储在一个叫做方法区的内存空间中。除类信息外,方法区还将存储常量池信息,可能包括字符串字面量和数字常量

类的加载过程

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-F01KH4jG-1626592537331)(JVM.assets/1626075857528.png)]

  1. 加载

    [外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-IgMddp4Z-1626592537332)(JVM.assets/1626076221607.png)]

  2. 链接

    • 验证
      • 目的时确保Class文件字节流中包含的信息符合要求
      • 大约有四个阶段:文件格式验证、元数据验证、字节码验证、符号引用验证
    • 准备
      • 类中定义的变量(即静态变量)static修改后的变量)分配内存并设置类变量初始值的阶段。这些变量使用的内存应在方法区域分配JDK8后,类变量会随之而来Class对象一起粗放囊JAVA堆中
    • 解析
      • 用直接引用替换常量池内符号的过程
      • 符号引用:符号引用以一组符号描述引用的目标,可以是任何形式的字面量
      • 直接引用:直接指向目标的指针,相对偏移或间接定位到目标的句柄

    [外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-DXpCO6H4-1626592537333)(JVM.assets/1626076611571.png)]

  3. 初始化

对于初始化阶段,严格规定以下情况必须立即初始化(加载、验证、准备在此之前开始)

  • 遇到new,getstatic,putstatic或invokestatic如果这四个字节码指令的类型没有初始化,则需要从初始化阶段开始
    • 使用new创建实例对象
    • 读取或设置一个类型的静态字段(final除了在编译期间将结果放入常量池的静态字段外)
    • 调用一种静态放置的类型
  • 使用反射,如果类型没有初始化,则需要开始初始化
  • 当初是偶,如果发现父类还没有初始化,就需要先开始父类的初始化
  • 当虚拟机启动时,用户需要指定一个执行主类,虚拟机会首先初始化整个主类
  • 定义接口时JDK8新加入的默认方法(default关键字修改的接口方法),如果该接口的实现类是初始化的,则该接口应在其之前初始化
  • 当使用JDK新添加的动态语言

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-1w65FGpL-1626592537334)(JVM.assets/1626077104416.png)]

在准备阶段,变量赋予了系统要求的初始零值,在初始化阶段,根据程序编码指定的主观计划区域初始化变量等资源,初始化阶段是执行结构**()**方法的过程

  • **()方法是编译器自动收集所有类变量的赋值动作和静态句块(static{}块)中的句子合并,编译器收集的顺序由源程序中句子的顺序决定,静态句子块只能访问静态句子块前定义的变量,定义后的变量,前面的静态句子块可以赋值,但不能访问

  • public class Test{  static{   i=0.//给变量赋值可以正常编译   System.out.print(i);///这个编译器会提示非法引用  }  static int i=1; } 

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-Cuc6cbZY-1626592537334)(C:\Users\32537\AppData\Roaming\Typora\typora-user-images\1626079753797.png)]

先编译后选择视图show ByteCode可观察字节码

类加载器分类

类加载器用于实现类加载

JVM支持两种类型的加载器

  1. 引导加载器Bootstrap ClassLoader(使用C 语言实现是虚拟机本身的一部分)
  2. 自定义加载器(包括外部导入和自定义加载器,继承抽象类java.lang.ClassLoader)
    1. 扩展加载器Extension Class Loader,这种类型的加载器是在Launcher类下的ExtClassLoader以java实现代码形式,负责加载\ext目录下的类库
    2. 应用程序类加载器Application Class Loader,这种类型的加载器是在Launcher类下的AppClassLoader以java实现代码形式,负责加载用户路径(ClassPath)上所有类库

他们之间的关系包括关系

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-zA0l3jGW-1626592537335)(JVM.assets/1626079919076.png)]

[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-rwinrmm-1626592537336)(JVM.assets/1626088104124.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MG79cTni-1626592537336)(JVM.assets/1626088134869.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNqHdm5x-1626592537337)(JVM.assets/1626088149646.png)]

双亲委派机制

Java虚拟机对class文件采用的方式,也就是当需要使用该类时才会对他的class文件加载到内存生成class对象,加载过程是按双亲委派机制,即把请求交由父类处理

ClassLoader类中的loadClass()方法实现双亲委派,先检查

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    { 
        
        synchronized (getClassLoadingLock(name)) { 
        
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) { 
        
                long t0 = System.nanoTime();
                try { 
        
                    if (parent != null) { 
        
                        c = parent.loadClass(name, false);
                    } else { 
        
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) { 
        
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) { 
        
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) { 
        
                resolveClass(c);
            }
            return c;
        }
    }

沙箱保护机制

自定义S听类,但是加载自定义String类的时候会首先使用引导类加载器(BootStrap)加载,会先加载jdk的自带文件,报错信息说没有main方法,是因为自带类中的String类内没有main方法,这样保证了java核心源代码的保护

运行数据区

程序计数器

  • PC寄存器是用来存储指向下一条指令的地址
  • 每个线程都由它自己的程序计数器,是线程私有的,生命周期和线程的生命周期保持一致
  • 在任何时间一个线程只有一个方法在执行,也就是所谓的。程序计数器都会存储当前线程正在执行的Java方法的JVM指令地址
  • 它是程序控制流的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器
  • 字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
  1. 使用程序计数器存储字节码指令地址有什么作用
  2. 为什么使用程序计数器记录当前线程的执行地址

因为CPU需要切换各个线程,这时候切换回来以后,就能知道线程执行的位置

JVM的字节码解释器需要通过改变程序计数器的值来明确下一条应该执行什么样的字节码指令

(一个CPU并发进行线程中的程序)

  • 程序计数器为什么设定为线程私有
    • 为了能够准确记录各个线程正在执行的当前字节码指令地址, 设置每个线程一个程序计数器,可以达到互不干扰的效果

CPU时间片

CPU时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片

虚拟机栈

  • 栈是运行时的单位,堆是存储的单位
  • 生命周期和线程一致
  • 主管Java程序的运行,保存方法的局部变量,部分结果,并参与方法的调用和返回

常见的异常

  1. StackErrorTest:采用固定大小的Java虚拟机栈,每一个线程的虚拟机栈容量可以在线程创建的时候独立选定,如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,会抛出这个异常
  2. OOM:采用动态扩展,并且尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,会抛出这个异常

配置中可以设置内存空间的大小

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0yBvd7h-1626592537337)(JVM.assets/1626101540639.png)]

栈的存储单位

  • 栈中的数据都是以栈帧的格式
  • 在这个线程上正在执行的每个方法都各自对应一个栈帧
  • 栈帧是一个内存区块,是一个数据集,维系方法执行过程中的各种数据

运行原理:

  • 操作只有两个,压栈和出栈

  • 在一条活动线程中,一个时间点上,只有一个活动的栈帧,这个栈帧被称为,对应的方法就是,定义这个方法的类就是

  • 执行引擎运行的所有戒子吗指令只针对当前栈帧进行操作

  • 如果该方法调用其他方法,对应新的栈帧会被创建出来,为新的当前栈帧

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ha1JzuQf-1626592537338)(JVM.assets/1626102896577.png)]

栈帧的内部结构

  • 动态链接(指向运行时常量池的方法引用)
  • 方法返回地址
  • 附加信息

局部变量表

  • 也称为局部变量数组,最小单位是变量槽
  • 定义为一个数字数组,主要存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型,对象引用,以及returnAddress类型
  • 局部变量表所需的容量大小在编译期确定下来的
  • 线程私有数据,不存在数据安全问题
  • java虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的变量槽数量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KsXQCw4l-1626592537339)(JVM.assets/1626136555891.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0qcK7rXz-1626592537340)(JVM.assets/1626104056565.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yfQJXotc-1626592537340)(JVM.assets/1626105530531.png)]

如果调用double的变量值,它占有两个slot(4和5)这里调用起始索引4

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RavNzxhq-1626592537341)(JVM.assets/1626106336032.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rWDScCX4-1626592537341)(JVM.assets/1626106357871.png)]

这里c占用的是b的索引位置,b离开大括号就销毁了

局部变量表方法结构(序号≈索引)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c77Mg5VR-1626592537342)(JVM.assets/1626136432570.png)]

LineNumberTable是Code属性中的一个子属性,用来描述java源文件行号与字节码文件偏移量之间的对应关系。当程序运行抛出异常时,异常堆栈中显示出错的行号就是根据这个对应关系来显示的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGK7Kbgp-1626592537342)(JVM.assets/1626105070918.png)]

静态变量和局部变量的对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J0EstXFK-1626592537343)(JVM.assets/1626106436953.png)]

局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收(可达性回收方法)

操作数栈

  • 也称表达式栈

  • 在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈或出栈

  • 主要用于保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间

  • 是JVM执行引擎的一个工作区,当一个工作方法刚开始执行,新的栈帧被创建出来,

  • 操作数栈

  • 栈中的任何一个元素都可以任意的java数据类型

    • 32bit的类型占用一个栈容量
    • 64bit的类型占用两个栈容量
  • 如果被调用的方法带有返回值,其返回值将会被压入当前栈帧的操作数栈中,并更新程序计数器中下一条需要执行的字节码指令

load获取上一个栈帧返回的结果

常见的i++和++i的区别

动态链接

  • 每一个栈帧内部都包含一个指向,包含这个引用的目的就是为了支持当前方法的代码能够实现
  • 动态链接的作用就是为了将这些(#指向运行常量池)转换为

方法的引用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jA5AjabT-1626592537344)(JVM.assets/1626139937367.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y38VlTWE-1626592537344)(JVM.assets/1626139996480.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1pE1dP7M-1626592537345)(JVM.assets/1626139977976.png)]

调用了methodA的构造方法

方法的调用

  • 静态链接
    • 当一个字节码被装进JVM内部时,如果被调用的,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接引用的过程称为静态链接
  • 动态链接
    • 如果,即只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换具有动态性,因此称为动态链接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-poYlsrYb-1626592537345)(JVM.assets/1626141681425.png)]

虚方法:在编译期间无法确定的方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GkfvLi24-1626592537346)(JVM.assets/1626143561150.png)]

方法重写

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ah1nNTfe-1626592537346)(JVM.assets/1626144282248.png)]

方法的返回地址

  • 存放调用该方法的程序计数器的值
  • 一个方法的结束有两种方式
    • 正常执行完成
    • 出现未处理的异常,非正常退出
  • 无论哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,。而通过异常退出的,返回地址时要通过异常表来确定,栈帧中一般不会保存这部分信息
  • 正常完成和异常完成出口的区别在于:通过异常完成出口退出的不会给它的上层调用者产生返回值

异常表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OPblAb7k-1626592537347)(JVM.assets/1626147390278.png)]

字节码4行跳转到第8行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sAUtPGGt-1626592537347)(JVM.assets/1626147413513.png)]

按11行的方式进行处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6nc8Vke-1626592537348)(JVM.assets/1626147599461.png)]

Java虚拟机栈面试题

  • 举例栈溢出的情况(StackOverflowError)

  • 通过-Xss设置栈的大小:OOM

  • 调整栈大小,就能保证不出现溢出吗

  • 不能保证

  • 分配的栈内存越大越好吗

    • 不是,会占用其他数据区域的内存空间
  • 垃圾回收是否涉及到虚拟机栈

    • 不存在GC,但是可达性分析会从局部变量表出发
  • 方法中定义的局部变量是否线程安全

    • /** * 面试题: * 方法中定义的局部变量是否线程安全?具体情况具体分析 * * 何为线程安全? * 如果只有一个线程才可以操作此数据,则必是线程安全的。 * 如果有多个线程操作此数据,则此数据是共享数据。如果不考虑同步机制的话,会存在线程安全问题。 * @author shkstart * @create 2020 下午 7:48 */
      public class StringBuilderTest { 
                  
      
          int num = 10;
      
          //s1的声明方式是线程安全的,每个线程都有一个S1不会去调用其他线程的S1
          public static void method1(){ 
                  
              //StringBuilder:线程不安全
              StringBuilder s1 = new StringBuilder();
              s1.append("a");
              s1.append("b");
              //...
          }
          //sBuilder的操作过程:是线程不安全的
          public static void method2(StringBuilder sBuilder){ 
                  
              sBuilder.append("a");
              sBuilder.append("b");
              //...
          }
          //s1的操作:是线程不安全的,该方法返回类型是StringBuilder可能会被其他方法调用
          public static StringBuilder method3(){ 
                  
              StringBuilder s1 = new StringBuilder();
              s1.append("a");
              s1.append("b");
              return s1;
          }
          //s1的操作:是线程安全的
          public static String method4(){ 
                  
              StringBuilder s1 = new StringBuilder();
              s1.append("a");
              s1.append("b");
              return s1.toString();
          }
      
          public static void main(String[] args) { 
                  
              StringBuilder s = new StringBuilder();
      
      
              new Thread(() -> { 
                  
                  s.append("a");
                  s.append("b");
              }).start();
      
              method2(s);
      
          }
      
      }
      

本地方法栈

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cAKMnCjC-1626592537348)(JVM.assets/1626159558984.png)]

当某个线程调用一个本地方法时,它就进入了一个全新地并且不再受虚拟机限制地世界,它和虚拟机拥有同样的权限

  • 本地方法可以通过本地方法接口来
  • 它可以直接使用本地处理器中的寄存器
  • 直接从本地内存的堆分配任意数量的内存

  • 一个JVM实例只存在一个堆内存,堆也是Java内存管理的核心区域,所有的对象实例和数组都应当在运行时分配在堆上
  • Java堆区在JVM启动的时候即被创建,其空间大小也就确定,是JVM管理的最大一块内存空间
    • 堆内存的大小是可以调节的(-Xms20m -Xmx20m)以此类推
  • 堆可以处于物理上不连续的内存空间,但是逻辑上它被视为连续的
  • 所有线程共享Java堆,在这里还可以划分线程私有的缓冲区

在方法结束后,堆中的对象不会马上被移除,仅在垃圾回收时才会被移除

堆是GC执行垃圾回收的重点区域

内存划分

JAVA8的堆空间逻辑上分为三部分

  • 新生区
  • 老年区
  • 元空间(方法区的实现)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZuXoaeN-1626592537349)(JVM.assets/1626188103286.png)]

  • -Xms用于表示堆区的起始内存
  • -Xmx用于表示堆区的最大内存(起始内存和最大内存一致可以避免堆自动扩展)
    • 一旦超过-Xmx所指定的最大内存,将会抛出OOM的异常

年轻代&老年代

  • 存储在JVM的java对象分为两类
    • 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
    • 另外一类对象的生命周期却非常长,甚至可以和JVM的生命周期保持一致
  • 年轻代可以划分为Eden空间,Survivor0空间,Survivor1空间(from区和to区)
    • 分配比列8:1:1

大多数情况下,对象在新生代Eden区中分配,绝大部分的Java对象的销毁都在新生代进行

当Eden区内存不够的时候,就会出发MinorGC,对新生代进行垃圾回收,From区要是满了则会移动到老年代

  • From区和To区:在GC开始的时候,对象只会存在于Eden区和From区,To区是空的,经过一次MinorGc后,Eden区和From区存活的对象会移动到To区,然后情况Eden区和From区,并对存活的对象年龄+1,如果年龄达到15,则分配给老年代。MinorGC完成后,From区和To区的功能互换,下一次MinorGC时,会把To区和Eden区存货的对象放入From区,并计算对象存活的年龄

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oDU5R675-1626592537349)(JVM.assets/1626234732716.png)]

垃圾回收后,对象都会移动到To区,原本的from区就清空了,成为下一次GC的To区

若创建的对象过大,可能直接分配到老年代

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3uR9CLuB-1626592537350)(JVM.assets/1626242520818.png)]

在Java8中,永久代已经被移除,取而代之的是一个称之为“元数据区”(元空间)的区域。元空间和永久代类似,都是对JVM中规范中方法的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存的限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中。这样可以加载多少类的元数据就不再由MaxPermSize控制,而由系统的实际可用空间来控制。

采用元空间而不用永久代的原因:

  • 为了解决永久代的OOM问题,元数据和class对象存放在永久代中,容易出现性能问题和内存溢出。
  • 类及方法的信息等比较难确定其大小,因此对于永久代大小指定比较困难,大小容易出现永久代溢出,太大容易导致老年代溢出(堆内存不变,此消彼长)。
  • 永久代会为GC带来不必要的复杂度,并且回收效率偏低。

MinorGC,MajorGC与FullGC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XShD0ts2-1626592537350)(JVM.assets/1626242858875.png)]

  • MinorGC
    • 当年轻代空间不足时,会出发MinorGC,这里的年轻代满指Eden区满,Survivor满不会引发GC
    • 因为Java对象大多都具有朝生夕灭的特性,所有MinorGC非常频繁,回收速度也比较块
    • MinorGC会引发STW,暂停其他用户的线程,等待垃圾回收结束,用户线程才恢复进行
  • 老年代GC(MajorGC/FullGC)
    • 出现MajorGC,经常会伴随至少一次的MinorGC(非绝对)
    • 老年代空间不足时,会先尝试出发MinorGC,如果之后空间还不足,则触发MajorGC
    • 速度比MinorGC慢,STW时间更长
    • MajorGC后内存还不足则会报出OOM的异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BKTROwZN-1626592537351)(JVM.assets/1626243504448.png)]

为什么要Java堆分代?

  • 分代原因:优化GC性能,如果没有分代,进行GC需要扫描所有区域

内存分配策略

  • 优先分配到Eden
  • 大对象直接分配到老年代
    • 避免Eden区及两个Survivor区之间来回赋值,产生大量的内存复制操作
  • 长期存活的对象分配到老年代
  • 动态对象年龄判断
    • 如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等待规定的年龄
  • 空间分配担保
    • –XX:HandlePromotionFailure

TLAB(Thread Local Allocation Buffer)

什么是TLAB

对Eden区域进行划分,JVM为,包含在Eden空间内

多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,这种内存分配方式称为

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ErofnYqQ-1626592537352)(JVM.assets/1626246282554.png)]

  • 堆是不是分配对象的唯一选择

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZqClRNKB-1626592537352)(JVM.assets/1626248890508.png)]

  • 逃逸分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bbY3IqNd-1626592537353)(JVM.assets/1626249117418.png)]

判断对象是否发生逃逸,主要分析对象是否在方法外被调用

代码的优化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yrGtyW4a-1626592537354)(JVM.assets/1626249673191.png)]

方法区

  • 栈,堆,方法区的交互关系[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dr00Udhm-1626592537355)(JVM.assets/1626250938060.png)]
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTwpnuea-1626592537355)(JVM.assets/1626250959748.png)]

是各个线程共享的内存区域

用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。

对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。

HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,

**方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。**在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XnSJ5qiT-1626592537356)(JVM.assets/1626259091546.png)]

方法区内部结构

存储已被虚拟机加载的

  • 类型信息
    • 对每个加载的类型(类,接口,枚举,注解),JVM必须在方法区中存储以下类型信息
    • 这个类型的完整有效名称
    • 这个类型直接父类的完整有效名(对于interface或Object没有父类)
    • 这个类型的修饰符(public,abstract,final的某个子集)
    • 这个类型直接接口的一个有序列表
  • 域信息
    • JVM必须在方法区中保存类型的所有域的相关信息以及声明顺序
    • 包括:域名称,域类型,域修饰符
  • 方法信息
    • 方法名称
    • 返回类型(或void)
    • 修饰符
    • 字节码,操作数栈,局部变量表及大小
    • 异常表
      • 每个异常处理的开始位置,结束位置,代码处理在程序计数器中的偏移地址,被捕获的异常类的常量池索引

类型信息如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QaLEuMuC-1626592537357)(JVM.assets/1626260874346.png)]

域信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-leCaP4GE-1626592537357)(JVM.assets/1626261021917.png)]

方法信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9snV6CHN-1626592537358)(JVM.assets/1626261058152.png)]

异常表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSdhNcG3-1626592537358)(JVM.assets/1626261193254.png)]

运行时常量池和常量池

  • 方法区,内部包含了运行时常量池(JDK8后在元空间实现)
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9rezHUQ-1626592537358)(JVM.assets/1626263998318.png)]
  • 字节码文件,内部包含了常量池
    • 各种字面量和对类型,域和方法的符号引用
    • 一个java源文件的类,接口,编译后产生一个字节码文件;而Java中的字节码需要数据支持,这种数据通常很大不会直接存到字节码,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用,动态链接的时候会用到运行时常量池

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rLjeoCQx-1626592537359)(JVM.assets/1626265235485.png)]

  • 字符串常量池为什么调整

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KziD0EYP-1626592537359)(JVM.assets/1626266116894.png)]

  • 静态变量的存储

    • 静态变量的引用存储在堆中

    方法区常量池主要存放两大类常量:字面量和符号引用

    字面量比较接近Java语言层次的常量概念,如文本字符串,final声明的常量值

    符号引用则属于编译原理方面的概念,包括下面三类常量:

    1. 类和接口的全限定名
    2. 字段的名称和描述符
    3. 方法的名称和描述符
  • HotSpot虚拟机堆常量池的回收策略:

  • 回收废弃常量与回收Java堆中的对象相似

判定类回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ba6u5xpj-1626592537359)(JVM.assets/1626267522245.png)]

对象实例化

  • 创建对象的方式
    • new
    • Class的newInstance():反射的方式,无参,public
    • Constructor的newInstance(xxx):反射的方式,有参/无参,权限无要求
    • clone():不调用任何构造器
    • 反序列化
    • Objenesis
  • 创建对象的步骤
    • 判断对象对应的类是否加载,链接,初始化;在元空间的常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载,解析,初始化(判断类元信息是否存在),如果没有,那么在双亲委派模型下,使用当前类加载器查找对应的.class文件,如果没有找到文件,则抛出ClassNotFound的异常,如果找到则进行类加载,并生成对应的Class类对象
    • 为对象分配内存
      • 如果内存规整–
      • 内存不规整–
      • 说明
    • 处理并发安全问题
      • 采用CAS配上失败重试保证更新的原子性
      • 每个线程分配一块TLAB
    • 初始化分配到空间
      • 所有属性设置默认值,保证对象实例字段不赋值也可以被使用
    • 设置对象的对象头
    • 执行方法进行初始化

JVM分配内存的两种方式

当使用new关键字创建一个类的对象时,虚拟机需要为新生对象分配内存空间,而对象的大小在类加载完成后已经确定了,所以分配内存只需要在Java堆中划分出一块大小相等的内存。在Java虚拟机中有指针碰撞和空闲列表两种方式分配内存。

  1. 如果Java堆中内存是规整排列的,所有被用过的内存放一边,空闲的可用内存放一边,中间放置一个指针作为它们的分界点,在需要为新生对象分配内存的时候,只要将指针向空闲内存那边挪动一段与对象大小相等的距离即可分配。

  2. 如果Java堆中内存不是规整排列的,用过的内存和可用内存是相互交错的,这种情况下将不能使用指针碰撞方式分配内存,Java虚拟机需要维护一个列表用于记录哪些内存是可用的,在为新生对象分配内存的时候,在列表中寻找一块足够大的内存分配,并更新列表上的记录。

  3. Java虚拟机采用哪种方式为新生对象分配内存,取决于所使用的垃圾收集器,当垃圾收集器具有整理过程时,虚拟机将采用指针碰撞的方式;当垃圾收集器的回收过程没有整理过程时,则采用空闲列表方式。

内存布局

  • 对象头(如果创建的是数组,还需要记录数组的长度)
      • 哈希值(指向堆空间的地址)
      • GC分代年龄
      • 锁状态标志
      • 线程持有的锁
      • 偏向线程ID
      • 偏向时间戳
    • –指向类元数据,确定对象的所属类型
    • 它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来和本身拥有的字段)
    • 父类定义的变量会出现在子类之前
    • 相同宽度的字段总是被分配在一起
    • 如果CompactFields参数为true(默认),子类窄变量会插入父类变量的空隙
  • 对齐填充
    • 起到占位符的作用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JOy8g85y-1626592537360)(JVM.assets/1626308009364.png)]

联系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k5R7aPVS-1626592537360)(JVM.assets/1626312064009.png)]

对象访问定位

JVM是如何通过栈帧中的对象引用访问到内部的对象实例?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6o5dGui4-1626592537360)(JVM.assets/1626312457780.png)]

定位,通过栈帧上的reference(引用)访问

对象访问的两种方式:

  • 句柄访问
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XHh8hM4t-1626592537361)(JVM.assets/1626312619557.png)]
  • 直接指针(HotSpot采用)节省空间
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zNQRGg7J-1626592537361)(JVM.assets/1626312996669.png)]

直接内存

  • 直接内存是在Java堆外的,直接向系统申请的内存空间
    • 访问直接内存的速度会优先于Java堆

执行引擎*(搞不懂)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XYHLDbJd-1626592537362)(JVM.assets/1626328905813.png)]

工作过程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REBZNVf6-1626592537362)(JVM.assets/1626329633750.png)]

Java代码编译和执行的过程

  • 解释器
    • 当Java虚拟机启动时会根据预定义的规范堆字节码采用逐行解释的方式执行,将每条字节码文件的内容“翻译”为对应平台的本地机器指令执行
  • JIT编译器
    • 虚拟机将源代码直接编译成和本地机器平台相关的机器语言

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kuJnRzJw-1626592537362)(JVM.assets/1626330884344.png)]

  • 字节码
    • 是一种中间状态(中间码)的二进制代码,需要转译才能称为机器码
    • 为了实现特定软件运行和软件环境,与硬件无关
    • 编译器将源码编译成字节码

StringTable

,字符串常量池位于堆

String

  • 声明为final,不可被继承
  • 实现了Serializable接口:表示字符串支持序列化的
  • 实现Comparable接口:表示String可以比较大小
  • 当对字符串重写赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
  • 当对现有的字符串进行连接操作,也需要重新指定内存区域赋值
  • 当调用replace()方法时同样
  • 通过字面量的方式(区别于new)给一个字符串赋值,此时字符串值声明在字符串常量池中

jdk8底层存放char类型数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ao1q1bmQ-1626592537363)(JVM.assets/1626344274777.png)]

字符串拼接

  1. 常量与常量的拼接结果在常量池,原理时编译器优化
  2. 常量池中不会存在相同的内容的常量
  3. 只要其中有一个是变量,结果就在堆中。变量拼接的原理是StringBuilder
  4. 如果拼接的结果是调用intern()方法,则主动将常量池中还没有的字符串对象放入池中,并返回对象地址
@Test
    public void test1(){ 
        
        String s1 = "a" <

标签: 连接器y16hy16连接器y16p

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台