前言
性能优化专题共计四个部分,分别是:
- Tomcat 性能优化
- MySql 性能优化
- JVM 性能优化
- 性能测试
本节是性能优化专题第三部分 —— JVM 性能优化篇,共计六个小节,分别是:
通过这六节的学习,你将学到:
➢ 了解JVM内存模型以及每个分区详解。
➢ 熟悉运行时数据区,特别是堆内存结构和特点。
➢ 熟悉GC三种收集方法的原理和特点。
➢ 熟练使用GC调优工具,快速诊断线上问题。
➢ 生产环境CPU负载升高怎么处理?
➢ 生产环境给应用分配多少线程合适?
➢ JVM字节码是什么东西?
对象创建
• 给对象分配内存
• 线程安全性问题
• 初始化对象
• 执行构造方法
给对象分配内存
- 指针碰撞
- 空闲列表
线程安全性问题
• 线程同步
• 本地线程分配缓冲(TLAB)
Thread Local Allocation Buffer(本地分配缓存区)
对象的结构
每一个对象都由对象头、对象的实例数据区和对齐填充字节这三部分组成。
- Header(对象头)
对象头由三部分组成:- Mark Word:记录对象和锁的有关信息。当一个对象被 synchronized 关键字加锁之后,围绕锁的操作就都会和MarkWord有关联。MarkWord通常都是 32 bit位大小。会保存一些分代年龄、无锁状态下对象的HashCode、偏向锁的线程ID、轻量级锁指向栈中锁记录的指针、指向重量级锁的指针、锁的标志位等内容。
- 指向类的指针:大小也通常为32bit,它主要指向类的数据,也就是指向方法区中的位置。
- 数组长度:只有数组对象才有,在32位或者64位JVM中,长度都是32bit。
- InstanceData
相同宽度的数据分配到一起(long,double)
- Padding(对齐填充):
8个字节的整数倍
对象的访问定位
• 使用句柄
• 直接指针
逃逸分析与栈上分配
从源码到类文件
源码
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架构师成长之路