java深入理解jvm对象内存结构和大小分析(示范四种查看jvm中对象内容和大小的方式)
基本知识
32位操作系统一个指针4个字节
64位操作系统一个指针8个字节
java一个字符2个字节
本次实验使用64位机器、操作系统、jvm
Java对象内存查看方法
org.openjdk.jol依赖
maven项目
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
代码
package test.object_size;
/**
* @author humorchen
* @date 2022/2/10 16:59
*/
public class Demo {
public char[] chars = {'2', '3', '2', '3', '2', '3', '2', '3'};
public char[] chars1 = {'2', '3', '2', '3', '2', '3'};
// public char[] chars3 = {'2', '2', '3', '2', '3', '2', '3'};
// public char[] chars4 = {'2', '2', '3', '2', '3', '2', '3'};
// public List<Integer> list = new ArrayList<>();
}
package test.object_size;
import org.openjdk.jol.info.ClassLayout;
/**
* @author humorchen
* @date 2022/2/14 18:35
*/
public class ShowObjectTable {
public static void main(String[] args) {
Demo demo = new Demo();
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
运行结果
dump堆
在程序代码后加上Thread.sleep让进程睡眠给我们充足的时间去dump下jvm堆内存情况
- Demo类
/**
* @author humorchen
* @date 2022/2/10 16:59
*/
public class Demo {
public char[] chars = {'2', '3', '2', '3', '2', '3', '2', '3'};
public char[] chars1 = {'2', '3', '2', '3', '2', '3'};
public char[] chars3 = {'2', '2', '3', '2', '3', '2', '3'};
// public char[] chars4 = {'2', '2', '3', '2', '3', '2', '3'};
// public List<Integer> list = new ArrayList<>();
}
- 测试类
/**
* @author humorchen
* @date 2022/2/10 18:35
*/
public class TestDumpStack {
public static void main(String[] args) throws Exception {
Demo demo = new Demo();
Thread.sleep(10000000);
}
}
运行测试类,进入jdk的bin目录打开jvisualvm.exe
在java visualvm中如果你没有安装插件Visual GC就安装一个
安装的时候可能会下载失败,因为github连接不稳定,可以采用你懂的方法或者多次尝试解决安装问题,实在安装失败也没事不影响
- 双击刚才运行的java进程- 点开监视并点击堆dump
- 点开类,搜索Demo类
阿里巴巴Arthas jvm分析工具
https://arthas.aliyun.com/
可以搜索相关博客学习使用
java增强Instrumentation
maven项目
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>Test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.7</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 创建对象大小计算工具类ObjectSizeUtil
package test.object_size;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
/**
* @author humorchen
* @date 2022/2/10 10:32
*/
public class ObjectSizeUtil {
private static Instrumentation instrumentation;
/**
* 这个方法先于主方法(main)执行
*
* @param args
* @param inst
*/
public static void premain(String args, Instrumentation inst) {
instrumentation = inst;
}
/**
* 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、
* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;
* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小
*
* @param o 需要计算内存的对象
* @return 返回内存大小
*/
public static long sizeOf(Object o) {
return instrumentation.getObjectSize(o);
}
/**
* 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小
* 注意:这个方法如果你看不懂也没关系,会用就行
*
* @param objP
* @return
* @throws IllegalAccessException
*/
public static long fullSizeOf(Object objP) throws IllegalAccessException {
Set<Object> visited = new HashSet<Object>();
Deque<Object> toBeQueue = new ArrayDeque<>();
toBeQueue.add(objP);
long size = 0L;
while (toBeQueue.size() > 0) {
Object obj = toBeQueue.poll();
//sizeOf的时候已经计基本类型和引用的长度,包括数组
size += skipObject(visited, obj) ? 0L : sizeOf(obj);
Class<?> tmpObjClass = obj.getClass();
if (tmpObjClass.isArray()) {
//[I , [F 基本类型名字长度是2
if (tmpObjClass.getName().length() > 2) {
for (int i = 0, len = Array.getLength(obj); i < len; i++) {
Object tmp = Array.get(obj, i);
if (tmp != null) {
//非基本类型需要深度遍历其对象
toBeQueue.add(Array.get(obj, i));
}
}
}
} else {
while (tmpObjClass != null) {
Field[] fields = tmpObjClass.getDeclaredFields();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers()) //静态不计
|| field.getType().isPrimitive()) { //基本类型不重复计
continue;
}
field.setAccessible(true);
Object fieldValue = field.get(obj);
if (fieldValue == null) {
continue;
}
toBeQueue.add(fieldValue);
}
tmpObjClass = tmpObjClass.getSuperclass();
}
}
}
return size;
}
/**
* String.intern的对象不计;计算过的不计,也避免死循环
*
* @param visited
* @param obj
* @return
*/
static boolean skipObject(Set<Object> visited, Object obj) {
if (obj instanceof String && obj == ((String) obj).intern()) {
return true;
}
return visited.contains(obj);
}
}
- 测试类TestObjectSize
package test.object_size;
/**
* @author humorchen
* @date 2022/2/10 15:31
*/
public class TestObjectSize {
public static void main(String[] args) throws Exception {
System.out.println("1 memory size = " + ObjectSizeUtil.fullSizeOf(1));
int in = 1;
System.out.println("int 1 memory size = " + ObjectSizeUtil.fullSizeOf(in));
Integer integer = new Integer(1);
System.out.println("Integer 1 size = " + ObjectSizeUtil.fullSizeOf(integer));
int[] ints0 = new int[0];
int[] ints1 = new int[1];
int[] ints2 = new int[2];
int[] ints3 = new int[3];
int[] ints4 = new int[4];
int[] ints5 = new int[200];
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints0));
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints1));
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints2));
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints3));
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints4));
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints5));
ints1[0] = 1090;
ints2[0] = 10910;
ints2[1] = 221090;
System.out.println("after set value");
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints0));
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints1));
System.out.println("int array of 0 length memory size = " + ObjectSizeUtil.fullSizeOf(ints2));
String s1 = "13";
String s2 = "123456";
System.out.println("s1 memory size: " + ObjectSizeUtil.sizeOf(s1));
System.out.println("s2 memory size: " + ObjectSizeUtil.sizeOf(s2));
char[] chars1 = {'1', '2'};
//16+8
char[] chars2 = {'1', '2', '1', '2', '1', '2', '1', '1'};
//16+16
System.out.println("chars1 memory size: " + ObjectSizeUtil.fullSizeOf(chars1));
System.out.println("chars2 memory size: " + ObjectSizeUtil.fullSizeOf(chars2));
System.out.println("chars1 full memory size: " + ObjectSizeUtil.sizeOf(chars1));
System.out.println("chars2 full memory size: " + ObjectSizeUtil.sizeOf(chars2));
Demo demo = new Demo();
System.out.println("demo sizeof size: " + ObjectSizeUtil.sizeOf(demo));
System.out.println("demo fullSizeOf size: " + ObjectSizeUtil.fullSizeOf(demo));
// Thread.sleep(1000000);
}
}
- MANIFEST.MF
路径为:src/main/resources/META-INF/MANIFEST.MF
内容为你的获取对象内存大小工具类位置
Premain-Class: test.object_size.ObjectFetcher
- 打包运行
点击maven的package
或者命令
mvn package
进入target目录后运行这个测试类
java -javaagent:.\Test-1.0-SNAPSHOT.jar test.object_size.TestOb
jectSize
示例和解释
对象代码
public class Demo {
public char[] chars = {'2', '3', '2', '3', '2', '3', '2', '3'};
}
创建对象代码
Demo demo = new Demo();
该对象的堆内存大小为16字节(不计算对象里指针引用的对象大小,也称为浅堆,如果递归计算引用的对象大小则称为深堆大小),对象状态信息8个字节,对象类型信息4个字节,属性chars的对象指针4个字节(两种不同方法查看对象大小的时候是会有差异的,反正两种方法看大小相差不会超过8字节)
底层分析
java对象内存结构
对象头Header
由以下组成:Mark Word、类型指针(Class Pointer虚拟机通过这个知道当前对象是哪个类的实例)、数组长度(数组才有)。
- Mark Word
记录了对象和锁有关的信息,在32位JVM中是32bit(4字节),在64位JVM中是64bit(8字节)。
锁状态 存储内容 偏向锁标志位 锁标志位
无锁 哈希值,分代年龄 0 01
偏向锁 线程ID、时间戳、分代年龄 1 01
轻量级锁 指向栈中锁记录的指针 无 00
重量级锁 指向monitor的指针 无 10
GC标记 无 无 11
其中无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态。
-
类型指针Class Pointer
指向对象类型的指针,32bit(4字节)
-
数组长度(对象为数组的时候才有)
32bit(4字节可用)
这一部分总共8字节+4字节+4字节(数组长度或者对齐空间),也就是一个对象最少是16字节。
淘宝内部PPT的图为如下
java指针压缩技术
在C++里,32位机器上一个指针4个位,最大寻址为4G,这也是为什么32为机器运行内存最大4G。64位机器上最大寻址是2的64次方比特,为16384PB,也就是16777216TB,而我们目前的计算机运行内存无法达到这个量,因此够用,而64bit则一个指针的大小为8字节
在jvm里分配内存是8字节为最小单位的,因此64为jvm假设按指针内存大小为4字节就可以拥有32G的寻址了,8字节则浪费掉了一半,因此jvm做了指针压缩技术,将64位机器上jvm的指针由原本的8字节减小到了4字节。
Java 使用内存指针压缩来解决这个问题。 指针不再表示对象在内存中的精确位置,而是表示偏移量 。32 位的指针可以引用 40 亿个对象 , 而不是 40 亿个字节。最终,也就是说堆内存增长到 32 GB 的物理内存,也可以用 32位的指针表示。
一旦超过32G内存,为了能达到超过32G内存的寻址,此时指针压缩就会失效。即便你有足够的内存,也尽量不要 超过32GB。因为它浪费了内存,降低了 CPU 的性能,还要让 GC 应对大内存。
从jdk1.6 update14开始,在64bit操作系统中,JVM支持指针压缩,jvm配置参数:UseCompressedOops,compressed–压缩、oop–对象指针,启用指针压缩:-XX:+UseCompressedOops,禁止指针压缩:-XX:-UseCompressedOops。
本文来自博客园,作者:HumorChen99,转载请注明原文链接:https://www.cnblogs.com/HumorChen/p/18039506