对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。

正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。


1. 运行时数据区域

Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。

JDK1.6的运行时数据区域划分如下:

JDK1.8的运行时数据区域划分如下:

Java内存运行区域中,有些组成部分是线程私有的,其他的则是线程共享的。

线程私有的:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

线程共享的:

  • 方法区
  • 直接内存

2. 程序计数器

程序计数器是一块较小的内存空间。可以看做是当前线程所执行的字节码的行号指示器

字节码解释器工作时通过改变这个程序计数器的值来选取下一条需要执行的字节码指令,比如分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。

此外,为了线程切换后能恢复到正确的位置,每条线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

总结上边内容,程序计数器主要有两个作用:

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

3. Java虚拟机栈

与程序计数器一样,Java虚拟机栈也是线程私有的。

它的生命周期和线程相同,描述的是Java方法执行的内存模型。

Java内存可以粗糙的区分为堆(Heap)内存栈(Stack)内存,其中栈内存说的就是Java虚拟机栈或者说是虚拟机栈中局部变量表部分。

(实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息)。

局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他于此对象相关的位置)。

Java虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。

  • StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
  • OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。

Java虚拟机栈是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。

那么方法/函数如何调用?

Java 栈可以类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。

Java 方法有两种返回方式:

  1. return 语句。
  2. 抛出异常。

不管哪种返回方式都会导致栈帧被弹出。


4. 本地方法栈

本地方法栈和虚拟机栈发挥的作用相似。

本地方法栈和虚拟机栈的区别:

  • 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务。
  • 本地方法栈则为虚拟机使用到的 Native 方法服务。

在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、方法出口信息。

方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowErrorOutOfMemoryError 两种异常。


5. 堆Heap

堆内存是Java虚拟机所管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建。

堆内存的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存

Java堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap)。

从垃圾回收角度,由于现在收集器基本都采用分代垃圾回收算法,所以,

Java堆还可以细分为:新生代、老年代。

再细分有:Eden空间、From Survivor、To Survivor空间等。

进一步划分的目的是更好地回收内存,或者更快地分配内存。

在 JDK 7 版本及 JDK 7 版本之前,堆内存被通常分为下面三部分:

  1. 新生代内存(Young Generation)
  2. 老生代(Old Generation)
  3. 永生代(Permanent Generation)

在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。

上图所示的 Eden 区、两个 Survivor 区都属于新生代(为了区分,这两个 Survivor 区域按照顺序被命名为 from 和 to),中间一层属于老年代。

大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

堆这里最容易出现的就是 OutOfMemoryError 错误,并且出现这种错误之后的表现形式还会有几种,比如:

  1. java.lang.OutOfMemoryError: GC Overhead Limit Exceeded : 当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。
  2. java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。(和配置的最大堆内存有关,且受制于物理内存大小。最大堆内存可通过-Xmx参数配置,若没有特别配置,将会使用默认值。

6. 方法区

方法区与 Java 堆一样,是各个线程共享的内存区域。

方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。

相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。


7. 运行时常量池

运行时常量池是方法区的一部分。

Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)


8. 直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。

JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据

本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

Java内存区域(运行时数据区)相关推荐

  1. Java内存区域-运行时数据区域

    Java虚拟机在运行时将内存划分为以下五个不同区域. 1.程序计数器: 是一块较小空间,可以看作是当前线程所执行的字节码行号指示器.字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字 ...

  2. @JVM内存模型(运行时数据区)

    前言 说到Java内存区域,可能很多人第一反应是"堆栈".首先堆栈不是一个概念,而是两个概念,堆和栈是两块不同的内存区域,简单理解的话,堆是用来存放对象而栈是用来执行程序的.对于J ...

  3. 【Java虚拟机】运行时数据区

    Java在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途.创建和销毁的时间,有一些是随虚拟机的启动而创建,随虚拟机的退出而销毁,有些则是与线程一一对应,随 ...

  4. JVM内存模型——运行时数据区的特点和作用

    文章目录 前言 1程序计数器 2本地方法栈 3虚拟机栈 3.1局部变量表 3.2操作数栈 3.3动态连接 3.4返回地址 4方法区 5堆 5.1查看方法区跟堆大小 5.2新生代跟老年代 5.3什么时候 ...

  5. 20张图助你了解JVM运行时数据区,你还觉得枯燥吗?

    我们的JVM系列已经断更好几天了,小伙伴们在后台疯狂私信阿Q,想看后续内容,今天它来了.相信大家在上篇文章中已经对类加载子系统有了清晰的认识,接下来就让我们来揭开"运行时数据区"的 ...

  6. 1、虚拟机内存管理、运行时数据区、线程共享区、Java堆、新生代、老年代、Eden区域分配、方法区、线程独占区、虚拟机栈

    1.Java虚拟机内存管理 1.1.运行时数据区[Runtime Data Area] 1.1.1.线程共享区 1.1.1.1.Java堆[heap] 1.1.1.1.1.新生代.老年代.Eden区域 ...

  7. Java内存管理:Java内存区域 JVM运行时数据区

    Java内存管理:Java内存区域 JVM运行时数据区 在前面的一些文章了解到javac编译的大体过程.Class文件结构.以及JVM字节码指令. 下面我们详细了解Java内存区域:先说明JVM规范定 ...

  8. JVM内存区域(运行时数据区)划分

    前言: 我们每天都在编写Java代码,编译,执行.很多人已经知道Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文 ...

  9. Java JVM内存模型(运行时数据区域)详解

    详细介绍了JVM运行时数据区域,包括方法区.堆空间.栈空间.本地方法栈.程序计数器.常量池.直接内存.字面量.符号引用.直接引用. Java程序在运行时,需要在内存中的分配空间.为了提高运算效率,ja ...

  10. JVM(类加载、运行时数据区、堆内存、方法区、本地接口、执行引擎和垃圾回收)java虚拟机(JVM)的超详细知识点

    JVM虚拟机 一.JVM的概述 1.为什么要学习JVM 2.虚拟机 3.JVM的作用 作用 特点 4.JVM的位置 5.JVM的分类 6.各个组成部分的用途 7.Java 代码的执行流程 8.JVM ...

最新文章

  1. NLP入门 | 通俗讲解Subword Models
  2. Spark1.0.0 开发环境高速搭建
  3. element input 只能输入数字_Python之input()函数
  4. 4.0 编译apk中无classes.dex问题解决方法
  5. java怎么设置404界面_如何使用Spring MVC显示自定义的404 Not Found页面
  6. ios简单sqlite使用
  7. 哪些Amazon erp是可以免费使用的?
  8. Autodesk 首届中国开发者训练营将开始报名,5月24日前报名6折优惠!
  9. WebLogic部署配置
  10. 模拟电路——集成运算放大器(1)
  11. 通过nali命令统计访问的IP输入地理区域等作用
  12. 【12c】12c RMAN新特性之recover table(表级别恢复)
  13. C语言实现矩阵的秩求解分析
  14. 目标检测任务超大图像的切图实现
  15. Windows下生成dump文件的三种方式
  16. 计算机网络基础(类别 | 性能指标 | OSI模型初识)
  17. 流程图讲解_流程图+地图题小作文练习,详细讲解+精选范文!!
  18. Power BI----M函数*
  19. 6种比较好的“在线图片无损压缩工具”+PDF转换工具
  20. python画蛋糕祝福_python实现生日蛋糕

热门文章

  1. Solaris做desktop必装的10个软件
  2. ConcurrentHashMap源码(JDK1.8)
  3. 【Java必备技能四】如何使用泛型?
  4. JOIN查询流程与驱动表
  5. 探索Spring异步代理循环依赖失败的问题
  6. ubuntu16 下安装freeswitch 1.8.3
  7. 成都阿里技术一面后感,给广大码友的一些小建议
  8. webpack系列之-原理篇
  9. Linux笔记之shell script
  10. UAC2.0 Requests处理