性能优化专题 - JVM 性能优化 - 02 - 类文件讲解

前言

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

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

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

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

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

对象创建

• 给对象分配内存
• 线程安全性问题
• 初始化对象
• 执行构造方法
在这里插入图片描述

给对象分配内存

  • 指针碰撞
  • 空闲列表

指针碰撞和空闲列表

线程安全性问题

• 线程同步

解析JVM线程同步机制

• 本地线程分配缓冲(TLAB)

Thread Local Allocation Buffer(本地分配缓存区)

对象的结构

每一个对象都由对象头、对象的实例数据区和对齐填充字节这三部分组成。

  • Header(对象头)
    对象头由三部分组成:
    1. Mark Word:记录对象和锁的有关信息。当一个对象被 synchronized 关键字加锁之后,围绕锁的操作就都会和MarkWord有关联。MarkWord通常都是 32 bit位大小。会保存一些分代年龄、无锁状态下对象的HashCode、偏向锁的线程ID、轻量级锁指向栈中锁记录的指针、指向重量级锁的指针、锁的标志位等内容。
    2. 指向类的指针:大小也通常为32bit,它主要指向类的数据,也就是指向方法区中的位置。
    3. 数组长度:只有数组对象才有,在32位或者64位JVM中,长度都是32bit。
  • InstanceData

    相同宽度的数据分配到一起(long,double)

  • Padding(对齐填充):

    8个字节的整数倍

在这里插入图片描述

对象的访问定位

• 使用句柄
• 直接指针

JVM 中对象的访问定位

逃逸分析与栈上分配

逃逸分析(Escape Analysis)与栈上分配

从源码到类文件

源码

class Person{
    
    

    private String name;

    private int age;

    private static String address;

    private final static String hobby="Programming";

    public void say(){
    
    
        System.out.println("person say...");
    }

    public int calc(int op1,int op2){
    
    
        return op1+op2;
    }
}

现在我们对 Person.java 类编译的得到 Person.class 字节码文件:

javac Person.java

在这里插入图片描述

编译过程

Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器-> 注解抽象语法树 -> 字节码生成器 -> Person.class文件

类文件(Class文件)

Class 文件是一组以 8 位字节为基础单位的二进制流,各个数 据项目严格按照顺序紧凑地排列在 Class 文件之中,中间没有添加 任何分隔符,这使得整个 Class 文件中存储的内容几乎全部是程序 运行的必要数据,没有空隙存在。
当遇到需要占用 8 位字节以上空间的数据项时,则会按照 高位在前(Big-Endian)的方式分割成若干个 8 位字节进行存储。
Class 文件只有两种数据类型:

  • 无符号数

在这里插入图片描述
我们刚才通过java文件反编译成了class字节码文件,那么我们怎么通过字节码编译成java文件呢?

实际上jvm就帮助我们做了这样的事情,那么jvm具体是怎么实现的呢?如果我们不借助jvm,怎么可以破解这样的二进制字节码文件?
Oracle官网(开源JDK的厂商)关于Class文件的描述

magic(魔数):

The  magic	item supplies the magic number identifying the  class  file format; it has the
	
value  0xCAFEBABE .

我们注意到字节码的开头是

cafe babe

通过官网查的,此为固定写法。

接着我们看:

0000 0034	

通过官网文档得知:

minor_version, major_version
所以我们知道了 这两位16进制的数字就对应10进制的52,代表JDK 8中的一个版本。

接着看:

0027

官网查得 constant_pool_count
说明是 对应十进制27,代表常量池中27个常量

等等依次类推,这里不再细说,有兴趣的可以参考Oracle官方文档对于类文件的解析:

  • 魔数

魔数版本
JDK1.8 = 52
JDK1.8 = 51

  • Class文件版本
  • 常量池 :

格式:

cp_info {
    u1 tag;
    u1 info[];
}

常量池对照表

  • 访问标志

  • 类索引,父类索引,接口索引集合

  • 字段表集合 :字段表用于描述接口或者类中声明的变量

  • 方法表集合

  • 属性表集合

整体结构如下:

ClassFile {
    
     
 	u4 magic; // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE
 	u2 minor_version; // 分别为Class文件的副版本和主版本
 	u2 major_version; 
 	u2 constant_pool_count; // 常量池计数
 	cp_info constant_pool[constant_pool_count-1]; // 
 	u2 access_flags; // 类访问标识
	u2 this_class; // 当前类
 	u2 super_class; // 父类
 	u2 interfaces_count; // 实现的接口数
 	u2 interfaces[interfaces_count]; // 实现接口信息
 	u2 fields_count; // 字段数量
 	field_info fields[fields_count]; // 包含的字段信息
 	u2 methods_count; // 方法数量
	method_info methods[methods_count]; // 包含的方法信息
	u2 attributes_count; // 属性数量
 	attribute_info attributes[attributes_count]; // 各种属性 
 }

javap文件分解器

javap -c Person.class > Person.txt

在这里插入图片描述

Compiled from "Person.java"
class com.testjvm.Person {
    
    
  com.testjvm.Person();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void say();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String person say...
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public int calc(int, int);
    Code:
       0: iload_1
       1: iload_2
       2: iadd
       3: ireturn
}

写在最后

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

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

猜你喜欢

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