前言

Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: 程序计数器 ;Java虚拟机栈 ;本地方法栈 ;堆 ;方法区。Java虚拟机中的内存分配图 :

各个区域的特性总结如下表:

一、程序计数器(PC Register)

什么是程序计数器?

程序计数器是一块较小的内存空间,可以把它看作当前线程正在执行的字节码的行号指示器。也就是说,程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址。 注:但是,如果当前线程正在执行的是一个本地方法,那么此时程序计数器为空。 

程序计数器的作用

1)、字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
2)、在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

程序计数器的特点

1)、是一块较小的存储空间
2)、线程私有。每条线程都有一个程序计数器。
3)、是唯一一个不会出现OutOfMemoryError的内存区域。
4)、生命周期随着线程的创建而创建,随着线程的结束而死亡。

二、Java虚拟机栈(JVM Stack)

什么是Java虚拟机栈?

Java虚拟机栈是描述Java方法运行过程的内存模型。Java虚拟机栈会为每一个即将运行的Java方法创建一块叫做“栈帧”的区域,这块区域用于存储该方法在运行过程中所需要的一些信息,这些信息包括:局部变量表(存放基本数据类型变量、引用类型的变量、returnAddress类型的变量。)、操作数栈、动态链接、方法出口信息等。当一个方法即将被运行时,Java虚拟机栈首先会在Java虚拟机栈中为该方法创建一块“栈帧”,栈帧中包含局部变量表、操作数栈、动态链接、方法出口信息等。当方法在运行过程中需要创建局部变量时,就将局部变量的值存入栈帧的局部变量表中。 当这个方法执行完毕后,这个方法所对应的栈帧将会出栈,并释放内存空间。

局部变量表: 存放了编译器可知的各种基本数据类型、对象引用(引用指针,并非对象本身),其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间。

注意:人们常说,Java的内存空间分为“栈”和“堆”,栈中存放局部变量,堆中存放对象。 这句话不完全正确!这里的“堆”可以这么理解,但这里的“栈”只代表了Java虚拟机栈中的局部变量表部分。真正的Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。

Java虚拟机栈的特点

1)、局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建。而且,局部变量表的大小在编译时期就确定下来了,在创建的时候只需分配事先规定好的大小即可。此外,在方法运行的过程中局部变量表的大小是不会发生改变的。
2)、Java虚拟机栈会出现两种异常:StackOverFlowError和OutOfMemoryError。 a) StackOverFlowError: 若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。 b) OutOfMemoryError: 若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
3)、Java虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
注意:StackOverFlowError和OutOfMemoryError的异同? StackOverFlowError表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间可能还有很多。而OutOfMemoryError是指当线程申请栈时发现栈已经满了,而且内存也全都用光了。

三、本地方法栈(Native Stack)

什么是本地方法栈?

本地方法栈和Java虚拟机栈实现的功能类似,只不过本地方法区是本地方法运行的内存模型。本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间。也会抛出StackOverFlowError和OutOfMemoryError异常。

四、堆(Heap)

什么是堆?

堆是用来存放对象的内存空间。几乎所有的对象都存储在堆中。堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组(但不是所有的对象实例都在堆中)。其大小通过-Xms(最小值)和-Xmx(最大值)参数设置(最大最小值都要小于1G),前者为启动时申请的最小内存,默认为操作系统物理内存的1/64,后者为JVM可申请的最大内存,默认为物理内存的1/4,默认当空余堆内存小于40%时,JVM会增大堆内存到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列;当空余堆内存大于70%时,JVM会减小堆内存的大小到-Xms指定的大小,可通过XX:MaxHeapFreeRation=来指定这个比列,当然为了避免在运行时频繁调整Heap的大小,通常-Xms与-Xmx的值设成一样。堆内存 = 新生代+老生代+持久代。在我们垃圾回收的时候,我们往往将堆内存分成新生代和老生代(大小比例1:2),新生代中由Eden和Survivor0,Survivor1组成,三者的比例是8:1:1,新生代的回收机制采用复制算法,在Minor GC的时候,我们都留一个存活区用来存放存活的对象,真正进行的区域是Eden+其中一个存活区,当我们的对象时长超过一定年龄时(默认15,可以通过参数设置),将会把对象放入老生代,当然大的对象会直接进入老生代。老生代采用的回收算法是标记整理算法。

堆的特点

1)、线程共享:整个Java虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。
2)、在虚拟机启动时创建
3)、垃圾回收的主要场所。
4)、可以进一步细分为:新生代、老年代。新生代又可被分为:Eden、From Survior、To Survior。不同的区域存放具有不同生命周期的对象。这样可以根据不同的区域使用不同的垃圾回收算法,从而更具有针对性,从而更高效。
5)、堆的大小既可以固定也可以扩展,但主流的虚拟机堆的大小是可扩展的,因此当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError。

五、方法区(Method Area)

什么是方法区?

Java虚拟机规范中定义方法区是堆的一个逻辑部分。 方法区中存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。方法区也称"永久代",它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。默认最小值为16MB,最大值为64MB(64位JVM由于指针膨胀,默认是85M),可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。它是一片连续的堆空间,永久代的垃圾收集是和老年代(old generation)捆绑在一起的,因此无论谁满了,都会触发永久代和老年代的垃圾收集。不过,一个明显的问题是,当JVM加载的类信息容量超过了参数-XX:MaxPermSize设定的值时,应用将会报OOM的错误。参数是通过-XX:PermSize和-XX:MaxPermSize来设定的运行时常量池(Runtime Constant Pool):是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

从JDK7开始移除永久代(但并没有移除,还是存在),贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。从JDK8开始使用元空间(Metaspace),元空间的大小受本地内存限制,新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。具体的可以查看这篇文章:https://blog.csdn.net/zhushuai1221/article/details/52122880 (Java 8: 从永久代(PermGen)到元空间(Metaspace))

方法区的特点

1)、线程共享:方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的。整个虚拟机中只有一个方法区。
2)、永久代:方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,我们把方法区称为老年代。
3)、内存回收效率低:方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效。对方法区的内存回收的主要目标是:对常量池的回收和对类型的卸载。
4)、Java虚拟机规范对方法区的要求比较宽松。和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。

什么是运行时常量池?

方法区中存放三种数据:类信息、常量、静态变量、即时编译器编译后的代码。其中常量存储在运行时常量池中。我们一般在一个类中通过public static final来声明一个常量。这个类被编译后便生成Class文件,这个类的所有信息都存储在这个class文件中。当这个类被Java虚拟机加载后,class文件中的常量就存放在方法区的运行时常量池中。而且在运行期间,可以向常量池中添加新的常量。如:String类的intern()方法就能在运行期间向常量池中添加字符串常量。当运行时常量池中的某些常量没有被对象引用,同时也没有被变量引用,那么就需要垃圾收集器回收。

六、直接内存(Direct Memory)

直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小。直接内存是除Java虚拟机之外的内存,但也有可能被Java使用。在NIO中引入了一种基于通道和缓冲的IO方式。它可以通过调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象直接操作该内存,而无需先将外面内存中的数据复制到堆中再操作,从而提升了数据操作的效率。直接内存的大小不受Java虚拟机控制,但既然是内存,当内存不足时就会抛出OOM异常。

七、总结

1)、Java虚拟机的内存模型中一共有两个“栈”,分别是:Java虚拟机栈和本地方法栈。 两个“栈”的功能类似,都是方法运行过程的内存模型。并且两个“栈”内部构造相同,都是线程私有。只不过Java虚拟机栈描述的是Java方法运行过程的内存模型,而本地方法栈是描述Java本地方法运行过程的内存模型。
2)、Java虚拟机的内存模型中一共有两个“堆”,一个是原本的堆,一个是方法区。方法区本质上是属于堆的一个逻辑部分。堆中存放对象,方法区中存放类信息、常量、静态变量、即时编译器编译的代码。
3)、堆是Java虚拟机中最大的一块内存区域,也是垃圾收集器主要的工作区域。
4)、程序计数器、Java虚拟机栈、本地方法栈是线程私有的,即每个线程都拥有各自的程序计数器、Java虚拟机栈、本地方法区。并且他们的生命周期和所属的线程一样。而堆、方法区是线程共享的,在Java虚拟机中只有一个堆、一个方法栈。并在JVM启动的时候就创建,JVM停止才销毁。

Java虚拟机学习总结(1)——JVM内存模型相关推荐

  1. 深入理解Java虚拟机学习笔记-1.JVM内存模型

    JVM内存模型 1.内存模型结构图 名称 特征 作用 配置参数 异常 程序计数器 占用内存小,线程私有, 生命周期与线程相同 大致为字节码行号指示器 无 无 虚拟机栈 线程私有,生命周期与线程相同,使 ...

  2. java 内存溢出的分类_【深入理解Java虚拟机】读后感:JVM内存划分与内存溢出小结...

    扫码关注公众号:Java 技术驿站 发送:vip 将链接复制到本浏览器,永久解锁本站全部文章 [公众号:Java 技术驿站] [加作者微信交流技术,拉技术群] # JVM内存划分与内存溢出小结 # 1 ...

  3. java虚拟机学习-JVM调优总结-新一代的垃圾回收算法(11)

    java虚拟机学习-深入理解JVM(1) java虚拟机学习-慢慢琢磨JVM(2) java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制 java虚拟机学习-JVM内存管理:深 ...

  4. JVM内存模型与垃圾回收GC

    Java开发有个很基础的问题,虽然我们平时接触的不多,但是了解它却成为Java开发的必备基础--这就是JVM.在C++中我们需要手动申请内存然后释放内存,否则就会出现对象已经不再使用内存却仍被占用的情 ...

  5. 【深入浅出指南:JVM知多少】一、JVM内存模型

    我是飞乐,一个在互联网苟且的男人,微信关注搜索[nlefer],跟飞乐一起学习更多内容 JVM内存模型 前言 一.概述 二.内存模型 1.程序计数器 2.Java虚拟机栈 2.1.栈帧过多测试 3.本 ...

  6. 深入理解JAVA虚拟机学习笔记(一)JVM内存模型

    摘要:   上周末搬家后,家里的宽带一直没弄好,跟电信客服反映了N遍了终于约了个师傅明天早上来迁移宽带,可以结束一个多星期没网的痛苦日子了.这段时间也是各种忙,都一个星期没更新博客了,再不写之前那种状 ...

  7. Java虚拟机学习(1):体系结构 内存模型

    一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内 ...

  8. 分页池内存持续增长_Java技术学习之对虚拟机(JVM)内存模型的分析

    JVM会将Java进程所管理的内存划分为若干不同的数据区域,这些区域有各自的用途.创建/销毁时间. JVM内存数据:栈管运行,堆管存储 第一章 线程私有区域 线程私有数据区域生命周期与线程相同, 依赖 ...

  9. JVM内存模型深度讲解(Java学习笔记)

    JDK,JRE,JVM之间的关系 JDK(Java Development Kit)就是java 开发工具,从图中可以看到,JDK包含JRE,Java基础类库和Java工具,这是给开发者使用的产品. ...

最新文章

  1. [bzoj] 1176 Mokia || CDQ分治
  2. SpringCloud 2020版本教程2:使用spring cloud gateway作为服务网关
  3. AI:神经网络调参(数据、层数、batch大小,学习率+激活函数+正则化+分类/回归)并进行结果可视化
  4. USACO 1.2-Transformations
  5. Autofac在.NET Core 中的使用
  6. java 读取csv_Java读取CSV的常用方法 | 学步园
  7. 不实例化图片,获取图片宽高的方法(vb.net)
  8. SDUT_经典线型DP
  9. ::在c++中什么意思
  10. 百度文库复制文字代码
  11. windows server 2008 R2 SP1多国语言包官方下载
  12. 尼枚罗指数matlab,洛伦兹系统李雅普诺夫指数的MATLAB源代码
  13. 使用python 的turtle 画太阳花
  14. 在线python编程网页-手把手教你用Python写一个在线网站,这真的很装逼!
  15. 半入耳式蓝牙耳机哪款音质好?音质最好的半入耳蓝牙耳机推荐
  16. 环境数据采集系统——中期总结
  17. win7截屏快捷键未在计算机上运行,修复win7“截图工具当前未在计算机上运行”的方法...
  18. MySQL 员工信息查询
  19. 疫情放开后,如何保护居家员工的办公安全?
  20. spring-aop切面

热门文章

  1. 矩阵分析 第三章 内积空间 正规矩阵 Hermite矩阵
  2. 【数据结构----笔记4】插入排序算法之【折半插入排序算法】
  3. 电力设计手册_电气设计负荷计算方法
  4. python画猫和老鼠_观察者模式(猫与老鼠的故事!)
  5. 哪三级分类java_技术汇总:第五章:使用angularjs做首页三级分类
  6. 计算机硬盘修复教程,DiskGenius数据恢复教程 教你电脑硬盘数据如何恢复
  7. 计算机专业简历自我评价,计算机专业毕业生简历自我评价
  8. android linux 优化,【「Android」UE手游研发中,如何做好Android内存优化?】|Linux|DEX|腾讯游戏|_傻大方...
  9. java分布式dubbo_Dubbo剖析-搭建一个简单的分布式系统(1)
  10. maven java 结构_Maven标准目录结构介绍