JVM--自动内存管理--JAVA内存区域

1. 运行时数据区域
  1. 灰色的线程共享,白色的线程独享

  2. 白色的独享就是根据个体"同生共死"

  3. 程序计数器:

    1. 是唯一一个没有OOM(内存溢出)的地方

    2. 是线程独享的

    3. 作用:

      1. 是一块较小的内存空间,是当前线程所执行的字节吗的行号指示器

      2. 由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器

  4. java虚拟机栈:

    1. 线程私有的,生命周期和线程相同

    2. 作用

      1. 每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧 (Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

      2. 局部变量表:局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

      3. 这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。请读者注意,这里说的"大小"是指变量槽的数量,虚拟机真正使用多大的内存空间(譬如按照1个变量槽占用32个比特、64个比特,或者更多)来实现一个变量槽,这是完全由具体的虚拟机实现自行决定的事情。(如果变量槽是64比特,long和double可能会占用1个变量槽呢)

  5. 本地方法栈:

    1. 本地方法值:非java语言编写的程序

    2. 本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

  6. 堆:

    1. 是虚拟机所管理的内存中最大的一块

    2. 物理上可以不连续,逻辑上连续就可以

    3. Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java世界里"几乎"所有的对象实例都在这里分配内存。

    4. 从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。例如:10个人共同分配财产,谁使用谁来财务处拿,如果每次使用都来拿使用的钱,那么就会很慢,如果先给10个人 一人100,使用完来再分配100,那么会将速率提升

    5. 不过无论从什么角度,无论如何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存。

    6. cs+失败重试的(分配的同时用完,同时来找空间)

  7. 方法区:

    1. 与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
  8. 运行时常量池

    1. 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

    2. 既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError(内存不足)异常。

  9. 直接内存

    1. 并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。

    2. 例如I/O操作的内存,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。

    3. 本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制

2. HotSpot虚拟机对象探秘
  1. 对象的创建:

  2. 创建阶段将分配到的内存空间(但不包括对象头)都初始化为零值

  3. 从虚拟机的视角来看,一个新的对象已经产生了。但是从Java程序的视

    角看来,对象创建才刚刚开始------构造函数,即Class文件中的<init>()方法还没有执行,所有的字段都

    为默认的零值,对象需要的其他资源和状态信息也还没有按照预定的意图构造好。

  4. 对象的内存布局(对象的组成):

  5. 分为3部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

  6. 对象头信息

    1. 第一类:下边的表对应的是存储锁标志位

    2. 第二类:

      1. 对象头的另外一部分是类型指针,即对象指向它的类型元数据的指针,Java虚拟机通过这个指针来确定该对象是哪个类的实例。

      2. 并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身

      此外,如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是如果数组的长度是不确定的,将无法通过元数据中的信息推断出数组的大小。(所以由于对象头中有记录数组长度的数据所以查询长度时用的是.length而不是方法)

    3. 实例数据

      1. 是对象真正存储的有效信息,即我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。

      2. 这部分的存储顺序会受到虚拟机分配策略参数(-XX:FieldsAllocationStyle参数)和字段在Java源码中定义顺序的影响。

      3. HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、 oops(OrdinaryObject Pointers,OOPs)

    对齐填充:

    1. 这并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。

    2. HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍(任何对象的大小都必须是8字节的整数倍。),对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

  7. 对象的访问定位

    1. 定义:创建对象自然是为了后续使用该对象,我们的Java程序会通过栈上的reference数据来操作堆上的具体对象。

    2. 有两种主流的访问形式

      1. 使用句柄访问:Java堆中将可能会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息

      2. 直接指针访问:Java堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销

      3. 两种的优势:

        句柄和直接指针:使用句柄来访问的最大好处就是reference中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被修改。
        2.

        解释:例如在java栈的本地变量表中存有多个reference指向同一对象的,如果对象被移动(地址变化)只需要改变变句柄中的实例数据指针就可以,而reference指向的是对象实例数据的指针,所以不需要变化,而直接指针访问就需要改变每一个reference中的句柄地址

        同样由于直接指针节省了一次指针定位的时间开销,所以使用直接指针来访问最大的好处就是速度更快

相关推荐
程序媛小果4 分钟前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林9 分钟前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨23 分钟前
El表达式和JSTL
java·el
q5673152324 分钟前
在 Bash 中获取 Python 模块变量列
开发语言·python·bash
许野平1 小时前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
也无晴也无风雨1 小时前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar1 小时前
yelp数据集上识别潜在的热门商家
开发语言·python
duration~1 小时前
Maven随笔
java·maven
zmgst1 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java