Java对象之生

简介: 内存、性能是程序永恒的话题,实际开发中关于卡顿、OOM也经常是打不完的两只老虎,关于卡顿、OOM的定位方法和工具比较多,这篇文章也不打算赘述了,本章主要是来整理一下JVM的内存模型以及Java对象的生与死。 生存空间(内存区域) Java程序运行在JVM之上,如果Java对象是一个有血有肉的生灵

内存、性能是程序永恒的话题,实际开发中关于卡顿、OOM也经常是打不完的两只老虎,关于卡顿、OOM的定位方法和工具比较多,这篇文章也不打算赘述了,本章主要是来整理一下JVM的内存模型以及Java对象的生与死。

生存空间(内存区域)

Java程序运行在JVM之上,如果Java对象是一个有血有肉的生灵,那么它生存环境是怎样的呢?很多人把Java内存分为堆内存(Heap)和栈内存(Stack),实际上这种划分比较出粗糙和片面。比较细致的划分是这样的:

内存区域

分为程序计数器、虚拟机栈、本地方法栈、方法区和堆。

程序计数器

理解程序计数器之前,我们先来理解一下线程的并行:

线程并行

感官上两条线程是同时执行的,但是在一个CPU上实际上是切换轮流执行的,在同一时刻,CPU不会同时执行一个线程,线程的“并行”通过CPU的高速切换来实现的。

线程切换执行

现在的问题是:线程切换回来之后是如何确定当前线程之前执行的位置和状态?
答案是:使用程序计数器。每一条线程需要一个独立的程序计数器来记录线程执行的状态,各个线程之间的计数器互不影响,所以程序计数器是线程私有的内存区域。所以说线程越多开销越大。

Java 虚拟机栈

虚拟机栈也是线程私有的内存区域,用于存储方法执行过程中的局部变量、操作数栈、方法出入口等信息。线程执行每一个方法都会创建一个栈帧,栈帧中就包含了局部变量表、操作数栈、方法出入口等信息,局部变量表存放基本类型的临时变量,包含boolean、byte、char、short、int、float、long、doubble和对象应用类型(reference,对象地址)。例如下面一段代码:

_

假如某线程执行方法a(),那么该线程的栈内存大概是这样的:

_1

假如方法执行到15行,方法b()的栈帧创建并入栈:

_2

执行完15行到16行,方法b的栈帧出栈:

_1

Java虚拟机栈,就是我们常说的栈内存,如果线程请求的深度超过虚拟机运行的深度就会抛出 StackOverflowError 的异常。

本地方法栈

本地方法栈和虚拟机栈是类似的,虚拟机栈是为虚拟机执行 Java 代码服务,本地方法栈是为虚拟机使用Native 方法服务。在 HotSpot 虚拟机中本地方法栈也会抛出 StackOverflowError 的异常。

对象栖息之地-Java堆

堆内存,大部分人都比较熟悉了,Java 堆是虚拟机中站内存最大的一块区域,是所有对象实例的土壤。堆内存是线程共享的,所有线程产生的对象都要在这块区域中划分内存。

_

Java 堆是垃圾回收器主要管理的区域,又叫 “GC堆”,从垃圾回收器的角度来看 Java 堆又分为新生代和老生代(和垃圾回收器的分代算法相关,见:《《 Java 对象之死》);从线程共享的情况来看,Java 堆还可能划分为每个线程划分一个下的内存区域作为线程使用的缓存区域(Thread Local Allocation Buffer,简称TLAB,后面会进一步说明)。无论怎么划分,当堆中没有足够的空间来存放新的实例时就会抛出 OutOfMemoryError(OOM) 异常。

方法区

和 Java 堆一样,也是线程共享的,这部分内存用于存储类信息、常量、静态变量和即时编译的代码数据。

方法区

类型信息,指的是类型和其指针的对应关系,在创建和访问对象的时候得到,用于查找区分类型。例如有两个类,class A 和 class B ,假如这两个类都加载了,那么方法区大概是这样记录的:

_

常量中还有一部分叫做运行时常量池,这部分并不是在编译和加载期间产生的,而是运行期间产生的,例如:

String a = "abc";
String b = "def";
String c = a+b;

上述代码产生的 "abc" 和 "def" 会被存放到运行时常量池中。方法区的内存使用超过限制会抛出 StackOverflowError 的异常。

对象的“出生”

前面介绍了对象的生存环境:内存的区域和各个区域的作用。接下来说说对象的“出生”,一个 new 关键字到底包含了那些“不为人知”的过程?

对象创建过程

当程序执行遇到一个 new 关键字之后,首先会去方法区参赛定位到这个类符合的引用,查询到是什么类之后再去检查这个类有没有被加载,如果没有执行类加载过程,类加载过程也是一个比较复杂的过程,这里不展开论述。

在类加载完成(或者已经加载过)之后,接下来就开始为新的对象分配内存了,为对象分配内存一般有两种方式:“指针碰撞”和“空闲列表”。如果 Java 堆中正在使用的内存和空闲内存分别都是连续的规整的,中间临界点存放一个指针作为分界标识,为新对象分配内存的时候该指针移动和这个对象大小相等的一段距离就行了,所以叫“指针碰撞”。如果 Java 堆中正在使用的内存和空闲内存不是连续的,那么就没有办法是用指针碰撞这种方式分配内存了,虚拟机就必须维护一个列表来记录那些内存是空闲的,在分配内存的时候就冲空闲列表中找一份足够大的空间类分配给对象,这种方式称为“空闲列表”。是用“指正碰撞”还是“空闲列表”取决 Java 堆内存是否规整,而 Java 堆内存是否规整取决于垃圾回收器使用的回收算法(参考《 Java 对象之死》)是否带有压缩功能。

前面提到 Java 堆内存是线程共享的,多线程同时在堆内存中分配内存,就要保证内存划分的原子行。如何保证内存分配的线程安全?一般有两种方案,第一种方案就是实用同步控制处理,第二种实用 Thread Local Allocation Buffer(TLAB)方式。第一种用多说了很好理解,不用过多解释。TLAB,即线程本地缓冲,就是预先为每个线程分配一个 TLAB ,当线程需要使用内存的时候就在自己的 TLAB 上分配就好了。

内存分配玩之后需要对对象进行必要的设置,例如对象类型信息、元数据
对象哈希码、GC年龄等。

对象长啥样子

通过上面的介绍我们知道对象的“出生”过程了,对象“长啥样”呢?对象在内存中可以分为3个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
对象头包括哈希码、GC代年龄、锁状态、线程持有锁、偏向线程ID、时间戳等,另外还包含类型指针。类型指针的作用是JVM使用这个类型指针来查找这个对象是属于那个类的。实例数据部分才是真正存储对象信息那部分。对齐补充部分不是必然存在的,仅仅用于站位,因为 HotSpot VM 要求对象的大小必须是8字节的整数倍,对象头的长度已经是8字节的整数倍,实例数据大小不固定,所以使用Padding部分来填充。

对象布局

和它握手

对象已经创建了,并且已经知道它的大概模样,如何访问它呢。在 HotSpot 中使用直接指针访问的。前面介绍过虚拟机栈,方法执行过程中创建的栈帧中局部变量表中存储了方法的局部变量,包含基本类型和引用类型,其中引用类型其实就是一个指向对象内存地址的指针。

对象访问

Java 对象之死

《 Java 对象之死》

目录
相关文章
|
6天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
10天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
34 17
|
9天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
18天前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第7天】Java零基础教学篇,手把手实践教学!
23 6
|
22天前
|
Oracle Java 关系型数据库
重新定义 Java 对象相等性
本文探讨了Java中的对象相等性问题,包括自反性、对称性、传递性和一致性等原则,并通过LaptopCharger类的例子展示了引用相等与内容相等的区别。文章还介绍了如何通过重写`equals`方法和使用`Comparator`接口来实现更复杂的相等度量,以满足特定的业务需求。
16 3
|
22天前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
28天前
|
XML Java Maven
在 Cucumber 测试中自动将 Cucumber 数据表映射到 Java 对象
在 Cucumber 测试中自动将 Cucumber 数据表映射到 Java 对象
44 7
|
23天前
|
存储 Java 数据管理
Java零基础-Java对象详解
【10月更文挑战第3天】Java零基础教学篇,手把手实践教学!
12 1
|
27天前
|
Java 数据安全/隐私保护
java类和对象
java类和对象
21 5
|
2月前
|
Java
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
26 2
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用