图文深入理解java的内存分配

news2024/10/21 21:51:57

本篇图文深入讨论java的内存分配。当然,这里所说的深入,并不是指长篇大论,而是就事论事重点深入介绍java的内存分配原理机制。
在理解java如何为变量或者对象分配内存之前,我们先来了解一个问题:为什么需要分配内存?

为什么需要分配内存?

为什么需要分配内存?相信不少人会这么问。这得先从计算机的硬件组成结构说起。

1. 服务器的硬件組成

这里其实就是泛指计算机,但是因为java程序大部分都是run在服务器的,所以本文就以服务器代称。服务器的硬件组成如下图所示:
在这里插入图片描述
这里所包括的只是计算机最核心的一些硬件组成:CPU,内存,disk,并不包含其他外设,核心一词对于java程序来说,就是有了这些结构,java程序就能够运行起来,少了这些硬件它就run不起来。
这些结构里面,CPU和内存都是稀缺资源,稀缺的言下之意就是资源有限,不是你想用多少就能给你多少。所以,就需要提前进行内存的分配,java提前进行内存分配也是好处多多的,不信你看:

2. java进行内存分配的好处

  1. 为对象提供存储位置
    当创建一个 Java 对象时,需要有一块确定的内存空间来存放这个对象的实例数据和对象头信息。如果不先分配内存,就无法确定对象的存储位置,也就无法进行后续的对象初始化操作。例如创建一个自定义的Person对象,Person person = new Person();,如果没有预先分配好内存,这个对象就无处安放,无法在程序中被使用和操作。
  2. 确保数据完整性
    内存分配过程可以确保对象在创建时有一个连续的、合适大小的内存空间。这样可以保证对象的各个成员变量和状态能够正确地存储在内存中,避免数据混乱和错误。如果在对象创建过程中随意地存储数据而没有事先分配好内存,可能会导致数据覆盖、丢失或者存储不完整的情况发生。
  3. 支持垃圾回收机制
    通过先分配内存,Java 的垃圾回收机制可以更好地管理内存。当对象被创建并分配内存后,垃圾回收器可以跟踪这些对象的引用情况。一旦对象不再被引用,垃圾回收器就可以识别出这些无用对象,并回收它们所占用的内存空间,从而提高内存的利用率。如果没有明确的内存分配过程,垃圾回收器将难以确定哪些内存区域是正在使用的对象,哪些是可以回收的无用空间。
  4. 实现多线程安全
    在多线程环境下,内存分配的过程可以通过适当的同步机制来保证线程安全。例如,在对象创建时,可以使用同步块或者原子操作来确保多个线程不会同时尝试分配同一块内存,从而避免内存冲突和数据不一致的问题。这样可以保证程序在多线程环境下的稳定性和可靠性。
  5. 有利开发和调优
    这种明确的内存分配方式也使得开发人员更容易理解和掌握 Java 程序的内存使用情况,便于进行性能优化和调试。

3. java JVM内存与服务器内存的关系

在这里插入图片描述
正如上图所示,实际的java程序基本都涉及到多线程,每个CPU核心都可以访问共享变量,但是每个线程都需要自己的工作内存来处理数据,多线程的执行最终都会映射到硬件处理器上执行。但Java JVM内存模型和硬件内存架构并不完全一致,硬件内存只有寄存器、缓存内存、主内存的概念,并没有工作内存(线程私有数据区域)和主内存(堆内存)之分,也就是说Java内存模型对内存的划分对硬件内存并没有任何影响,因为JMM只是一种抽象的概念,是一个抽象出来的概念,并不实际存在。不管是工作内存的数据还是主内存的数据,对于计算机硬件来说都会存储在计算机主内存中(一些热数据存储在CPU缓存或者寄存器中),所以,JVM的内存分配其实就是一种虚拟的概念,说得简单直白点就是为了提高程序的运行效率而预先分配好的一种内存使用流程和机制,不像硬件那样,并不具有严格的框架约束。

4. java JVM的内存结构

在这里插入图片描述

  1. JVM由三部分组成:类装载子系统,运行时数据区(内存),字节码执行引擎;其中类装载子系统把类加载进来, 放入到虚拟机中,采用双亲委派机制,把类加载进来放入到JVM虚拟机中。然后, 字节码执行引擎去虚拟机中读取数据。
  2. 运行时数据区主要由5个部分构成: 堆,栈,本地方法栈,方法区,程序计数器,下文会详细介绍这5部分。
  3. 如上图所示,JVM的上述三部分密切配合工作。

Java内存分配的基本机制

Java内存分配机制是基于堆栈式的内存管理机制。为了提高运算效率,JVM内存被划分为不同的区域,每个区域都有其特定的用途和管理方式。这些区域主要包括方法区(Method Area)、堆区(Heap)、栈区(Stack)、本地方法栈(Native Method Stack)以及程序计数器(Program Counter Register)。
在这里插入图片描述

1. 方法区:

方法区是JVM中的一块内存区域,用于存储类的元数据(metadata)信息,如类的描述符、字段、方法等信息。在JDK 8及以前,方法区通常被实现为永久代(PermGen space),但在JDK 8及以后版本中,方法区被元空间(Metaspace)所取代,使用本地内存来存储类的元数据,从而避免了永久代在Java堆中的内存限制。

2. 堆区:

堆区是JVM管理的最大一块内存空间,用于存放对象实例和数组。堆内存由JVM自动管理,程序员不需要手动分配和释放。堆内存细分为新生代(Young Generation)和老年代(Old Generation)。新生代又包括Eden区和两个Survivor区(S0和S1)。新创建的对象首先被分配在Eden区,当Eden区满时,会触发一次Minor GC(年轻代GC),将存活的对象复制到其中一个Survivor区,并清空Eden区。经过多次Minor GC后,存活的对象会被移动到老年代。老年代主要存放生命周期较长的大对象。当老年代内存不足时,JVM会触发Major GC或Full GC,这类GC通常代价较高,会导致系统暂停时间较长。

3. 栈区:

栈区是线程私有的,用于存储局部变量、基本数据类型变量的值、对象的引用等。每个方法被调用时,JVM会为该方法在栈中分配一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。当方法执行完毕后,栈帧会被销毁,局部变量等也会被自动释放。栈内存的生命周期与线程同步,每个线程都有自己的栈内存。

5. 本地方法栈:

本地方法栈与栈的作用非常相似,主要区别在于本地方法栈是为执行本地方法(Native Method)服务的。本地方法一般是用C/C++编写的,并且被编译为本地代码。与栈一样,本地方法栈也是线程私有的。

6. 程序计数器:

程序计数器是线程私有的,用于记录当前线程所执行的字节码的行号指示器。它是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,因此为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。

Java内存分配策略及优化技术

1. Java内存分配策略

Java内存分配策略主要包括静态存储分配、栈式分配和堆式分配。

  1. 静态存储分配:
    –在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给它们分配特定的存储空间。
    –静态存储分配要求程序中不允许有可变数据结构的存在(比如数组、链表),也不允许有嵌套或者递归的结构出现,因为它们会导致编译程序无法计算准确的存储空间需求。
  2. 栈式分配(动态存储分配):
    –栈式分配由一个类似于堆栈的运行栈来实现。
    –在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道。但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存。
    栈式存储分配按照先进后出的原则进行分配。
  3. 堆式分配:
    –专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例。
    –堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。

2. Java内存分配的特点

  1. 堆内存的特点:
    可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的。
    Java的垃圾收集器会自动收走这些不再使用的数据。但由于要在运行时动态分配内存,存取速度较慢。
  2. 栈内存的特点:
    –存取速度比堆要快,仅次于寄存器。
    –栈数据可以共享。但栈中的数据大小与生存期必须是确定的,缺乏灵活性。

3. Java内存优化技术

  1. 对象池技术:对于频繁创建的短生命周期对象,如字符串,可以使用对象池技术减少对象创建的开销。例如,String Pool通过缓存不可变字符串,减少了相同字符串的重复创建。
  2. 逃逸分析:逃逸分析是JVM的一个优化技术,用于检测对象是否逃逸出方法。如果对象未逃逸,JVM可以将其分配在栈上而不是堆上,从而避免不必要的垃圾回收。
  3. 减少对象创建:尽量避免在循环或高频率调用的地方频繁创建对象。可以通过使用缓存、对象复用等方式来减少对象的创建次数和垃圾回收压力。
  4. 合理规划数据结构:大对象(如大型数组或集合)直接分配在老年代。频繁分配大对象可能导致老年代快速充满,触发Full GC。因此,需要合理规划数据结构,尽量避免不必要的大对象分配。
  5. 其他内存优化技巧:
    –通过调整JVM参数,可以对堆内存的分配机制进行优化,例如调整Eden区和老年代的比例,或者调整垃圾收集器的行为。
    –合理设置元空间的大小可以避免频繁的内存回收,提高JVM性能。
    –注意避免内存泄漏,如静态集合长时间持有大对象的引用、长生命周期对象不再使用但仍然占用内存、内部类持有外部类的引用等。

Java如何确定对象的内存分配大小?

在 Java 中,确定对象的内存分配大小主要通过以下几个方面:

1. 对象头

对象头占用一定的内存空间。在 64 位的 JVM 中,对象头一般占用 12 字节(开启指针压缩,默认开启)或 16 字节(未开启指针压缩)。对象头中包含了对象的哈希码、分代年龄、锁状态标志等信息。

2. 实例数据

  1. 基本数据类型成员变量:
    不同的基本数据类型占用不同的内存空间。例如,boolean类型占用 1 个字节;byte类型占用 1 个字节;short类型占用 2 个字节;int类型占用 4 个字节;long类型占用 8 个字节;float类型占用 4 个字节;double类型占用 8 个字节;char类型占用 2 个字节。如果对象中有多个基本数据类型的成员变量,它们会按照各自的类型大小依次排列在内存中。
  2. 引用数据类型成员变量:
    引用数据类型(如对象引用、数组引用等)在 64 位 JVM 中一般占用 8 个字节(开启指针压缩)或 16 字节(未开启指针压缩)。引用实际上是指向堆中实际对象或数组的内存地址。
  3. 对齐填充:
    为了满足某些硬件架构的内存对齐要求,对象的内存大小可能会进行填充。例如,如果对象的实例数据部分大小不是 8 字节的整数倍,可能会填充一些字节使得对象的总大小是 8 字节的整数倍。

3. 数组对象

如果是数组对象,除了对象头之外,还需要考虑数组元素的类型和数量。例如,一个包含 int 类型元素的数组,每个元素占用 4 个字节,再加上对象头的大小,就是整个数组对象的内存分配大小。
其实在实际开发中,一般不需要精确地知道对象的内存大小,但了解对象内存分配的大致原理可以帮助你更好地理解 Java 程序的内存使用情况,进行性能优化和故障排查。

不同的数据类型内存具体是怎么分配的?

1. 基本数据类型

基本数据类型(如int、short、long、byte、float、double、boolean、char)的内存分配相对简单。这些类型的数据通常直接存储在栈内存中。栈内存的特点是存取速度快,但大小固定且生存期有限。基本数据类型的变量存的是字面值,不是类的实例,这些字面值的数据由于大小可知、生存期可知,出于追求速度的原因,就存在于栈中。
在这里插入图片描述

2. 对象类型

对象类型的内存分配相对复杂。当创建一个对象时,首先会在栈内存中为对象的引用变量分配空间,然后在堆内存中为对象的实例分配空间。堆内存是Java程序中用于存储对象的主要内存区域,它的大小是可扩展的,因此可以在运行时动态分配空间。例如,创建一个Integer对象时,JVM会首先在堆内存中为其分配空间,并调用Integer类的构造函数来初始化对象。之后,返回一个指向该对象的引用,该引用存储在栈内存中,以便后续通过该引用来访问和操作对象。如下图
在这里插入图片描述

3.字符串

字符串在Java中是一个特殊的对象类型,但它有自己的内存分配机制。字符串字面量(例如String str = “abc”;)会被存储在字符串常量池中。字符串常量池是Java中的一个特殊内存区域,用于存储字符串字面量。当创建一个字符串字面量时,JVM会首先检查字符串常量池中是否已经存在相同内容的字符串。如果存在,则返回该字符串的引用;如果不存在,则在字符串常量池中创建一个新的字符串对象,并返回其引用。
在这里插入图片描述
使用new关键字创建的字符串对象(例如String str = new String(“abc”);)则不会在字符串常量池中创建,而是在堆内存中创建一个新的字符串对象。这意味着每次使用new关键字创建字符串对象时,都会占用额外的内存空间。

4.数组

数组在Java中是一种基本数据结构,用于存储一系列相同类型的数据。数组的内存分配是一块连续的内存空间,用于存储数组中的所有元素。数组的内存模型可以分为两个部分:数组对象本身和数组元素。数组对象是数组的一个实例,包含数组的长度和数组元素的总大小。数组元素是数组中的单个数据项,它们存储在连续的内存空间中。
在这里插入图片描述

对于基本数据类型的数组,数组元素直接存储在数组对象的内存区域之后。对于对象类型的数组,数组元素也是存储在连续的内存空间中,但每个元素都是一个对象。
本篇博文个人是奔着难易适度的原则去写的,可能难免有些错误,请各位大佬指正。

码字不易,宝贵经验分享不易,请各位支持原创,转载注明出处,多多关注作者,家人们的点赞和关注是我笔耕不辍的动力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2220312.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新电脑Win11家庭中文版跳过联网激活方法(教程)

预装Win11家庭中文版的新电脑,如何跳过联网激活;由于微软限制必须要联网激活,需要使用已有的微软账户登入或者注册新的微软账户后才可以继续开机使用,Win11联网后系统会自动激活。下面介绍一下初次开机初始化电脑时如何跳过联网激…

今年双十一最值得入手的好物有哪些?双十一值得选购的好物盘点!

在这个全民狂欢的购物盛宴——双十一,每一个角落都弥漫着诱人的优惠与不可错过的精品。从科技潮品到生活必需品,从时尚尖货到家居好物,无数精选商品在这一季集中绽放,等待着慧眼识珠的你将它们带回家,今年的双十一&…

除GOF23种设计模式之简单工厂模式

文章目录 1. 简介2. 代码2.1 抽象类:Course.java2.2 产品A:JavaCourse.java2.3 产品B:PythonCourse.java2.4 工厂:CourseFactory.java2.5 测试:Test.java 3. 心得参考链接(无) 1. 简介 简单工厂模式(Simple Factory Patern):又称…

Java项目-基于springboot框架的网上书城系统项目实战(附源码+文档)

作者:计算机学长阿伟 开发技术:SpringBoot、SSM、Vue、MySQL、ElementUI等,“文末源码”。 开发运行环境 开发语言:Java数据库:MySQL技术:SpringBoot、Vue、Mybaits Plus、ELementUI工具:IDEA/…

【番外】软件设计师中级笔记关于数据库技术更新笔记问题

提问 由于软件设计师中级笔记中第九章数据库技术基础的笔记内容太多,我应该分几期发布呢?还是一期一次性发布完成。 如果分为一期发布,可能需要给我多一些时间,由于markdown格式有所差异,所以我需要部分进行修改与调…

策略路由---选路

目录 拓扑图 配置IP 配置静态路由 配置ospf nat 配置路由策略 流分类 流行为 流策略 应用接口 测试流量路径 拓扑图 配置IP [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip add 192.168.1.254 24 [R1-GigabitEthernet0/0/0]int g0/0/1 [R1-GigabitEthernet0/0/1]ip add…

Java项目-基于Springboot的车辆充电桩项目(源码+说明).zip

作者:计算机学长阿伟 开发技术:SpringBoot、SSM、Vue、MySQL、ElementUI等,“文末源码”。 开发运行环境 开发语言:Java数据库:MySQL技术:SpringBoot、Vue、Mybaits Plus、ELementUI工具:IDEA/…

Javascript算法——二分查找

1.数组 1.1二分查找 1.搜索索引 开闭matters!!![left,right]与[left,right) /*** param {number[]} nums* param {number} target* return {number}*/ var search function(nums, target) {let left0;let rightnums.length-1;//[left,rig…

⌈ 传知代码 ⌋ 无监督动画中关节动画的运动表示

💛前情提要💛 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间,对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

PP-ChatOCRv3—文档场景信息抽取v3产线使用教程

文档场景信息抽取v3产线使用教程 1. 文档场景信息抽取v3产线介绍 文档场景信息抽取v3(PP-ChatOCRv3)是飞桨特色的文档和图像智能分析解决方案,结合了 LLM 和 OCR 技术,一站式解决版面分析、生僻字、多页 pdf、表格、印章识别等常…

Spring连接数据库:Mybatis

MyBatis是一款优秀的框架 在数据库中创建表 1.创建项目mybatis 2.在proxml文件中导入必要配置并进行编译 <dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33<…

电能表预付费系统-标准传输规范(STS)(15)

6.3.7 CRC: CyclicRedundancyCheck (循环冗余校验) The CRC is a checksum field used to verify the integrity of the data transferred for all tokens, except for Class 0 with SubClass 4 to 7, which uses CRC_C (see 6.3.22). The checksum is derived using the foll…

网络基础知识:交换机关键知识解析

了解交换机的关键知识对网络工程师至关重要。 以下是交换机的基础知识解析&#xff0c;包括其基本概念、工作原理和关键技术点&#xff1a; 01-交换机的基本概念 交换机是一种网络设备&#xff0c;用于在局域网&#xff08;LAN&#xff09;中连接多个设备&#xff0c;如计算机…

如何恢复U盘里格式化数据?别慌,有带图详细步骤!

U盘&#xff0c;这个小巧的存储神器&#xff0c;我们几乎天天都在用。但有时候&#xff0c;一不小心手滑&#xff0c;U盘就被格式化了&#xff0c;里面的东西好像全没了&#xff0c;别急&#xff0c;其实数据恢复没那么难。这篇文章就来告诉你&#xff0c;怎么把格式化的U盘里的…

NVR录像机汇聚管理EasyNVR多品牌NVR管理工具/设备云台接入及控制详解

在当今快速发展的信息化时代&#xff0c;视频监控系统已成为企业管理和安全防范的重要工具。随着技术的不断进步&#xff0c;多品牌NVR&#xff08;网络视频录像机&#xff09;管理工具如海康NVR管理平台/工具EasyNVR多个NVR同时管理凭借其强大的兼容性和智能化管理功能&#x…

【LInux】Shell脚本编写基本语法

文章目录 一、前期准备1、查看本机bash2、编辑脚本 二、 判断结构1、if结构2、if/else结构3、if/elif/else结构4、case结构 三、循环结构1、for循环2、while循环3、until循环 四、谢谢观看&#xff01; 一、前期准备 1、查看本机bash which bash之后编写脚本时&#xff0c;第…

数据同步工具Sqoop原理及场景优化

目录 0 数据同步策略 1 数据同步工具 ​编辑 2 Sqoop同步数据原理分析 2.1 原理分析 2.2 Sqoop基本使用分析 3 切片逻辑 3.1 MR切片逻辑 3.2 Hive CombineInputformat切片逻辑 3.3 实验1:Map任务并行度分析1 3.4 实验2: Map任务并行度分析2 3.5 实验3:Map任务并行…

C++ 类的基础用法与详细说明:简单易懂的入门指南

什么是类&#xff1f; C类_百度百科 类是C中一种用于封装数据和功能的基本结构。你可以将类视为一种自定义的数据类型&#xff0c;它可以包含数据&#xff08;成员变量&#xff09;和操作这些数据的函数&#xff08;成员函数&#xff09;。 创建一个简单的类 让我们通过一个…

Java爬虫:获取商品评论数据的高效工具

在电子商务的激烈竞争中&#xff0c;商品评论作为消费者购买决策的重要参考&#xff0c;对于商家来说具有极高的价值。它不仅能够帮助商家了解消费者的需求和反馈&#xff0c;还能作为改进产品和服务的依据。Java爬虫技术&#xff0c;以其稳健性和高效性&#xff0c;成为了获取…

Vue2的依赖注入(跨级通信)基本使用

provide(提供) &#xff0c;inject(注入) 祖先级组件用provide传递数据,它的所有后代都可以通过inject取到数据 使用演示&#xff1a; //祖先组件 <template><div>父组件传的值&#xff1a;{{num}} </div> </template><script> //导入子组件 i…