JVM的内存区域

  JVM的内存区域分为线程私有区域(程序计数器、虚拟机栈、本地方法区)、线程共享区域(堆、方法区)和直接内存,如图所示

  线程私有区域的生命周期与线程相同,随线程启动而创建,随线程结束而销毁。在JVM内部,每个线程都与操作系统的本地线程直接映射,因此线程私有内存区域的存在与否,和本地线程的启动和销毁对应。

  线程共享区域随虚拟机启动而创建,随虚拟机的关闭而销毁。

  直接内存也叫堆外内存,它并不是JVM运行时数据区的一部分,但在并发编程中被频繁使用。JDK的NIO模块提供的基于Channel与Buffer的I/O操作方式就是基于堆外内存实现的,NIO模块通过调用Native函数库直接在操作系统上分配堆外内存,然后使用DirectByteBuffer对象作为这块内存的引用对内存进行操作,Java进程可以通过堆外内存技术避免Java堆和Native堆中来来回复制数据带来的资源浪费和性能消耗,因此堆外内存在高并发应用场景下被广泛使用(Netty、Flink、HBase、Hadoop 都有用到堆外内存)。

程序计数器:线程私有,无内存溢出

  程序计数器是一块很小的内存空间,用于存储当前运行的线程所执行的字节码的行号指示器。每个运行中的线程都有一个独立的程序计数器,在方法正在执行时,该方法的程序计数器记录的是实时虚拟机字节码指令的地址;如果该方法执行的是Native方法,则程序计数器的值为空(Undefined)。

  程序计数器属于“线程私有”的内存区域,他是唯一没有内存溢出(Out Of Memory)的区域。

  在线程切换过程中,程序计数器记录当前线程执行的字节码指令行号,再切换回该线程时,能保证正确运行。所以程序计数器是线程私有的。

虚拟机栈:线程私有,描述Java方法的执行过程

  虚拟机栈是描述Java方法的执行过程的内存模型,它在当前栈帧(Strack Frame)中存储了局部变量表、操作数栈、动态链接、方法出口等信息。同时,栈帧用来存储部分运行时数据及其数据结构,处理动态链接(Dynamic Linking)方法的返回值和异常分派(Dispatch Exception)。

  栈帧用来记录方法的执行过程,在方法被执行时虚拟机会为其创建一个与之对应的栈帧,方法的执行和返回对应栈帧在虚拟机中的入栈和出栈。无论方法是正常运行完成还是异常完成(抛出了在方法内未被捕获的异常),都视为方法运行结束。下图展示了线程运行及栈帧变化的过程。线程1在CPU1上运行,线程2在CPU2上运行,在CPU资源不足时,其他线程将处于等待状态(如图中的等待线程N),等待获取CPU时间片。在线程内部,每个方法的执行和返回都对应一个栈帧的入栈和出栈,每个运行中的线程当前只有一个栈帧处于活动状态。

栈中可能出现的问题

Java虚拟机规范允许Java栈的大小是动态的或者是固定不变的。

  • 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError异常

  • 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个OutOfMemoryError异常。
    设置栈的大小.

  我们可以使用参数-Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。

本地方法区:线程私有

● Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
   本地方法区和虚拟机栈的作用类似,区别是虚拟机栈为执行Java方法服务,本地方法栈为Native方法服务。
● 本地方法栈,也是线程私有的。
● 允许被实现成固定或者是可动态扩展的内存大小。(在内存溢出方面是相同的)
 ➢如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError 异常。
 ➢如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个OutOfMemoryError 异常。
● 本地方法是使用C语言实现的。
● 它的具体做法是Native Method Stack中 登记native方法,在Execution Engine 执行时加载本地方法库。
● 当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虛拟机限制的世界。它和虛拟机拥有同样的权限。
 ➢本地方法可以通过本地方法接口来访问虛拟机内部的运行时数据区。
 ➢它甚至可以直接使用本地处理器中的寄存器
 ➢直接从本地内存的堆中分配任意数量的内存。
● 并不是所有的JVM都支持本地方法。因为Java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。如果JVM产品不打算支持native方法,也可以无需实现本地方法栈。
● 在Hotspot JVM中,直接将本地方法栈和虚拟机栈合二为一 。

堆:也叫做运行时数据区,线程共享

​  在JVM运行过程中创建的对象和产生的数据都被存储在堆中,堆是线程共享的内存区域,也是垃圾回收器进行垃圾回收的主要内存区域。由于现代JVM采用分代收集算法,因此Java堆从GC(Garbage Collection,垃圾回收)的角度还可以分为:新生代、老年代和永久代。

方法区:线程共享

  方法区也被称为永久代,用于存储常量、静态变量、类信息、即时编译器编译后的机器码、运行常量池等数据,如下图所示:

  JVM把GC分代收集扩展至方法区,即使用Java堆的永久代来实现方法区,这样JVM的垃圾回收器就可以像管理Java堆一样管理这部分内存。永久代的内存回收主要针对常量池的回收和类的卸载,因此可回收的对象很少。

  常量被存储在运行时常量池(Runtime Constant Pool)中,是方法区的一部分。静态变量也属于方法去的一部分。在类信息(Class文件)中不但保存了类的版本、字段、方法接口等描述信息,还保存了常量信息。

  在即时编译后,代码的内容将在执行阶段(类加载完成后)被保存在方法区的运行常量池中。Java虚拟机堆Class文件的每一部分的格式都有明确的规定,只有符合JVM规范的Class文件才能通过虚拟机的检查,然后被装载、执行。

JVM的运行时内存

  JVM的运行时内存也叫做JVM堆,从GC的角度可以将JVM分为新生代、老年代和永久代。其中新生代默认占1/3堆内存空间,老年代默认占2/3堆内存空间,永久代占非常少的对内存空间。新生代又分为Eden区、SurvivorFrom区和SurvivorTo区, Eden区默认占8/10新生代空间,SurvivorFrom区和SurvivorTo区默认分别占1/10新生代空间,Eden区最小占3/5新生代空间,SurvivorFrom区和SurvivorTo区分别占1/5新生代空间,如下图所示:

新生代:Eden区、SurvivorFrom区和SurvivorTo区

  JVM新创建的对象(除了大对象外)会被放在新生代,默认占1/3对内存空间。由于JVM会频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收。新生代又分为Eden区、SurvivorFrom区和SurvivorTo区,如下所述:

  1. Eden区:Java新创建的对象首先会被存放在Eden区,如果新创建的对象属于大对象,则直接将其分配到老年代。大队相关的定义和具体的JVM版本、堆大小和垃圾回收策略有关,一般为2KB-128KB,可通过 XX:PretenureSizeThreshold设置其大小。在Eden区的内存空间不足时会触发MinorGC,对新生代进行一次垃圾回收。

  2. SurvivorFrom区:保留上一次MinorGC时的幸存者。

  3. SurvivorTo区:将上一次MinorGC时的幸存者作为这一次MinorGC的扫描者。

​ 新生代的GC过程叫做MinorGC,采用复制算法实现,具体过程如下:

  1. 把在Eden区和SurvivorFrom区中存活的对象复制到SurvivorTo区。如果某对象的年龄达到老年代的标准(对象晋升老年代的标准由 XX:MaxTenuringThreshold设置,默认为15),则将其复制到老年代,同时把这些对象的年龄加1;如果SuriviorTo区的内存空间不够,则也直接将其复制到老年代;如果对象属于大对象(大小为2KB-128KB的对象属于大对象,例如通过 XX:PretenureSizeThreshold=2097152 设置大对象为2MB,1024 × 1024 × 2Byte = 2MB),则也直接将其复制到老年代。
  2. 清空Eden区和SurvivorFrom区中的对象。
  3. 将SurvivoTo区和SurvivorFrom区互换,原来的SurvivorTo区成为下一次GC时的SurvivorFrom区。

老年代

  老年代主要存放长生命周期的对象和大对象。老年代的GC过程叫做MajorGC。在老年代对象比较稳定,MajorGC不会被频繁触发。在进行MajorGC前,JVM会进行一次MinorGC,在MinorGC过后仍然出现老年代且当老年代空间不足或无法找到足够大的连续内存空间分配给新创建的大对象时,会触发MajorGC进行垃圾回收,释放JVM的内存空间。

  MajorGC采用标记清除算法,该算法首先会扫描所有对象并标记存活的对象,然后回收未被标记的对象,并释放空间。

  因为要先扫描老年代的所有对象再回收,所以MajorGC的耗时较长。MajorGC的标记清除算法容易产生内存碎片。在老年代没有内存空间可分配时,会抛出Out Of Memory异常。

永久代

  永久代指内存的永久保存区域,主要存放Class和Meta(元数据)的信息。Class在类加载时被放入永久。永久代和老年代、新生代不同,GC不会在程序运行期间对永久代的内存进行清理,这也导致了永久代的内存会随着加载的Class文件的增加而增加,在加载Class文件过多时会抛出Out Of Memory异常,比如Tomcat引用Jar文件过多会导致JVM内存不足而无法启动。

  需要注意的是,在Java 8 中永久代已经被元数据区(也叫做元空间)取代。元数据区的作用和永久代类似,二者最大的区别在于:元数据区并没有使用虚拟机内存,而是直接使用操作系统的本地内存。因此,元空间的大小不受JVM内存的限制,之和操作系统的内存有关。

  在Java 8 中,JVM将类的元数据放入本地内存(Native Memory)中,将常量池和类的静态变量放入Java堆中,这样JVM能够加载多少元数据信息就不再由JVM的最大可用内存(MaxPermSize)空间决定,而由操作系统的实际可用的内存空间决定。

JVM详解【三】JVM的内存结构相关推荐

  1. 内存详解-理解 JVM 如何使用 Windows 和 Linux 上的本机内存

    内存详解 理解 JVM 如何使用 Windows 和 Linux 上的本机内存 Java™ 堆耗尽并不是造成 java.lang.OutOfMemoryError 的惟一原因.如果本机内存 耗尽,则会 ...

  2. JVM虚拟机详解(三)类加载器的分类

    JVM虚拟机详解(三)类加载器的分类 1. 类加载器概述 JVM严格来讲支持两种类型的类加载器 .分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defin ...

  3. JVM详解——什么是JVM、JVM优点

    JVM详解--什么是JVM.JVM优点 最近在学习Java,经常在书中看到JVM这三个字母,那到底什么是JVM呢? 并且JVM的知识在Java面试中也是非常受面试官青睐的知识点,那对于JVM我们到底要 ...

  4. Java虚拟机(Jvm详解)

    Java虚拟机(Jvm详解) 总体知识点框架 1.运行时数据区域 线程私有的: 程序计数器 虚拟机栈 本地方法栈 线程共享的: 堆 方法区 直接内存 (非运行时数据区的一部分) Java 虚拟机规范对 ...

  5. java vm 远程监控配置文件_Java VisualVM监控远程JVM(详解)

    我们经常需要对我们的开发的软件做各种测试, 软件对系统资源的使用情况更是不可少, 目前有多个监控工具, 相比JProfiler对系统资源尤其是内存的消耗是非常庞大,JDK1.6开始自带的VisualV ...

  6. linux 进程间通信 dbus-glib【实例】详解三 数据类型和dteeth(类型签名type域)(层级结构:服务Service --> Node(对象、object) 等 )(附代码)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

  7. Carson带你学JVM:图文解析Java虚拟机内存结构

    前言 了解Java中的对象.变量等存放的内存区域十分重要 本文将全面讲解Java虚拟机中的内存模型 & 分区,希望你们会喜欢 Carson带你学JVM系列文章,具体如下: Carson带你学J ...

  8. Android init.rc文件解析过程详解(三)

    Android init.rc文件解析过程详解(三) 三.相关结构体 1.listnode listnode结构体用于建立双向链表,这种结构广泛用于kernel代码中, android源代码中定义了l ...

  9. Android Studio 插件开发详解三:翻译插件实战

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78113868 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  10. 数据结构--图(Graph)详解(三)

    数据结构–图(Graph)详解(三) 文章目录 数据结构--图(Graph)详解(三) 一.深度优先生成树和广度优先生成树 1.铺垫 2.非连通图的生成森林 3.深度优先生成森林 4.广度优先生成森林 ...

最新文章

  1. python第三方模块—psutil模块
  2. Android Studio 小技巧/快捷键 合集
  3. opencv国际象棋_国际象棋是的
  4. java异常捕获常使用的语句_要点Java14 捕获异常
  5. delete释放基本数据类型和对象数组的方法的区别和原因
  6. python建站部署_SpringBoot入门建站全系列(三十二)接入xxl-job分布式任务调度平台...
  7. DateUtils常用方法
  8. 数学之史:微积分的发明——变量数学的巨人之争
  9. Veket PuppyLinux系统装在U盘中
  10. mysql重新编译_重新编译mysqld_exporter0.10-阿里云开发者社区
  11. python结构_Python 项目的结构
  12. eplan备份时卡顿_EPLAN卡顿了怎么办?
  13. AMOS分析技术:结构方程模型的拟合度评价指标
  14. hashmap经典面试问题以及答案
  15. CTF-reverse菜鸡想要走出菜狗设计的迷宫
  16. The APR based Apache Tomcat Native library which allows optimal performance in production environm
  17. Box2D 实现不倒翁效果 绘制扇形
  18. WIN7 64位系统安装CodeWarrior 6.3及BDM驱动
  19. mybatis方法参数是list的批量插入
  20. 防住CV中这颗“不定时炸弹”,有哪些捷径?丨独家公开课实录(4)

热门文章

  1. html写一个简单的浏览页面
  2. Java和vue实现音乐播放器_vue实现的网易云音乐在线播放和下载功能案例
  3. Excel 透视表 - 添加计算字段
  4. 【Python】Python安装workflow
  5. macbook电池怎么使用?MacBook怎样检查电池健康程度
  6. 盛迈坤电子商务:店铺质量分怎么操作
  7. Java-输出字符型变量时与双引号拼接问题
  8. 精挑整理 8款HTML5/jQuery应用,高端大气上档次特效
  9. 南卡和万魔还有漫步者蓝牙耳机哪个好?半入耳式蓝牙耳机对比
  10. java entryset key值_java – HashMap如何以及何时初始化entrySet并向其中添加值