「Java对象的指针压缩」java指针压缩原理

博主:adminadmin 2023-01-24 01:36:09 294

本篇文章给大家谈谈Java对象的指针压缩,以及java指针压缩原理对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

怎么确定Java对象的大小

普通对象的结构如下,按64位机器的长度计算

1. 对象头(_mark), 8个字节

2. Oop指针,如果是32G内存以下的,默认开启对象指针压缩,4个字节

3. 数据区

4.Padding(内存对齐),按照8的倍数对齐

数组对象结构是

1. 对象头(_mark), 8个字节

2. Oop指针,如果是32G内存以下的,默认开启对象指针压缩,4个字节

3. 数组长度,4个字节

4. 数据区

5. Padding(内存对齐),按照8的倍数对齐

清楚了对象在内存的基本布局后,咱们说两种计算Java对象大小的方法

1. 通过java.lang.instrument.Instrumentation的getObjectSize(obj)直接获取对象的大小

2. 通过sun.misc.Unsafe对象的objectFieldOffset(field)等方法结合反射来计算对象的大小

java.lang.instrument.Instrumentation.getObjectSize()的方式

先讲讲java.lang.instrument.Instrumentation.getObjectSize()的方式,这种方法得到的是Shallow Size,即遇到引用时,只计算引用的长度,不计算所引用的对象的实际大小。如果要计算所引用对象的实际大小,可以通过递归的方式去计算。

java.lang.instrument.Instrumentation的实例必须通过指定javaagent的方式才能获得,具体的步骤如下:

1. 定义一个类,提供一个premain方法: public static void premain(String agentArgs, Instrumentation instP)

2. 创建META-INF/MANIFEST.MF文件,内容是指定PreMain的类是哪个: Premain-Class: sizeof.ObjectShallowSize

3. 把这个类打成jar,然后用java -javaagent XXXX.jar XXX.main的方式执行

有兴趣可以看下博主的:

es 不建议设置堆内存超过32GB原因

开启指针压缩技术,用4字节32位存储压缩地址,未开启的话,将会使用8字节存储真实地址

未开启缺点

增加了GC开销: 需要占用更多的堆空间,有效空间将会减少,导致频繁的进行GC.

降低CPU缓存命中率: 缓存的oop变少了,降低效率.

所以默认开启指针压缩

java的对齐填充机制(就想内存的8bit为1byte一样)

将java堆内存进行8字节划分

java对象的指针地址就可以不用存对象的真实的64位地址了,而是可以存一个映射地址编号.

所以可以寻址32GB(2^32 * 8)

一个Java对象到底占多大内存

对象头

对象头在32位系统上占用8bytes,64位系统上占用16bytes。

实例数据

原生类型(primitive type)的内存占用如下:

Primitive Type Memory Required(bytes)

boolean 1

byte 1

short 2

char 2

int 4

float 4

long 8

double 8

reference类型在32位系统上每个占用4bytes, 在64位系统上每个占用8bytes。

对齐填充

HotSpot的对齐方式为8字节对齐:

(对象头 + 实例数据 + padding) % 8等于0且0 = padding 8

指针压缩

对象占用的内存大小收到VM参数UseCompressedOops的影响。

1)对对象头的影响

开启(-XX:+UseCompressedOops)对象头大小为12bytes(64位机器)。

static class A {

int a;

}

jdk1.6与1.7的区别?

jdk1.7比jdk1.6添加了一些新的特性。

1、JDK1.7的新特性:

现在的 Java7也是采用了模块的划分方式来提速,一些不是必须的模块并没有下载和安装,因此在使用全新的Java7的虚拟机的时候会发现真的很快,当虚拟机需要用到某些功能的时候,再下载和启用相应的模块,这样使得最初需要下载的虚拟机大小得到了有效的控制。

2、JDK1.7与JDK1.6的变化

在JDK1.7的新特性方面主要有下面几方面的增强: 1.jdk7语法上 1.1二进制变量的表示,支持将整数类型用二进制来表示,用0b开头 。

3、Java 7的功能

编程方面,带来了很多令人激动的新功能,这将使你的应用程序具备更好的并行任务性能。

如何准确计算Java对象的大小

首先,我们先写一段大家可能不怎么写或者认为不可能的代码:一个类中,几个类型都是private类型,没有public方法,如何对这些属性进行读写操作,看似不可能哦,为什么,这违背了面向对象的封装,其实在必要的时候,留一道后门可以使得语言的生产力更加强大,对象的序列化不会因为没有public方法就无法保存成功吧,OK,我们简单写段代码开个头,逐步引入到怎么样去测试对象的大小,一下代码非常简单,相信不用我解释什么:

import java.lang.reflect.Field;

class NodeTest1 {

private int a = 13;

private int b = 21;

}

public class Test001 {

public static void main(String []args) {

NodeTest1 node = new NodeTest1();

Field []fields = NodeTest1.class.getDeclaredFields();

for(Field field : fields) {

field.setAccessible(true);

try {

int i = field.getInt(node);

field.setInt(node, i * 2);

System.out.println(field.getInt(node));

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

代码最基本的意思就是:实例化一个NodeTest1这个类的实例,然后取出两个属性,分别乘以2,然后再输出,相信大家会认为这怎么可能,NodeTest1根本没有public方法,代码就在这里,将代码拷贝回去运行下就OK了,OK,现在不说这些了,运行结果为:

26

42

为什么可以取到,是每个属性都留了一道门,主要是为了自己或者外部接入的方便,相信看代码自己仔细的朋友,应该知道门就在:field.setAccessible(true);代表这个域的访问被打开,好比是一道后门打开了,呵呵,上面的方法如果不设置这个,就直接报错。

看似和对象大小没啥关系,不过这只是抛砖引玉,因为我们首先要拿到对象的属性,才能知道对象的大小,对象如果没有提供public方法我们也要知道它有哪些属性,所以我们后面多半会用到这段类似的代码哦!

对象测量大小的方法关键为java提供的(1.5过后才有):java.lang.instrument.Instrumentation,它提供了丰富的对结构的等各方面的跟踪和对象大小的测量的API(本文只阐述对象大小的测量方法),于是乎我心喜了,不过比较恶心的是它是实例化类:sun.instrument.IntrumentationImpl是sun开头的,这个鬼东西有点不好搞,翻开源码构造方法是private类型,没有任何getInstance的方法,写这个类干嘛?看来这个只能被JVM自己给初始化了,那么怎么将它自己初始化的东西取出来用呢,唯一能想到的就是agent代理,那么我们先抛开代理,首先来写一个简单的对象测量方法:

步骤1:(先创建一个用于测试对象大小的处理类)

import java.lang.instrument.Instrumentation;

public class MySizeOf {

private static Instrumentation inst;

/**

*这个方法必须写,在agent调用时会被启用

*/

public static void premain(String agentArgs, Instrumentation instP) {

inst = instP;

}

/**

* 直接计算当前对象占用空间大小,包括:当前类及超类的基本类型实例字段大小

* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小

* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小

* 用来测量java对象的大小(这里先理解这个大小是正确的,后面再深化)

*/

public static long sizeOf(Object o) {

if(inst == null) {

throw new IllegalStateException("Can not access instrumentation environment.\n" +

"Please check if jar file containing SizeOfAgent class is \n" +

"specified in the java's \"-javaagent\" command line argument.");

}

return inst.getObjectSize(o);

}

}

步骤2:上面我们写好了agent的代码,此时我们要将上面这个类编译后打包为一个jar文件,并且在其包内部的META-INF/MANIFEST.MF文件中增加一行:Premain-Class: MySizeOf代表执行代理的全名,这里的类名称是没有package的,如果你有package,那么就写全名,我们这里假设打包完的jar包名称为agent.jar(打包过程这里简单阐述,就不细说了),OK,继续向下走:

步骤3:编写测试类,测试类中写:

public class TestSize {

public static void main(String []args) {

System.out.println(MySizeOf.sizeOf(new Integer(1)));

System.out.println(MySizeOf.sizeOf(new String("a")));

System.out.println(MySizeOf.sizeOf(new char[1]));

}

}

下一步准备运行,运行前我们准备初步估算下结果是什么,目前我是在32bit模式下运行jvm(注意,不同位数的JVM参数设置不一样,对象大小也不一样大)。

(1) 首先看Integer对象,在32bit模式下,class区域占用4byte,mark区域占用最少4byte,所以最少8byte头部,Integer内部有一个int类型的数据,占4个byte,所以此时为8+4=12,java默认要求按照8byte对象对其,所以对其到16byte,所以我们理论结果第一个应该是16;

(2) 再看String,长度为1,String对象内部本身有4个非静态属性(静态属性我们不计算空间,因为所有对象都是共享一块空间的),4个非静态属性中,有offset、count、hash为int类型,分别占用4个byte,char value[]为一个指针,指针的大小在bit模式下或64bit开启指针压缩下默认为4byte,所以属性占用了16byte,String本身有8byte头部,所以占用了24byte;其次,一个String包含了子对象char数组,数组对象和普通对象的区别是需要用一个字段来保存数组的长度,所以头部变成12byte,java中一个char采用UTF-16编码,占用2个byte,所以是14byte,对其到16byte,24+16=40byte;

(3) 第三个在第二个基础上已经分析,就是16byte大小;

也就是理论结果是:16、40、16;

步骤4:现在开始运行代码:运行代码前需要保证classpath把刚才的agent.jar包含进去:

D:javac TestSize.java

D:java -javaagent:agent.jar TestSize

16

24

16

第一个和第三个结果一致了,不过奇怪了,第二个怎么是24,不是40,怎么和理论结果偏差这么大,再回到理论结果中,有一个24曾经出现过,24是指String而不包含char数组的空间大小,那么这么算还真是对的,可见,java默认提供的方法只能测量对象当前的大小,如果要测量这个对象实际的大小(也就是包含了子对象,那么就需要自己写算法来计算了,最简单的方法就是递归,不过递归一项是我不喜欢用的,无意中在一个地方看到有人用栈写了一个代码写得还不错,自己稍微改了下,就是下面这种了)。

import java.lang.instrument.Instrumentation;

import java.lang.reflect.Array;

import java.lang.reflect.Field;

import java.lang.reflect.Modifier;

import java.util.IdentityHashMap;

import java.util.Map;

import java.util.Stack;

public class MySizeOf {

static Instrumentation inst;

public static void premain(String agentArgs, Instrumentation instP) {

inst = instP;

}

public static long sizeOf(Object o) {

if(inst == null) {

throw new IllegalStateException("Can not access instrumentation environment.\n" +

"Please check if jar file containing SizeOfAgent class is \n" +

"specified in the java's \"-javaagent\" command line argument.");

}

return inst.getObjectSize(o);

}

/**

* 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小

*/

public static long fullSizeOf(Object obj) {//深入检索对象,并计算大小

MapObject, Object visited = new IdentityHashMapObject, Object();

StackObject stack = new StackObject();

long result = internalSizeOf(obj, stack, visited);

while (!stack.isEmpty()) {//通过栈进行遍历

result += internalSizeOf(stack.pop(), stack, visited);

}

visited.clear();

return result;

}

//判定哪些是需要跳过的

private static boolean skipObject(Object obj, MapObject, Object visited) {

if (obj instanceof String) {

if (obj == ((String) obj).intern()) {

return true;

}

}

return (obj == null) || visited.containsKey(obj);

}

private static long internalSizeOf(Object obj, StackObject stack, MapObject, Object visited) {

if (skipObject(obj, visited)) {//跳过常量池对象、跳过已经访问过的对象

return 0;

}

visited.put(obj, null);//将当前对象放入栈中

long result = 0;

result += sizeOf(obj);

Class ?clazz = obj.getClass();

if (clazz.isArray()) {//如果数组

if(clazz.getName().length() != 2) {// skip primitive type array

int length = Array.getLength(obj);

for (int i = 0; i length; i++) {

stack.add(Array.get(obj, i));

}

}

return result;

}

return getNodeSize(clazz , result , obj , stack);

}

//这个方法获取非数组对象自身的大小,并且可以向父类进行向上搜索

private static long getNodeSize(Class ?clazz , long result , Object obj , StackObject stack) {

while (clazz != null) {

Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {

if (!Modifier.isStatic(field.getModifiers())) {//这里抛开静态属性

if (field.getType().isPrimitive()) {//这里抛开基本关键字(因为基本关键字在调用java默认提供的方法就已经计算过了)

continue;

}else {

field.setAccessible(true);

try {

Object objectToAdd = field.get(obj);

if (objectToAdd != null) {

stack.add(objectToAdd);//将对象放入栈中,一遍弹出后继续检索

}

} catch (IllegalAccessException ex) {

assert false;

}

}

}

}

clazz = clazz.getSuperclass();//找父类class,直到没有父类

}

return result;

}

}

Synchronize的实现原理

Java对象在JVM中的结构如下:

java对象包括:

对象都在32/64位机器中每个部分分别是32/64位,Class Pointer在64位机器默认开启指针压缩,只占用32位。

对象加锁使用的是Mark Word字段,如下是32位的Mark Word

通过 synchronize 关键字给对象加锁的过程如下:

JVM引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换 ThreadID 的时候依赖一次CAS原子指令( 一旦出现多线程竞争的情况就必须撤销偏向锁 )。

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁 。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

synchronize的实现过程:

注意 :lock前缀指令的功能:Synchronize, volatile,CMS都是使用这个实现

当锁膨胀成重量级锁的时候,在JVM中当前锁对象关联的ObjectMonitor对象。

ObjectMonitor对象的数据结构如下:

EntryList是一个后进先出的双向链表,AQS(ReentrantLock)是一个先进先出的双向链表。

ObjectMoniter的流程:

注意:

Synchronize只有一个WaitSet,AQS可以创建多个Condition队列(功能和Waitset类似)。

synchronize的实现原理_技术流水-CSDN博客_synchronize

关于Java对象的指针压缩和java指针压缩原理的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。