对象创建时内存分配

从归属区分:

归属线程的:虚拟机栈、本地方法栈、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位系统中

java实现对象存储大文件下载 java对象存储在哪_sed

关于类型指针(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 图解对象内存分配

java实现对象存储大文件下载 java对象存储在哪_方法区_02

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监控看看就知道了

java实现对象存储大文件下载 java对象存储在哪_Java中Object内存_03

如果我们增加一个对象呢

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

java实现对象存储大文件下载 java对象存储在哪_加载_04