栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java JVM

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java JVM

Java JVM 一、JVM结构

二、Class文件
public class Test {
    
    private int a;

    public void f1(){
        System.out.println(a);
    }
}

编译后的class文件:

//每4位代表2个字节
cafe babe 0000 0034 001e 0a00 0600 1009
0011 0012 0900 0500 130a 0014 0015 0700
1607 0017 0100 0161 0100 0149 0100 063c
696e 6974 3e01 0003 2829 5601 0004 436f
6465 0100 0f4c 696e 654e 756d 6265 7254
6162 6c65 0100 0266 3101 000a 536f 7572
6365 4669 6c65 0100 0954 6573 742e 6a61
7661 0c00 0900 0a07 0018 0c00 1900 1a0c
0007 0008 0700 1b0c 001c 001d 0100 0454
6573 7401 0010 6a61 7661 2f6c 616e 672f
4f62 6a65 6374 0100 106a 6176 612f 6c61
6e67 2f53 7973 7465 6d01 0003 6f75 7401
0015 4c6a 6176 612f 696f 2f50 7269 6e74
5374 7265 616d 3b01 0013 6a61 7661 2f69
6f2f 5072 696e 7453 7472 6561 6d01 0007
7072 696e 746c 6e01 0004 2849 2956 0021
0005 0006 0000 0001 0002 0007 0008 0000
0002 0001 0009 000a 0001 000b 0000 001d
0001 0001 0000 0005 2ab7 0001 b100 0000
0100 0c00 0000 0600 0100 0000 0100 0100
0d00 0a00 0100 0b00 0000 2700 0200 0100
0000 0bb2 0002 2ab4 0003 b600 04b1 0000
0001 000c 0000 000a 0002 0000 0006 000a
0007 0001 000e 0000 0002 000f 
1.魔数

​ 文件开头的4个字节,标记文件的类型。class文件魔数为0xCAFFBABE

2.版本号

​ 后续的4个字节代表jdk版本号。0000是编译器jdk版本的次版本号,0034转化为十进制是52,是主版本号,Java的版本号从45开始,除1.0和1.1都是使用45.x外,以后每升一个大版本,版本号加一,也就是说,编译生成该class文件的jdk版本为1.8.0。

反编译字节码文件

对class文件进行反编译:

C:UsersXXXDesktop>javap -v -p Test.class
Classfile /C:/Users/XXX/Desktop/Test.class //Class文件当前所在位置
  Last modified 2022-4-23; size 380 bytes //最后修改时间 文件大小
  MD5 checksum 87f79e3030c2bb49f83ec8b3d6db0294 //MD5值
  Compiled from "Test.java" //编译自哪个文件
public class Test //类的全限定名
  minor version: 0 //jdk次版本号
  major version: 52 //主版本号 ---> 1.8.0
  flags: ACC_PUBLIC, ACC_SUPER //该类的访问标志
Constant pool: //常量池
   #1 = Methodref          #6.#16         // java/lang/Object."":()V
   #2 = Fieldref           #17.#18        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Fieldref           #5.#19         // Test.a:I
   #4 = Methodref          #20.#21        // java/io/PrintStream.println:(I)V
   #5 = Class              #22            // Test
   #6 = Class              #23            // java/lang/Object
   #7 = Utf8               a
   #8 = Utf8               I
   #9 = Utf8               
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               f1
  #14 = Utf8               SourceFile
  #15 = Utf8               Test.java
  #16 = NameAndType        #9:#10         // "":()V
  #17 = Class              #24            // java/lang/System
  #18 = NameAndType        #25:#26        // out:Ljava/io/PrintStream;
  #19 = NameAndType        #7:#8          // a:I
  #20 = Class              #27            // java/io/PrintStream
  #21 = NameAndType        #28:#29        // println:(I)V
  #22 = Utf8               Test
  #23 = Utf8               java/lang/Object
  #24 = Utf8               java/lang/System
  #25 = Utf8               out
  #26 = Utf8               Ljava/io/PrintStream;
  #27 = Utf8               java/io/PrintStream
  #28 = Utf8               println
  #29 = Utf8               (I)V
{
  private int a;
    descriptor: I
    flags: ACC_PRIVATE

  public Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 1: 0

  public void f1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #3                  // Field a:I
         7: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 10
}
SourceFile: "Test.java"
3.访问标志
标志名称含义
ACC_PUBLIC是否为Public类型
ACC_FINAL是否被声明为final,只有类可以设置
ACC_SUPER是否允许使用invokespecial字节码指令的新语义
ACC_INTERFACE标志这是一个接口
ACC_ABSTRACT是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假
ACC_SYNTHETIC标志这个类并非由用户代码产生
ACC_ANNOTATION标志这是一个注解
ACC_ENUM标志这是一个枚举
4.常量池

​ 存放字面量(常量)和符号引用。

符号引用:

  1. 类和接口的全限定名
  2. 字段的名称和类型
  3. 方法的名称和返回值

eg. Main方法调用Object类的构造函数

#1 = Methodref          #6.#16         // java/lang/Object."":()V
#6 = Class              #23            // java/lang/Object
#9 = Utf8               
#10 = Utf8               ()V
#16 = NameAndType        #9:#10         // "":()V
#23 = Utf8               java/lang/Object

eg. 类的属性a,类型为int

#3 = Fieldref           #5.#19         // Test.a:I
#5 = Class              #22            // Test
#7 = Utf8               a
#8 = Utf8               I
#19 = NameAndType        #7:#8          // a:I
#22 = Utf8               Test

类型表:

标识字符含义
B基本类型byte
C基本类型char
D基本类型double
F基本类型float
I基本类型int
J基本类型long
S基本类型short
Z基本类型boolean
V特殊类型void
L对象类型,以分号结尾,如Ljava/io/PrintStream;
5.方法表

​ 描述方法的类型、作用域

public void f1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #3                  // Field a:I
         7: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 10

stack: 最大操作数栈

locals:局部变量所需的存储空间,单位为Slot, Slot是虚拟机为局部变量分配内存时所使用的最小单位,为4个字节大小。方法参数(包括实例方法中的隐藏参数this),显示异常处理器的参数(try catch中的catch块所定义的异常),方法体中定义的局部变量都需要使用局部变量表来存放。值得一提的是,locals的大小并不一定等于所有局部变量所占的Slot之和,因为局部变量中的Slot是可以重用的

args_size:this

attribute_info:方法体内容,输出int类型a属性的值,没有返回值

LineNumberTable:源码行号与字节码行号(字节码偏移量)之间的对应关系

6.字段表

​ 描述接口或类中声明的变量作用域、类型。

eg. Test类中a字段,int类型,private

  private int a;
    descriptor: I
    flags: ACC_PRIVATE
7.属性表

​ 描述字段表、方法表中特殊的属性

8.类索引、父类索引、接口索引

​ 用于确定类的继承关系

三、类的生命周期

1.加载

  • 通过类的全限定名得到二进制字节流

  • 将二进制字节流中静态代码块、静态方法转换为方法区运行时数据结构

  • 在堆中生成代表这个列的Class类模版

ClassLoader类加载器:

public class ClassLoader_ {

    public static void main(String[] args) {

        //获取应用程序类加载器
        ClassLoader classLoader1 = ClassLoader.getSystemClassLoader();
        //获取classLoader1的父类加载器
        ClassLoader classLoader2 = classLoader1.getParent();
        //获取classLoader2的父类加载器
        ClassLoader classLoader3 = classLoader2.getParent();

        System.out.println("应用程序类加载器:" +classLoader1);
        System.out.println("扩展类加载器:" + classLoader2);
        System.out.println("启动类加载器:" +classLoader3);

        //获取本类的class对象
        Class aClass = ClassLoader_.class;

        ClassLoader classLoader = aClass.getClassLoader();

        System.out.println("本类的类加载器:" + classLoader);

        
        System.out.println(System.getProperty("java.class.path"));

        
        System.out.println(System.getProperty("java.ext.dirs"));
    }
}


  • 启动类加载器:

​ Bootstrap ClassLoader,jdkjrelib下、核心类库rt.jar,所有java.*开头的类都能加载。java程序无法获取到启动类加载器,它是由C/C++编写的

  • 扩展类加载器:

​ Extension ClassLoader,jdkjrelibext下、由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类)

  • 应用程序累加载器:

    Application ClassLoader,加载用户类路径(ClassPath)所指定的类,程序默认的类加载器

注意:

1.当类加载器负责加载某个类时,该类中引用的其他类也交由该类加载器加载

2.加载过的类会被缓存,当加载某个类时,只有当缓存区不存在时,才会进行加载

3.以上三种类加载器只是从本地加载class文件,如果需要从其他地方加载class则必须自定义类加载器

2.验证

​ 确保加载类的正确

  • 文件格式验证:验证字节流是否为class文件,以魔数0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型
  • 元数据验证:进行语义的分析
  • 字节码验证:验证程序语义合法
  • 符号引用验证

(-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间)

3.准备

​ 为类的静态变量分配内存(在方法区中)并初始化默认值

​ 八大基本类型赋默认值(0,false),引用类型赋null值

eg. static int a = 1;

在准备阶段a的值为默认零值,在初始化阶段才会真正赋初始值1

4.解析

​ 将常量池中的符号引用转换为直接引用(真正的地址)

符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量例如。在Class文件中它以CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等类型的常量出现。

直接引用:直接指向目标的指针、相对偏移量(比如,指向实例变量、实例方法的直接引用都是偏移量)或一个间接定位到目标的句柄

5.初始化

​ 为类中的静态变量赋初始值

导致类初始化情况:

  1. new
  2. 访问类的静态变量或为其赋值
  3. 调用类的静态方法
  4. 反射获取类的Class类对象
  5. 初始化某个类的子类,其父类会被初始化
6.使用

访问方法区中的该类的字段方法,真正的数据位于堆中

7.卸载

JVM结束生命周期:

  1. System.exit()
  2. 正常执行结束
  3. 异常或错误终止
四、双亲委派机制

​ 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,所有的类加载最终都应该被传递到顶层的启动类加载器中,只有当父加载器无法加载所需的类时,子加载器才会尝试自己去加载该类。

当AppClassLoader需要加载一个类时,它首先不会加载该类,而是委托给父类加载器ExtClassLoader,

而ExtClassLoader也不会加载该类,它也不会加载该类,委托给父类加载器BootstrapClassLoader,

当BootstrapClassLoader无法加载该类时,交给ExtClassLoader加载,

当ExtClassLoader无法加载该类时,交给AppClassLoader加载,

当AppClassLoader无法加载该类时,抛出ClassNotFoundException异常

双亲委派机制防止内存中出现和系统类相同的包名类名,保证java程序的安全

五、沙箱安全机制

​ 沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。

当前最新的安全机制实现,则引入了域 (Domain) 的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域 (Protected Domain),对应不一样的权限 (Permission),存在于不同域中的类文件就具有了当前域的全部权限。

六、PC寄存器

​ PC寄存器存储下一条指令的地址,即将要执行的指令代码,是线程私有的,生命周期和线程相同,每个线程都有属于自己的,是唯一不会导致OOM的地方。

​ 如果当前正在执行的是java方法,PC寄存器记录该指令地址,如果是本地方法(Native),则为undefine

七、虚拟机栈
  • 每创建一个线程就会创建一个虚拟机栈,栈中是一个个栈帧,对应着方法调用,是线程私有的,生命周期与线程相同

  • 栈不存在垃圾回收问题

  • 每个方法执行就会入栈,方法结束就会出栈(包括正常执行return返回、抛出异常)

  • 如果栈的大小固定,当线程请求分配栈容量超过最大容量会导致栈溢出(StackOverflowError),如果栈的大小可以动态增长,当分配的内存超出最大内存时会导致OOM(OutOfMemoryError),可以通过-Xss来设置线程的最大栈空间

一个线程在一个时刻只有一个活动的栈帧(栈顶栈帧),就是当前栈帧,即当前执行的方法

局部变量表:方法执行时完成参数传递,存储方法参数和定义在方法体内的局部变量(包括基本数据类型和对象引用),基本单位是Slot(槽),4个字节,当局部变量过了其作用域时该槽位可以重用节省资源

操作数栈:也称为表达式栈,保存计算过程的中间结果和变量

HotSpot栈顶缓存:将栈顶元素全部缓存到物理CPU寄存器中,降低对内存的读写,提高执行效率

动态链接:指向运行时常量池中该栈帧所属方法的引用,用于将符号引用转换为调用方法的直接引用

方法返回地址:存放调用该方法的 PC 寄存器的值,用于返回该方法被调用的位置,方法正常退出时,返回调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定的,栈帧中一般不会保存这部分信息,并且不会产生任何的返回值

附加信息:与 Java 虚拟机实现相关的一些信息

八、本地方法栈

本地方法接口:本地方法就是一个 Java 调用非 Java 代码的接口,例如Unsafe类中的本地方法

本地方法栈:用于管理登记本地方法,线程私有,在Hotspot中,本地方法栈和虚拟机栈合二为一

九、堆

为进行高效的垃圾回收,JVM把堆内存逻辑上分为三部分

年轻代(Young Generation):新对象和没达到一定年龄的对象,包括三个部分:伊甸园区、幸存者0区和幸存者1区,默认比例为8:1:1

老年代(Old Generation):被长时间使用的对象,老年代的内存空间应该要比年轻代更大

元空间:永久代(JDK8以前),JVM中方法区的实现

//JVM配置
//-Xms 设置初始化内存分配大小
//-Xmx 设置最大分配内存大小
//-XX:+PrintGCDetails 输出GC垃圾回收信息
//-XX:+HeapDump··· dump下程序的错误、异常 通过jprofiler工具定位具体的位置
public static void main(String[] args) {

    //获取JVM能使用的最大内存 默认为电脑内存的1/4
    long maxMemory = Runtime.getRuntime().maxMemory();

    //获取JVM初始化时总内存 默认为电脑内存的1/64
    long totalMemory = Runtime.getRuntime().totalMemory();

    //-Xms1g -Xmx1g -XX:+PrintGCDetails 设置JVM初始化时总内存、最大内存为1G并输出GC垃圾回收信息
    System.out.println("max = " + maxMemory + "字节, " + ((double)maxMemory / 1024 / 1024) + "M");
    System.out.println("total = " + totalMemory + "字节, " + ((double)totalMemory / 1024 / 1024) + "M");
}


默认情况下新生代和老年代的比例是 1:2,可以通过 –XX:NewRatio 来配置

新生代中的 伊甸园区、幸存者0区和幸存者1区,默认比例为8:1:1,可以通过 -XX:SurvivorRatio 配置

十、方法区

​ 所有线程共享的内存区域,也称为非堆(Non-Heap),存储存储类信息、常量池、静态变量、JIT编译后的代码等数据,只是一个概念,在Hotspot中实现方式为元空间

类型信息:

对于每个加载的类型(类 class、接口 interface、枚举 enum、注解 annotation)存储

  1. 全限定名
  2. 直接父类全限定名
  3. 修饰符
  4. 直接接口列表

方法信息:

对于每个方法存储:

  1. 方法名称
  2. 方法返回值类型
  3. 方法参数和类型
  4. 方法的修饰符
  5. 方法的字符码、操作数栈、局部变量表及大小(abstract 和 native 方法除外)
  6. 异常表(abstract 和 native 方法除外)

运行时常量池:JVM 为每个已加载的类型(类或接口)都维护一个动态的常量池

十一、逃逸分析和代码优化

分析对象动态作用域:

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

栈上分配:如果一个对象的指针永远不会逃逸,将堆分配转化为栈分配

同步省略:也叫锁消除,如果一个对象只被一个线程访问,则对其的操作可以不考虑同步

public class SynchronizedClear_ {

    public void test(){
        Object o = new Object();
	    
        //给test()方法中的局部变量上锁,等同于未加锁
        synchronized (o){
            System.out.println("test....");
        }
    }
}

标量替换:如果一个对象不会被外部访问,并且该对象可以被进一步分解时,JVM 不会创建该对象,而会将该对象成员变量分解若干个被这个方法使用的成员变量所代替

缺点:逃逸分析相对耗时,无法保证逃逸分析的性能消耗一定能高于他的消耗

十二、垃圾回收(GC)

​ 垃圾收集主要是针对堆和方法区进行,程序计数器、虚拟机栈和本地方法栈是线程私有的,线程执行完毕就会销毁,无需进行垃圾回收

1.方法区上的GC

​ 方法区主要存放永久代对象回收率低,只要是对常量池的回收和对类的卸载

类卸载的条件:

  • 类的所有实例都被回收,堆中不存在该类的实例

  • 加载该类的ClassLoader被回收

  • 类的Class类对象没有在其他地方引用

2.堆上的GC

Minor GC:轻GC,发生在新生代,会频繁执行,速度很快

Full GC:发生在老年代上,很少执行,速度慢

引用计数法(JVM不使用):

​ 给每个对象设置一个计数器,当对象增加一个引用时计数器加一,引用失效计数器减一,只回收计数器你为0的对象。如果存在循环引用的情况,引用计数器不可能为0,无法对它们进行回收

复制算法(新生代):

​ 对于新生代区域,每次将Eden(伊甸园区)和From幸存者区中存活的对象复制到To幸存区之中,复制完成后,幸存区名字交换,在两个幸存区中始终保证有一个为空。占用内存空间,有一半的空间浪

标记清除算法(老年代):

​ 对于老年代区域,对存活的对象进行标记,每次清除掉未标记的对象。但是标记影响效率,清除之后会产生大量不连续的内存碎片

标记清除整理算法(老年代):

​ 在标记清除算法基础之上,将存活的对象移到一端,避免产生内存碎片。但是移动存活对象也影响效率

分代收集算法:

​ 对于堆中不同区域采取不同的方式:

  • 新生代使用复制算法
  • 老年代使用标记清除整理算法
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/838692.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号