【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 创建对象时,系统会从堆内存的另一块特定区域为该对象分配所需的内存。而且,你要记住所有对象都会一直存在,直到对它的引用消失为止。此时系统的垃圾回收进程会将它所占用的内存收回到堆中。这种动态过程使准确估计一个程序的内存使用变得极为困难。


相关文章
|
1月前
|
设计模式 网络协议 数据可视化
Java 设计模式之状态模式:让对象的行为随状态优雅变化
状态模式通过封装对象的状态,使行为随状态变化而改变。以订单为例,将待支付、已支付等状态独立成类,消除冗长条件判断,提升代码可维护性与扩展性,适用于状态多、转换复杂的场景。
271 0
|
2月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
287 3
|
3月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
1月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
66 4
|
1月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
2月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
525 17
|
3月前
|
缓存 安全 Java
Java反射机制:动态操作类与对象
Java反射机制是运行时动态操作类与对象的强大工具,支持获取类信息、动态创建实例、调用方法、访问字段等。它在框架开发、依赖注入、动态代理等方面有广泛应用,但也存在性能开销和安全风险。本文详解反射核心API、实战案例及性能优化策略,助你掌握Java动态编程精髓。
|
3月前
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
203 1
|
3月前
|
存储 监控 算法
Java垃圾回收机制(GC)与内存模型
本文主要讲述JVM的内存模型和基本调优机制。
|
4月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
237 0

热门文章

最新文章