Jvm学习笔记(一)内存模型

这篇具有很好参考价值的文章主要介绍了Jvm学习笔记(一)内存模型。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Jvm学习笔记(一)内存模型

JVM内存模型

java不需要用户手动去管理内存的释放,这大大解放了程序员的心智负担,jvm运行的核心绕不开他的内存模型,本章着重于jvm的内存模型分析。

程序计数器

程序计数器是一块较小的内存区域,主要作用是确定下一条需要执行的字节指令(java执行的是字节指令),它是程序控制流的指示器。

java的线程是基于操作系统的时分多路,所以对于一个处理器,在确定的时刻,只有一个线程处于执行状态,为了切换线程以后能够继续执行之前的字节指令,jvm为每一个线程分配了一块程序计数器的内存,即线程隔离。

程序计数器是唯一一个不会出现oom的区域。

Java虚拟机栈

Java虚拟机栈也是线程私有的,它描述的是Java方法执行的线程内存模型:每个方法被执行的时候,都会同步创建一个栈帧(Stack Frame),每个栈帧中存放:

  • 局部变量表

  • 操作数栈

  • 动态连接

  • 方法出口信息

    每一个方法被调用到执行完成,对应着一个栈帧在虚拟机中入栈到出栈的过程。每个方法所用到的变量都存放在局部变量表中,由于栈的空间是连续的,所以在编译期间必须知道栈空间分配的大小,所以,在局部变量表中存放的变量,必须在编译期间就知道空间的占用,故在局部变量表中,存放的是Java虚拟机的基础数据类型、对象的引用(指针)、returnAddress类型(函数的返回其实就是把返回结果的地址传递给外部)。

    变量在局部变量表中的内存占用是用Slot表示,double和long是64位,其余32位。

    如多线程请求的栈的深度超过jvm允许的最大深度,就会出现StackOverFlow异常(HotSpot无法动态扩容,所以只会出现这个异常)

Native本地方法栈

本地方法栈的作用和虚拟机栈的作用非常相似,只不过虚拟机栈执行的是java方法,本地方法栈执行的是原生方法。

Hot Spot直接把本地方法栈和虚拟机栈合二为一了。本地方法栈一样会出现StackOverFlow异常。

Java堆

在栈中的内存必须在编译期间就可以确定,那么那些在运行时才能确定内存空间大小、那些在栈帧回收以后还能继续存在的数据应该存放在哪呢?答案就是堆区。类似CPP的堆,java的堆事jvm中由jvm控制的最大的一块内存,这块内存的唯一作用就是用来存放对象或数组。(在cpp中,堆中的内存由用户手动申请并得到指针,同时由用户在合适的时机手动释放这部分内存,在java中,万物皆对象,对象的创建由jvm分配内存和传递指针,对象的回收由GC完成)。

Java堆是GC主要工作堆区域,gc将堆区内存分为了老年代、青年代等,这部分在后面GC章会具体讲到。

java堆是线程共享的,线程之间通过共享堆内存,来实现线程间通讯,堆区也是发生内存泄漏的内存区。当内存不足时,会出现oom异常。

方法区

和堆区一样,方法区也是线程共享的,主要用来存放被虚拟机加载的类中的信息,比如静态变量、常量等类信息。在jdk7以后,HorSpot就把方法区中的数据移动到了本地内存中一块叫元数据的内存区域。

当方法区内存不足时,会出现oom异常。

运行时常量

运行时常量时方法区中的一块内存,用来存放那些在运行时生成的常量,既非 class文件中常量池中所定义的常量,比如Stirng.intern()方法产生的常量。

直接内存

直接内存不是虚拟机运行时数据区的一部分,而是一块原生内存区域,jvm通过DirectByteBuffer对象作为这块内存的引用进行操作,避免了java和native之间频繁的复制数据导致的开销增大(也就是说jni中把数据存在在这块区域,在java中可以直接操作而无需使用clone的方式)。

HotSpot对象的创建流程和内存模型

创建流程

在java中,万物皆对象,接下来就分析一下HotSpot虚拟机中对象新建时的创建流程和存储的内存结构。

第一步:根据new的对象加载对应的class(假设目前还未加载),具体加载细节在后面章节分析。这里主要分析的是对象的内存模型。

第二步:为对象分配内存,在class加载完成以后,一个对象所需的内存其实已经确定下来了,只需要把一块空闲的内存从Java堆中划分出来即可。内存分配的方式有两种:在内存规整的时候(既可用内存和不可用内存被完全区分开时),可以采用移动指针的方式来快速划分出一块可用内存,这种方式被称为指针碰撞,在内存不规整的时候,虚拟机就必须维护一个列表,记录着可用的空间,当对象需要空间时候,在列表中找到对应的空闲区域划分出去并更新列表,这种方法被称为空闲列表。采用哪一方法和虚拟机的垃圾回收器是否带有空间整理功能有关。Serial、ParNew采用指针碰撞,CMS采用空闲列表。

在分配内存时,还有一个线程同步的问题,当一个线程在分配内存未完成时,新的线程请求分配内存,就会导致线程冲突,虚拟机采用cas来保证更新操作的一致性,此外,虚拟机也会为各个线程分配一块砖们的缓存区域,称为本地线程分配缓冲,每个线程优先在该区域更新,本地缓冲用完以后才会去公共区域申请内存,是否使用TLAB可以通过 -XX:+/-UseTLAB的参数设定。

第三步:内存区域分配完以后,会将内存区域内数据设置为零值,开启TLAB以后会在初始化TLAB的是就将内存区域初始化。

第四步:虚拟机对对象的进行必要的设置,比如这个对象属于哪个类,对象的哈希值,对象GC的分代年龄信息,这些信息会被存储在对象头中。

第五步:内存区域已经分配完成,接下来执行class文件中定义的<init>()方法,该方法会初始化对象默认值并执行构造方法,到这一步一个对象被真正的创造出来了。

内存模型

对象的内存结构分为对象头、实例数据、对齐填充。

对象头内存存储在对象自身的运行时数据,如哈希码、gc分代年龄,锁状态等,这部分数据长度根据虚拟机不同分为32或者64位,并且数据会复用空间。此外,对象头中还存放着类型指针,指向这个对象所对应的数据类型的元数据,如果是数组对象,还会存放这个数组的长度。

实例数据就是对象中存放自定义的数据的区域。

对齐填充:HotSpot虚拟机的内存管理要求地址必须是8的倍数,所以这部分区域用来填充不足8倍数的空间,仅仅起到占位的作用。

对象的访问定位

在栈上,对象是一个指向对象的引用而不是真实数据,这个引用具体怎么指针真实数据,有两种方法。

1、使用句柄访问,引用指向堆区的一个句柄,由句柄指向真实的内存区域。优点是内存真实地址移动或者变化时只需要改变句柄的数据而不用变化引用本身。

2、直接引用,直接指向堆中真实的内存区域。好处就是减少一次内存定位。

HotSpot主要采用第二种方案。

内存溢出

根据上面的内存模型可知,除了程序计数器之外,都会发生OOM(内存溢出)。下来分析各个区域的内存溢出问题。

JAVA堆

java堆内存溢出是最常见的内存溢出情况,出现堆溢出时,会报OutOfMemory错误,会进一步提示heap space。

堆中出现内存溢出的原因是对象不停的创建的同时,始终无法回收垃圾,也就是某个对象本应被回收却还是被认定为使用中,这种现象称为内存泄漏

根据GC Roots和可达性,内存泄漏时因为有gc root节点引用了一个本该被回收的内存,可以使用堆转储工具,堆Dump出来的快照进行分析,看具体哪一个root节点仍然引用了堆中的对象。以下是一些本人建议排查的重点(我是写android的,主要以android的层面):

1、对象间生命周期的差异,从更高的抽象层角度看,内存泄漏其实就是长生命周期的对象引用了短生命周期对象,而使得本应被回收的短生命周期对象迟迟不能被回收。一般来说这些引用都会藏的很深,不会让你直接发觉,这需要对各个组件之间生命周期有一定了解,拿Android举例,Activity属于频繁新建和回收的短生命周期对象,如果采用MVVM或者MVP的价格,P和VM是普通对象,普通对象的生命周期远远长于Activity(至少保持到GC),如果直接持有Activity而不做Detach或者weakref处理,就会出现页面回收以后普通对象始终持有activity,出现内存泄露。

2、匿名内部类/非静态内部类会隐式持有外部类的this引用,如果内部类中存在耗时操作便会出现内存泄漏。解决办法的改为静态内部类,如需使用外部引用,采用弱引用的方式传入。

3、各种组件的regist和unRegist。改进方式是在组件的destory时机进行反注册。

4、单例,单例的生命周期是整个应用,如果持有了某些引用,会导致这些引用始终无法释放。

虚拟机栈和本地方法栈

虚拟机对栈的创建本身是有数量限制的,一旦超过限制,就会出现StackOverflow的异常,(本身虚拟机规范允许虚拟机动态扩容,但是HotSpot并没有扩容功能)栈容量由 -Xoss参数设置。

我觉得这个问题比较容易出现在递归的问题上面,开发中尽量少用递归,要用也是尾递归,尽量用迭代代替递归。

方法区和运行时常量

运行时常量区会在运行过程中产生新的常量,如果空间用完,也会出现oom的问题,会在问题后面出现PermGen space的提示。

直接内存

DirectByteBuffer类通过反射获取unSafe实例进行内存分配。在调用UnSafe::allocateMemory()会出现手动抛出异常的问题。

排查这个问题,当dump的快照文件很小,程序中又直接或间接使用了DirectByteBuffer,就可以考虑是这个问题导致。

在Android中,图片的加载的缓存往往会在Ntaive层复制一份,我曾经就遇到过Flutter下图片在native缓存过多导致程序奔溃并且没有任何相关提示(整个android系统崩溃重启了)。

结语

本章主要是概括了一下java中内存相关的问题,主要参考了书本,也有一些是自己在开发中遇到的问题和感悟,谢谢观看,给个点赞

 文章来源地址https://www.toymoban.com/news/detail-412732.html

到了这里,关于Jvm学习笔记(一)内存模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【JVM】JVM内存模型(详细)

    JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。 Java中的所有类,必须被装载到JVM中才能运行,这个装载工作是由jvm中的类装载器完成的,.class这个类型可以

    2023年04月08日
    浏览(34)
  • JVM前世今生之JVM内存模型

    JVM内存模型所指的是JVM运行时区域,该区域分为两大块 线程共享区域 堆内存、方法区,即所有线程都能访问该区域,随着虚拟机和GC创建和销毁 线程独占区域 虚拟机栈、本地方法栈、程序计数器,即每个线程都有自己独立的区域,该区域随着线程的生命周期创建和销毁  

    2024年02月12日
    浏览(35)
  • JVM原理:JVM运行时内存模型(通俗易懂)

    做了几年开发,平时除了写代码造BUG和修复BUG之外,偶尔也会遇到反馈说程序较慢问题,要对程序性能排查与优化就得更深入学习,学习JVM可以帮助我们加深对JAVA的理解,让我们具备一定的性能排查与调优的能力,无非就是让程序别太卡或者别挂了,那挂了目前我遇到的主要

    2024年02月08日
    浏览(55)
  • JVM内存模型深度解读

            JVM(Java Virtual Machine,Java虚拟机)对于Java开发者和运行 Java 应用程序而言至关重要。其重要性主要体现在跨平台性、内存管理和垃圾回收、性能优化、安全性和稳定性、故障排查与性能调优等方面。今天就下学习一下 JVM 的内存模型。         JVM 内存模型(

    2024年03月19日
    浏览(47)
  • JVM五大内存模型

    JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一个虚构出来的计算机,有着自己完善的硬件架构,如处理器、堆栈等。 Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码( 字节码 ),就可以在多种平台上不加

    2024年02月15日
    浏览(42)
  • JVM之内存模型

    首先说明下 JVM内存模型 和 Java内存模型 这是两个不同的概念,不要搞混淆了。 JVM内存模型定义了Java程序在运行时如何分配、使用和释放内存,跟存储和执行相关,也就是常说的运行时数据区域。 Java内存模型(Java Memory Model,简称 JMM)是一种规范,定义了线程和主内存之间

    2024年02月08日
    浏览(31)
  • 四、JVM-对象内存模型

    一个Java对象在内存中包括3个部分:对象头、实例数据和对齐填充 数据 内存 – CPU 寄存器 -127 补码 10000001 - 11111111 32位的处理器 一次能够去处理32个二进制位 4字节的数据 64位操作系统 8字节 2的64次方的寻址空间 指针压缩技术 JDK1.6出现的 开启了指针压缩 什么时候指针压缩会

    2024年02月14日
    浏览(34)
  • JVM oop内存模型

    1、非数组对象 InstaceOopDesc 2、数组对象  arrayOopDesc         2.1 基本数据类型数组 typeArrayOopDesc         2.2 引用类型数组 objArrayOopDesc  3、MarkOopDesc         存放锁信息、分代年龄等 1、InstanceKlass是JVM中表示类的对象的数据结构。JVM在加载class时,会创建 instanceKlass ,表示其元

    2024年02月12日
    浏览(46)
  • 认识JVM的内存模型

    从上一节了解到整个JVM大的内存区域,分为线程共享的heap(堆),MethodArea(方法区),和线程独享的 The pc Register(程序计数器)、Java Virtual Machine Stacks(Java虚拟机栈)、Native Method Stacks(本地方法栈),如下图 同时也大概了解到各个内存区域主要存储的数据类型,根据之前

    2024年02月10日
    浏览(36)
  • JVM——内存模型

      这里与局部变量自增不同,局部变量调用iinc是在局部变量表槽位上进行自增。  静态变量是在操作数栈自增。 这里的主内存和工作内存时再JMM里的说法。  因为操作系统是时间片切换的多个线程轮流使用CPU. JMM中通过synchronized(同步)保证原子性。 使用synchronized减i+

    2024年02月11日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包