【Java】Java对象内存模型

简介: 【Java】Java对象内存模型

前言

典型的 Java 实现使用 8 位表示字节,用 2 字节(16 位)表示一个 char 值,

用 4 字节(32 位)表示一个 int 值,用 8 字节(64 位)表示一个 double 或者 long 值,用 1 字节表示一个 boolean 值(因为计算机访问内存的方式都是一次 1 字节)。根据可用内存的总量就能够计算出保存这些值的极限数量。例如,如果计算机有 1 GB 内存(10 亿字节),那么同一时间最多能在内存中保存 2.56 亿万个 int 值或是 1.28 亿万个 double 值。

许多数据结果都涉及对机器地址的表示,而在各种计算机中一个机器地址所需的内存又各有不同。为了保持一致,假设表示机器地址需要 8 字节(C语言中的指针就是这个大小),这是现在广泛使用的 64 位构架中的典型表示方式,许多老式的 32 位构架只使用 4 字节表示机器地址。

一个对象的内存占用情况

要知道一个对象所使用的内存量,需要将所有实例变量使用的内存与对象本身的开销(一般是16 字节)相加。这些开销包括一个指向对象的类的引用、垃圾收集信息以及同步信息。另外,一般内存的使用都会被填充为8字节(64位计算机中的机器字)

的倍数。例如,一个 Integer 对象会使用 24 字节(16字节的对象开销,4 字节用于保存它的 int 值以及 4个填充字节)。类似地,一个Date对象需要使用 32 字节:16 字节的对象开销,3 个 int 实例变量各需 4 字节,以及 4 个填充字节。对象的引用

一般都是一个内存地址,因此会使用 8 字节。例如,一个 Counter 对象需要使用 32 字节:16 字节的对象开销,8 字节用于它的 String 型实例变量(一个引用),4 字节用于 int 实例变量,以及4 个填充字节。当我们说明一个引用所占的内存时,我们会单独说明它所指向的对象所占用的内存,因此这个内存使用总量并没有包含 String 值所使用的内存。

链表

嵌套的非静态(内部)类,例如我们的Node类,还需要额外的 8 字节(用于一个指

向外部类的引用)。因此,一个 Node 对象需要使用 40 字节(16 字节的对象开销,指向 Item 和 Node对象的引用各需 8 字节,另外还有 8 字节的额外开销)。因为 Integer 对象需要使用 24 字节,一个含

有 N 个整数的基于链表的栈(请见算法 1.2)需要使

用(32+64N)字节,包括 Stack 对象的 16 字节的开

销,引用类型实例变量8字节,int型实例变量4字节,

4 个填充字节,每个元素需要 64 字节,一个 Node 对象的 40 字节和一个 Integer 对象的 24 字节。

数组

Java 中数组被实现为对象,它们一般都会因为记录长度而需要额外的内存。一个原始数据类型的数组一般需要 24 字节的头信息(16字节的对象开销,4 字节用于保存长度以及 4 填充字节)再加上保存值所需的内存。例如,一个含有 N 个 int 值的数组需要使用(24 + 4N)字节(会被填充为 8 的倍数),一个含有 N 个 double值的数组需要使用(24 + 8N)字节。一个对象的数组就是一个对象的引用的数组,所以我们应该在对象所需的内存之外加上引用所需的内存。例如,一个含有 N 个 Date 对象的数

组需要使用 24 字节(数组开销)加上 8N 字节(所有引用)加上每个对象的 32 字节,总共(24 + 40N)字节。二维数组是一个数组的数组(每个数组都是一个对象)。例如,一个 M×N 的 double类型的二维数组需要使用 24 字节(数组的数组的开销)加上 8M 字节(所有元素数组的引用)加上 24M 字节(所有元素数组的开销)加上 8MN 字节(M 个长度为 N 的 double 类型的数组),总共(8MN+32M+24)~ 8MN 字节;当数组元素是对象时计算方法类似,结果相同,用来保存充满指向数组对象的引用的数组以及所有这些对象本身。

字符串对象

我们可以用相同的方式说明 Java 的 String 类型对象所需的内存,只是对于字符串来说别名是非常常见的。String 的标准实现含有 4 个实例变量:一个指向字符数组的引用(8 字节)和三个 int 值(各 4 字节)。第一个 int 值描述的是字符数组中的偏移量,第二个 int 值是一个计数器(字符串的长度)。按照下图所示的实例变量名,对象所表示的字符串由 value[offset]到 value[offset + count - 1] 中的字符组成。String 对象中的第三个 int 值是一个散列值,它在某些情况下可以节省一些计算,现在可以忽略它。因此,每个 String 对象总共会使用字节(16 字节表示对象,三个 int 实例变量各需 4 字节,加上数组引用的 8 字节和 4 个填充字节)。这是除字符数组之外字符串所需的内存空间,所有字符所需的内存需要另记,因为 String 的 char数组常常是在多个字符串之间共享的。因为 String 对象是不可变的,这种设计使 String 的实现在能够在多个对象都含有相同的 value[] 数组时节省内存。

字符串的值和子字符串

一个长度为 N 的 String 对象一般需要使用 40 字节(String 对象本身)加(24+2N)字节(字符数组),总共(64+2N)字节。但字符串处理经常会和子字符串打交道,所以 Java 对字符串的表示希望能够避免复制字符串中的字符。当你调用 substring() 方法时,就创建了一个新的 String对象(40 字节),但它仍然重用了相同的 value[] 数组,因此该字符串的子字符串只会使用 40 字节的内存。含有原始字符串的字符数组的别名存在于子字符串中,子字符串对象的偏移量和长度域标记了子字符串的位置。换句话说,一个子字符串所需的额外内存是一个常数,构造一个子字符串所需的时间也是常数,即使字符串和子字符串的长度极大也是这样。某些简陋的字符串表示方法在创建子字符串时需要复制其中的字符,这将需要线性的时间和空间。确保子字符串的创建所需的空间(以及时间)和其长度无关是许多基础字符串处理算法的效率的关键所在。字符串的值与子字符串示例如上图所示。这些基础机制能够有效帮助我们估计大量程序对内存的使用情况,但许多复杂的因素仍然会使这个任务变得更加困难。我们已经提到了别名可能产生的潜在影响。另外,当涉及函数调用时,内存的消耗就变成了一个复杂的动态过程,因为 Java 系统的内存分配机制扮演一个重要的角色,而这套机制又和 Java 的实现有关。例如,当你的程序调用一个方法时,系统会从内存中的一个特定区域为方法分配所需要的内存(用于保存局部变量),这个区域叫做栈(Java 系统的下压栈)。当方法返回时,它所占用的内存也被返回给了系统栈。因此,在递归程序中创建数组或是其他大型对象是很危险的,因为这意味着每一次递归调用都会使用大量的内存。当通过 new 创建对象时,系统会从堆内存的另一块特定区域为该对象分配所需的内存。而且,你要记住所有对象都会一直存在,直到对它的引用消失为止。此时系统的垃圾回收进程会将它所占用的内存收回到堆中。这种动态过程使准确估计一个程序的内存使用变得极为困难。


相关文章
|
14天前
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
55 0
|
8天前
|
Java
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
13 2
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
|
9天前
|
存储 缓存 Java
java线程内存模型底层实现原理
java线程内存模型底层实现原理
java线程内存模型底层实现原理
|
20天前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
22 9
Java——类与对象(继承和多态)
|
20天前
|
SQL Java 编译器
Java——类与对象(封装)
封装是面向对象编程中的概念,指将数据(属性)和相关操作(方法)组合成独立单元(类),使外部无法直接访问对象的内部状态,只能通过提供的方法进行交互,从而保护数据安全。例如,手机将各种组件封装起来,只暴露必要的接口供外部使用。实现封装时,使用`private`关键字修饰成员变量,并提供`get`和`set`方法进行访问和修改。此外,介绍了包的概念、导入包的方式及其注意事项,以及`static`关键字的使用,包括静态变量和方法的初始化与代码块的加载顺序。
23 10
Java——类与对象(封装)
|
20天前
|
Java C语言
Java——类与对象
这段内容介绍了Java中的类和对象、`this`关键字及构造方法的基本概念。类是对现实世界事物的抽象描述,包含属性和方法;对象是类的实例,通过`new`关键字创建。`this`关键字用于区分成员变量和局部变量,构造方法用于初始化对象。此外,还介绍了标准JavaBean的要求和生成方法。
21 9
Java——类与对象
|
9天前
|
存储 Java
Java编程中的对象和类
【8月更文挑战第55天】在Java的世界中,“对象”与“类”是构建一切的基础。就像乐高积木一样,类定义了形状和结构,而对象则是根据这些设计拼装出来的具体作品。本篇文章将通过一个简单的例子,展示如何从零开始创建一个类,并利用它来制作我们的第一个Java对象。准备好让你的编程之旅起飞了吗?让我们一起来探索这个神奇的过程!
25 10
|
4天前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
14 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
11天前
|
Java 编译器
深入理解Java内存模型:从基础到高级
本文旨在通过通俗易懂的方式,引导读者深入理解Java内存模型(JMM)的核心概念和工作原理。我们将从简单的基础知识入手,逐步探讨重排序、顺序一致性问题以及volatile关键字的实现机制等高级主题。希望通过这篇文章,你能够对Java内存模型有一个清晰、全面的认识,并在实际编程中有效地避免并发问题。
|
8天前
|
存储 算法 Java
深入理解Java内存管理
本文将通过通俗易懂的语言,详细解析Java的内存管理机制。从JVM的内存结构入手,探讨堆、栈、方法区等区域的具体作用和原理。进一步分析垃圾回收机制及其调优方法,最后讨论内存泄漏的常见场景及防范措施。希望通过这篇文章,帮助读者更好地理解和优化Java应用的内存使用。
下一篇
无影云桌面