一、JVM 内存分布 根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。 ? 1、虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建。 关于方法区内存溢出的问题会在下文中详细探讨。 我们现在通过动态生成类来模拟 “PermGen space”的内存溢出: ? ? 运行结果如下: ? 本例中使用的 JDK 版本是 1.7,指定的 PermGen 区的大小为 8M。 剩余空间容量的百分比,减少为分配空间所导致的垃圾收集 -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集 现在我们在 JDK 8下重新运行一下代码段 四、总结 通过上面分析,大家应该大致了解了 JVM 的内存划分,也清楚了 JDK 8 中永久代向元空间的转换。不过大家应该都有一个疑问,就是为什么要做这个转换?
Java8内存结构图 [75591d90-8204-4ac0-928c-7976bc19ee00.jpg] 虚拟机内存与本地内存的区别 Java虚拟机在执行时会把内存分成不同的区域,这些区域被称为虚拟机内存 ,是一张string table 静态变量 static修饰的静态变量,jdk8时从方法区迁移至堆中 线程分配缓冲区(Thread Local Allocation Buffer) 线程私有,但是不影响 方法区,Java8废弃,引入元空间! 方法区是所有线程共享内存,在java8以前是放在JVM内存中的,由永久代实现,受JVM内存大小参数限制,Java8移除了永久代和方法区,引入元空间,直接用物理内存实现。 类变量 类变量是用static修饰符修饰,定义在方法外的变量,随着java进程产生和销毁 在java8之前存放在方法区,在java8时存放在堆中 成员变量 成员变量是定义在类中,但是没有static修饰符修饰的变量
Java8相对之前的版本,JVM结构发生了较大的变化,取消了永久代,新增了元空间,同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。 下面,以Java8为例,对JVM结构做一番总结。 1 JVM结构详解 首先,看下最新的JVM结构图: ? 小结 经过上面的讲解,想必大家已经了解到JVM内存结构的基本情况。下面对照脑图,归纳总结一下,看你能说出来多少。 ? 2 Java8内存变化 2.1 永久代 首先,认识一个概念:永久代(Permanet Generation,也称PermGen)。 在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。 ?
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。 围城这个比喻过于形象!!! JVM内存区域 上经典图 【线程公有】:堆和方法区 【线程私有】:虚拟机栈,本地方法栈,程序计数器 堆 用于对象实例的内存分配,GC的主要区域。 方法区 存储已被虚拟机加载的类信息、常量、静态变量。 字面量可以理解为实际值,int a = 8中的8 和 String a = "hello"中的hello都是字面量 符号引用是一个字符串,对应唯一的类,方法,或者字段(相当于唯一ID)。 【符号引用转换为直接引用】 在编译时,比如People类的符号引用为com.simple.People,当类装载器装载People类后,符号引用被转为实际内存地址,也就是直接引用。
**主要结构:**堆内存、方法区、栈(说明:基于JDK1.7) 堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor 空间,默认情况下年轻代按照8:1:1的比例来分配; 方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆); 栈又分为java虚拟机栈和本地方法栈主要用于方法的执行 在虚拟机规范中对本地方法栈中方法使用的语言、使用方式 与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。 此内存区域的唯一目的就 是存放对象实例,几乎所有的对象实例都在这里分配内存。 七、直接内存 直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规 范中定义的内存区域。
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。 ? 这些区域中,一些是线程私有的,一些是线程共享的。 线程私有的:程序计数器、虚拟机栈、本地方法栈 线程共享的:堆、方法区、直接内存 1、程序计数器 一块较小的内存空间,用于标记当前线程所执行字节码的行号。 所有实例和数组都在这里分配内存,也是线程共享的内存区域。 -Xms 设置最小值;-Xmx 设置最大值。 堆内存分配会另写一篇文章介绍。 6、直接内存 直接内存并不是虚拟机内存的一部分,也不是 Java 虚拟机规范中定义的内存区域。 jdk1.4 中新加入的 NIO,引入了通道与缓冲区的 IO 方式,它可以调用 Native 方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。
JVM内存结构概览 先来看一张图: jvm主要分,堆、方法区、java栈、本地方法栈、程序计数器五个区域,其中方法区和堆区是线程共享的 堆区域 堆内存是JVM中最大的一块由新生代和老年代组成,而新生代内存又被分成三部分 TLAB仍然分配在堆上,结构比较简单,start、end就是起止地址,top表示已经分配到那里了,top与end相遇的时候,代表该缓存已经满了,JVM会试图再从Eden中分配一块 老年代 (old) 也就是说 eden 是 from 或 to 的8倍 监控和诊断堆内存的工具和方法 Jconsole 图形化分析 命令行工具: jstat、 jmap 等命令配合参数进行运行时查询 使用 Eclipse 永久代是早期hotspot JVM方法区的实现方式,存储Java的元数据、常量池等, JDK8之后就不存在永久代了 JDK8之后,原先永久代中类的元信息会被放入本地内存(元数据区,metaspace), 栈帧中存储: 1)局部变量表 存放了编译期就可知的:各种基本数据类型(8个基本数据类型)、对象引用(reference类型)、returnAddress类型(指向一条字节码指令地址)。
Java程序在运行时,需要在内存中的分配空间。为了提高运算效率,有对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式. 栈内存 用于存储局部变量,当数据使用完,所占空间会自动释放 堆(heap)内存 数组和对象,通过new建立的实例都存放在堆内存中。 每一个实体都有内存地址值 实体中的变量都有默认初始化值 实体不在被使用,会在不确定的时间内被垃圾回收器回收 方法区,本地方法区,寄存器
按照Java虚拟机规范的规定, JVM自动管理的内存将包括以下几个运行时的数据区域: ? 下面分别对几个数据区域进行说明: 1.程序计数器 程序计数器是JVM中一块较小的内存区域, 保存着当前线程执行的虚拟机字节码指令的内存地址. 所有虚拟机栈也是"线程私有"的内存区域. 这个栈中对应多个栈帧, 每调用一个方法就会往栈中创建并压入一个栈帧, 栈帧是用来存储方法数据和部分过程结果的数据结构, 每一个方法从调用到最终返回结果的过程, 就对应一个栈帧从入栈到出栈的过程. , 就会出现OOM 3.本地方法栈 本地方法栈和虚拟机栈的作用相似, 只是虚拟机栈是为Java方法服务的, 而本地方法栈是为Native方法服务的. 4.方法区 方法区是用来存储类结构信息(包括常量池、
对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像 C/C++程序开发程序员这样为每一个 new 操作去写对应的delete/free 操作,不容易出现内存泄漏和内存溢出问题。 进一步划分的目的是更好地回收内存,或者更快地分配内存。 ? Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入Java栈,每一个函数调用结束后,都会有一个栈帧被弹出。 直接内存 ---- 直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。 本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。
JVM的内存结构大概分为: ? 通过一张图来了解如何通过参数来控制各区域的内存大小 ? 控制参数 -Xms设置堆的最小空间大小。 -Xmx设置堆的最大空间大小。 然后,在Java8中,Hotspot取消了永久代。永久代真的成了永久的记忆。永久代的参数-XX:PermSize和-XX:MaxPermSize也随之失效。 4. 元空间(Metaspace) 对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?当然不是,方法区只是一个规范,只不过它的实现变了。 在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。 ? 直接内存的分配不受Java堆大小的限制,但是,既然是内存则肯定还是会受到本机总内存的限制。
说到java内存结构,不得不说下java内存模型,面试中可能经常会混淆,面试官问你的JAVA内存模型,可能人家想听到的是堆、栈、方法区等此类的关键词,只不过表述的有些问题,进而影响你的回答,所以在回答问题之前 ,一定先问清楚是内存结构和内存模型。 主要区别,java内存结构是对内存的具体划分,java内存模型是解决多线程下工作线程和主线程数据不一致问题而提出的抽象规则。 java内存结构简单划分如下图所示,以下结构是java8之前版本,在java8里元空间取代了之前版本的方法区 可以看到java内存主要划分线程共享(堆、方法区),线程私有(程序计数器、虚拟机栈、 对象作为这块内存的引用,避免在Java堆中和直接内存来回复制数据,实现零拷贝,显著提高性能
开局一张图 由于CPU频率太快了,为解决直接读取内存的数据上的延迟,在CPU和内存之间,存在3级缓存。 ? ? 缓存某个缓存行和主存数据的不一致,该缓存行需要在未来某个时间点回写主存,回写之后状态变为(exclusive 独享) E:(exclusive 独享)CPU缓存中的某个缓存行和主存数据一致,处于这个状态的可以被其他CPU读取内存时变成 总线锁 MESI协议之前,解决缓存一致性方案是总线锁机制,这种方案比较低效,锁期间,其他CPU无法访问内存。 CPU乱序 多核时代,处理器为提高运算速度,可能作出违背代码原有初衷的行为。 解决这种问题的方式就是内存屏障,简单点说是不同的处理器架构提供了不同指令集用来建立内存屏障,这样控制不可乱序。
JVM 内存结构Java 虚拟机的内存空间分为 5 个部分:程序计数器Java 虚拟机栈本地方法栈堆方法区图片JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代。 HotSpot 中,Eden 空间和另外两个 Survivor 空间缺省所占的比例是:8:1:1。 分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在 CPU 寄存器中。 图片直接内存(堆外内存) 直接内存是除 Java 虚拟机之外的内存,但也可能被 Java 使用。操作直接内存在 NIO 中引入了一种基于通道和缓冲的 IO 方式。 直接内存与堆内存比较直接内存申请空间耗费更高的性能直接内存读取 IO 的性能要优于普通的堆内存直接内存作用链: 本地 IO -> 直接内存 -> 本地 IO堆内存作用链:本地 IO -> 直接内存 ->
直接内存(堆外内存) 直接内存是除 Java 虚拟机之外的内存,但也可能被 Java 使用。 操作直接内存 在 NIO 中引入了一种基于通道和缓冲的 IO 方式。 直接内存的大小不受 Java 虚拟机控制,但既然是内存,当内存不足时就会抛出 OutOfMemoryError 异常。 直接内存与堆内存比较 直接内存申请空间耗费更高的性能 直接内存读取 IO 的性能要优于普通的堆内存。 直接内存作用链:本地 IO -> 直接内存 -> 本地 IO 堆内存作用链:本地 IO -> 直接内存 -> 非直接内存 -> 直接内存 -> 本地 IO 服务器管理员在配置虚拟机参数时,会根据实际内存设置 -Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存限制,从而导致动态扩展时出现OutOfMemoryError异常。
开局一张图 由于CPU频率太快了,为解决直接读取内存的数据上的延迟,在CPU和内存之间,存在3级缓存。 ? ? 缓存某个缓存行和主存数据的不一致,该缓存行需要在未来某个时间点回写主存,回写之后状态变为(exclusive 独享) E:(exclusive 独享)CPU缓存中的某个缓存行和主存数据一致,处于这个状态的可以被其他CPU读取内存时变成 总线锁 MESI协议之前,解决缓存一致性方案是总线锁机制,这种方案比较低效,锁期间,其他CPU无法访问内存。 CPU乱序 多核时代,处理器为提高运算速度,可能作出违背代码原有初衷的行为。 解决这种问题的方式就是内存屏障,简单点说是不同的处理器架构提供了不同指令集用来建立内存屏障,这样控制不可乱序。
Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。 ? 这些区域中,一些是线程私有的,一些是线程共享的。 线程私有的:程序计数器、虚拟机栈、本地方法栈 线程共享的:堆、方法区、直接内存 1、程序计数器 一块较小的内存空间,用于标记当前线程所执行字节码的行号。 所有实例和数组都在这里分配内存,也是线程共享的内存区域。 -Xms 设置最小值;-Xmx 设置最大值。 堆内存分配会另写一篇文章介绍。 6、直接内存 直接内存并不是虚拟机内存的一部分,也不是 Java 虚拟机规范中定义的内存区域。 jdk1.4 中新加入的 NIO,引入了通道与缓冲区的 IO 方式,它可以调用 Native 方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。
1 JDK1.7结构 1.1 JVM内存图 程序计数器: 线程私有的(每个线程都有一个自己的程序计数器), 是一个指针. 代码运行, 执行命令. 方法区: 线程共享的(所有的线程共享一份), .class的信息, 类的信息, 方法的定义, 常量池, 静态变量等. 1.2 JDK1.7 堆内存结构 Young 年轻区(代) Young区被划分为三部分 Virtual区: 最大内存和初始内存的差值,就是Virtual区。 这里提到的GC咱们后续继续介绍~ 2. JDK1.8 结构 2.1 JVM运行时内存图 JDK1.8和JDK1.7的jvm内存最大的区别是, 在1.8中方法区是由元空间(元数据区)来实现的,常量池移到堆中. 1.8不存在方法区,将方法区的实现给去掉了 元空间: 存储.class 信息, 类的信息,方法的定义,静态变量等.而常量池放到堆里存储 2.2 JDK1.8堆内存结构 由上图可以看出,jdk1.8的内存模型是由2部分组成, 年轻代+年老代.
然而,当我们深入研究结构体时,会发现一个有趣且重要的现象:结构体的内存对齐。内存对齐直接影响到程序的性能和内存使用效率。今天,我们就通过一个简单的程序来深入探讨结构体的内存对齐。 int main() { printf("Size of struct stu: %lu\n", sizeof(struct stu)); return 0; } A. 6 B. 8 内存对齐是指将数据的起始地址放在某个特定的地址边界上,例如,4字节对齐、8字节对齐等。对齐的方式取决于编译器的默认设置和目标硬件平台。 VS中,存在一个默认对齐数,大小为8字节。一个成员的对齐数为它本身占据的字节大小与默认对齐数的较小值。 GCC编译器本身没有对齐数,可以通过#pragma pack指令来设置对齐方式。 现代计算机的内存系统通常以块为单位进行访问,每个块的大小通常是2、4、8字节等。如果数据的地址与块的边界对齐,那么内存系统可以更高效地访问数据。
00491889 &(dsptr->ratio) = 0049188A Offset of signature = 0 Offset of version = 3 Offset of width = 8