java对象的内存

对象创建时内存分配

JMM描述我们了解到https://www.cnblogs.com/dhcao/p/10897256.html

  • 从归属区分:

    • 归属线程的:虚拟机栈、本地方法栈、pc计数器
    • 归属jvm的:堆、方法区
  • 从功能区分:

    • 保存对象实例数据:堆
    • 保存类的数据:方法区
    • 保存方法变量:虚拟机栈
    • 保存本地方法变量:本地方法栈
    • 保存线程执行位置:pc计数器
  • ps:jdk8以前,HotSpot通常用永久代来作为方法区的实现,其内存大小在启动时确定,虽然gc会处理这里的垃圾,但是当加载过多的类时,还是会出现oom。

    在jdk8以后的版本中,元空间取代了永久代,元空间开辟在本地内存(不在虚拟机中,直接使用服务器内存),理论上不会出现内存不足,并且将原先永久带中的字符串常量移到堆中,其他元数据(类元、字段、静态属性、方法、常量等)移动到元空间。

1 内存管理

new:我们很多时候,使用new来创建对象,new是强类型校验,他能调动所有的构造函数,当机器遇到new指令的时候,先检查类是否已经被加载,如果没有,那么先进行类加载。加载完毕,则开始在堆中分配内存。

​ 对象的内存我理解这里分为三个部分(也可将后两者放一起,毕竟都在堆中,也都属于对象):

  • 在方法区中分配对象元数据,类元、字段、静态属性、方法、常量等
  • 在堆中为对象分配对象头信息内存
  • 初始化对象在堆中分配对象的实例数据内存。

2 类加载时分配对象元数据区

​ 在java8之后,元空间取代perm(永久代)作为元数据所在地,并将常量池移动到堆中。

  • 在类加载过程“加载”阶段,虚拟机将class文件用二进制流方式读取到方法区
  • 在方法区中生成Class对象以保存类信息
  • 分配类变量(与之相比较的叫实例变量)内存,static修饰的变量、final域中的变量赋值、字段表、常量池等等。

注:由于数组时没有对象信息的,所以无法通过类加载器来加载数组(类加载器通过加载class文件获得类信息),所以数组是由虚拟机而不是类加载器创建的。

数组是一串连续的内存地址....大小一旦分配就无法更改。

3 对象头信息数据区

​ 对象保存在堆中,对象头信息包括2部分:对象标记(Mark Word) 和 类型指针

  • 该数据一定是在堆中,属于对象的。

  • 对象的运行时信息,记做Mark Word。

  • 此对象头信息记录如下类型

    存储内容 偏向锁标志位(biased_lock 标志位(lock) 状态
    对象哈希码、对象分代年龄 0 01 未锁定
    指向锁记录的指针 0 00 轻量级锁定
    指向重量级锁的指针 0 10 膨胀(重量级锁定)
    空,不需要记录信息 0 11 GC标记
    偏向线程ID、偏向时间戳、对象分代年龄 1 01 偏向锁

    其一是对象的各种记录:

    所谓运行时信息,值的是这个对象在运行过程中需要保存的信息,既然是运行时才记录,说明随着对象的运行而在不断变化。

    例如:对象分代年龄,对象每在一次Minor GC中存活,将年龄增加1,并在适当时候(默认年龄15,可设置)移动到“老年代”。

    其二是对象的锁状态:

    偏向锁标志位(biased_lock)+ 标志位(lock)决定了对象的锁状态。

    • 该部分内存在32位和64位系统中,分别占用32bit和64bit内存
    • 32位系统中:
      • 25bit储存对象哈希码(identity_hashcode
      • 4bit储存对象年龄分代(age
      • 1bit储存偏向锁标志位(biased_lock
      • 2bit储存锁标志位(lock
    |-------------------------------------------------------|--------------------|
    |                  Mark Word (32 bits)                  |       State        |
    |-------------------------------------------------------|--------------------|
    | identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
    |-------------------------------------------------------|--------------------|
    |  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
    |-------------------------------------------------------|--------------------|
    |               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
    |-------------------------------------------------------|--------------------|
    |               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
    |-------------------------------------------------------|--------------------|
    |                                              | lock:2 |    Marked for GC   |
    |-------------------------------------------------------|--------------------|
    
    • 64位系统中

  • 关于类型指针(class pointer)

    • 这部分占用4个字节(如果不开启指针压缩,则占8个字节,此处取4个字节)

    明显就是在对象头中指向方法区中class元数据的指针,通过此指针,我们可以通过对象来获取到对象的class信息。毕竟class是在方法区中,而对象是在堆中,如果没有指针关联是不科学的,而且由于一个class可以有多个对象实例,所以肯定是由对象指向class

这里是不是很明显,如何计算一个对象的大小??

在64位机器上,对象头信息包含64bit(8个字节)的对象运行时信息(Mark Word ),还有类型指针(class pointer)4个字节。那么就算没有任何实例需要赋值,一个对象的创建最少需要12个字节但由于HotSpot VM要求对象初始化大小必须是8的倍数,所以实际需要16个字节

4 对象的实例数据区

​ 在类加载“初始化”后,如果对象需要初始化赋值,那么需要执行init方法,该方法将为对象赋值实例数据

  • 该内存一定是在堆内存区,也是属于对象的。
  • 如果对象是引用,那么只需要保存实例的引用,一个引用占用4个字节。
  • 如果是基本类型,则根据对象类型保存对应长度

5 填充区域

​ 由于HotSpot VM要求对象初始化大小必须是8的倍数,所以有这个要求而已。

6 图解对象内存分配

7 实例:计算对象内存大小

/**
 * @Author: dhcao
 * @Version: 1.0
 */
public class OneObj {

    // 1. 对象头占用12个字节

    // 2. 这个在方法区,不包含在对象中,占0个字节
    private static int a = 10;

    // 3. 3个int共占12个字节
    int a1;
    int a2;
    int a3;

    // 4. 2个refObj共占8个字节
    Object b1;
    Object b2;

    // 5. 此处也只算引用,只占8个字节。ps:一个Integer对象大小是16个字节...
    Integer o1 = new Integer(91);
    Integer o2 = new Integer(98);

    // 所以共占:12 + 12 + 8 + 8 = 40 。正好是8的倍数,所以就占40个字节。
}



// 测试
/**
 * @Author: dhcao
 * @Version: 1.0
 */
public class SizeTest {
    public static void main(String[] args) {
        OneObj obj = new OneObj();
        while(true){

        }
    }
}
  • 按照我们的预想,这个是40个字节没问题了,我启动jprofile监控看看就知道了

  • 如果我们增加一个对象呢
 		// 5. 此处也只算引用,占了12个字节。ps:一个Integer对象大小是16个字节...
                Integer o1 = new Integer(91);
                Integer o2 = new Integer(98);
		Integer o3 = new Integer(98);
	
		// 此时比上面测试多了一个引用o3,那么应该是44个字节,进行补齐,应为48字节

原文地址:https://www.cnblogs.com/dhcao/p/11908902.html