性能优化专题 - JVM 性能优化 - 03 - 字节码执行引擎

前言

性能优化专题共计四个部分,分别是:

本节是性能优化专题第二部分 —— JVM 性能优化篇,共计六个小节,分别是:

  1. JVM介绍与入门
  2. 类文件讲解
  3. 字节码执行引擎
  4. GC算法与调优
  5. Java内存模型与锁优化
  6. Linux性能监控与调优

通过这六节的学习,你将学到:

➢ 了解JVM内存模型以及每个分区详解。
➢ 熟悉运行时数据区,特别是堆内存结构和特点。
➢ 熟悉GC三种收集方法的原理和特点。
➢ 熟练使用GC调优工具,快速诊断线上问题。
➢ 生产环境CPU负载升高怎么处理?
➢ 生产环境给应用分配多少线程合适?
➢ JVM字节码是什么东西?

本系列前面的章节我们主要学习了,从源码到类文件,本节我们就聊一聊从类文件到虚拟机(类加载机制)的过程以及类加载器。

所谓的类加载机制就是:

  • 虚拟机把Class文件加载到内存

  • 并对数据进行校验,转换解析和初始化

  • 形成可以虚拟机直接使用的Java类型,即java.lang.Class

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、解析、和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制

类文件到虚拟机(类加载机制)

装载(load)

查找和导入Class文件

(1)通过一个类的全限定名获取定义此类的二进制字节流
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

链接(Link)

验证(Verify)

保证被加载类的正确性

  • 文件格式验证

  • 元数据验证

  • 字节码验证

  • 符号引用验证

准备(Prepare)

为类的静态变量分配内存,并将其初始化为默认值

解析(Resolve)

把类中的符号引用转换为直接引用

初始化(Initialize)

对类的静态变量,静态代码块执行初始化操作

类加载机制图解

使用和卸载不算是类加载过程中的阶段,只是画完整了一下
在这里插入图片描述

类装载器ClassLoader

在装载(Load)阶段,其中第(1)步:通过类的全限定名获取其定义的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载Class文件的。通过一个类的全限定名获取定义此类的二进制字节流。

分类

  • Bootstrap ClassLoader 负责加载 $JAVA HOME中 jre/lib/rt.jar入里所有的class或Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader子类。

  • Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar-Djava.ext.dirs指定目录下的jar包。

  • App ClassLoader 负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。

  • Custom ClassLoader 通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。

图解

在这里插入图片描述

加载原则

检查某个类是否已经加载:顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加载,就视为已加载此类,保证此类只所有ClassLoader加载一次。

加载的顺序:加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

双亲委派机制:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

双亲委派机制的优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。

破坏:双亲委派机制的破坏

字节码与数据类型

在虚拟机的指令集中,大多数的指令包含了其操作所对应的数据类型信息

iLoad:从局部变量表中加载int型数据到操作数栈

大多数指令包含类型信息。类型多,指令少。

加载与存储指令

加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈直接来回传输

• 将局部变量表加载到操作数栈: iload lload fload dload aload
• 将一个数值从操作数栈存储到局部变量表:istore :lfda

• 将一个常量加载到操作数栈:bipush sipush ldc ldc_w ldc2_w aconst_null iconst_m1 iconst

• 扩充局部变量表的访问索引的指令:wide

类型转换指令

• 类型转换指令可以将两种不同的数值类型进行相互转换,这些转换操作一般用于实现用户代码中的显示类型转换操作以及用来处理字节码指令集中数据类型相关指令无法与数据类型一一对应的问题。

•宽化类型处理和窄化类型处理

•L2b i2c i2s l2i

对象创建与访问指令

•创建类实例的指令:new
•创建数组的指令:newarray anewarray multianewarray

•访问类字段:getfield putfield getstatic putstatic
•把数组元素加载到操作数栈的指令:aload
•将操作数栈的值存储到数组元素:astore
•取数组长度的指令:arraylength
•检查实例类型的指令:instanceof checkcast

操作数栈管理指令

  • 操作数栈指令用于直接操作操作数栈

  • 操作数栈的一个或两个元素出栈:pop pop2

  • 复制栈顶一个或两个数值并将复制或双份复制值重新压入栈顶:dup dup2 dup_x1 dup_x2

  • 将栈顶的两个数值替换:swap

控制转移指令

•控制转移指令可以让java虚拟机有条件或无条件的从指定的位置指令而不是控制转移指令的下一条指令继续执行程序。可以认为控制转移指令就是在修改pc寄存器的值
•条件分支:ifeq iflt ifle ifne ifgt ifnull ifcmple
•复合条件分支:tableswitch lookupswitch
•无条件分支:goto goto_w jsr jsr_w ret

方法调用指令

•Invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派

•Invokinterface指令用于调用接口方法,它会在运行时搜索一个实现了这个接口的对象,找出适合的方法进行调用

•Invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法

•Invokestatic指令用于调用类方法

方法返回指令

•方法返回指令是根据返回值的类型区分的,包括 ireturn(当返回值是boolean、byte、char、short和int类型时使用)、lreturn、freturn、dreturn和areturn,另外还有一条return指令供声明为void的方法、实例初始化方法以及类和接口的类初始化方法使用。

异常处理指令

•在Java程序中显示抛出异常的操作(throw 语句)都由athrow指令来实现,除了用throw语句显式抛出异常情况之外,Java虚拟机规范还规定了许多运行时异常会在其他Java虚拟机指令检测到异常状况时自动抛出。例如,在整数运算中,当除数为零时,虚拟机会在idiv或ldiv指令中抛出ArithmeticException异常。而在Java虚拟机中,处理异常(catch语句)不是由字节码指令来实现的(很久之前曾经使用jsr和ret指令来实现,现在已经不用了),而是采用异常表来完成的。

写在最后

本节代码下载地址为:https://github.com/harrypottry/jvmDemo

更多架构知识,欢迎关注本套系列文章Java架构师成长之路

猜你喜欢

转载自blog.csdn.net/qq_34361283/article/details/111415450