一、概述

Java的内存管理采用[自动内存管理]机制,因为这个自动管理机制,Java程序员就不需要去写释放内存的代码,而且不容易出现内存泄漏问题(比C/C++程序员少一些烦恼)。但是由于内存的申请和释放都交给了Java虚拟机,一旦出现内存泄漏和溢出问题时,在不了解Java虚拟机内存结构和自动管理机制的情况下,就很难排查问题的所在。所以如果想要成为一个优秀的程序员或者进阶为一个牛逼的架构师,掌握Java虚拟机的自动内存管理机制那是必须的。

二、JVM运行时数据区域

根据《Java虚拟机规范(Java SE 7版)》的规定,Java虚拟机所管理的内存将会包括以下几个运行时的数据区域:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

如图所示:

注:上图的虚拟机运行时数据区是Java虚拟机规范所规定的区域,不同的虚拟机有不同的实现。

上面图片有线程共享和线程隔离的区域,下面在通过一张图片来进行简单说明,让你更加清晰的理解什么是线程共享和什么是线程隔离

通过上面的两个图,大概对JVM的内存模型有个初步的认识,下面我们在看一下具体的每一个区域到底是什么东东。

1、程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以是看作当前线程所执行的字节码的行号指示器。说简单一点就是一个计数器,当字节码解释器工作是能够通过改变这个计数器的值来选取下一条需要执行的字节码指令。在说明一点,各条线程之间计数器互不影响,独立存储,程序计数器器内存区域为 线程私有 的。

类比汇编语言中的程序计数器:在汇编语言中,程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。

在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,此内存区域是唯一一个在JVM规范中没有规定任何OutOfMemoryError情况的区域。

2.本地方法栈

本地方法栈和虚拟机栈所发挥的作用是很相似的,它们之间的区别不过是 虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。Sun HotSpot 直接就把本地方法栈和虚拟机栈合二为一。本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。

3、Java虚拟机栈

 Java栈也称作虚拟机栈(Java Vitual Machine Stack),也是常说的栈。Java栈是Java方法执行的内存模型。Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。栈也是线程私有的。

下图表示了一个Java栈的模型

1)、局部变量表

就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。

2)、操作数栈

想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

3)、指向运行时常量池的引用

因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。

4)、方法返回地址

当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。也就解释了栈是线程私有的。

当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。在这个区域规定了两种异常状况:

  • 如果线程请求的栈深入大于虚拟机所允许的深度,将抛出StackOverFlowError异常!
  • 如果虚拟机栈可以动态扩展,当扩展到无法申请内存到足够的内存,就会抛出OutOfMemoryError异常!

4、Java堆

堆是jvm内存管理的最大的一块区域,此内存区域的唯一目的就是存放对象的实例,所有对象实例与数组都要在堆上分配内存。它也是垃圾收集器的主要管理区域。java对可以处于物理上不连续的空间,只要逻辑上是连续的即可。线程共享的区域。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将抛出OutOfMemoryError异常。

为了支持垃圾收集,堆被分为三个部分:

  • 年轻代 : 常常又被划分为Eden区和Survivor(From Survivor To Survivor)区(Eden空间、From Survivor空间、To Survivor空间(空间分配比例是8:1:1)
  • 老年代
  • 永久代 (jdk 8已移除永久代,下面会讲解)

1) 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的 (2) Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配 (3) TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。

(4) 所有新创建的Object 都将会存储在新生代Yong Generation中。如果Young Generation的数据在一次或多次GC后存活下来,那么将被转移到OldGeneration。新的Object总是创建在Eden Space。

这些知识在后面学习GC和内存调优方面非常重要。

5、方法区

方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。方法区是堆的一个逻辑部分,为了区分Java堆,它还有一个别名Non-Heap(非堆)。相对而言,GC对于这个区域的收集是很少出现的。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

在Java 7及之前版本,我们也习惯称方法区它为“永久代”(Permanent Generation),更确切来说,应该是“HotSpot使用永久代实现了方法区”!

6、运行时常量池

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池( Constant pool table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入运行时常量池中存放。运行时常量池相对于class文件常量池的另外一个特性是具备动态性,java语言并不要求常量一定只有编译器才产生,也就是并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。

7、直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域。但这部分内存也被频繁的使用,而且也可能导致OutOfMemoryError异常出现服务器管理员可以在配置虚拟机参数时根据实际内存设置-Xmx等参数信息,但经常忽略直接内存,使得各个内存区域总和大于物理内存的限制导致OutOfMemoryError异常出现

JDK1.4中新引入了NIO机制,它是一种基于通道与缓冲区的新I/O方式,可以直接从操作系统中分配直接内存,即直接堆外分配内存,这样能在一些场景中提高性能,因为避免了在Java堆和Native堆中来回复制数据。

JVM(2) JVM内存模型的更多相关文章

  1. [转帖]JVM—深入理解内存模型与垃圾收集机制

    JVM—深入理解内存模型与垃圾收集机制 https://juejin.im/post/5d68dc9ee51d4561ad6548f7 前言 Java是一种跨平台的语言,当初其设计初衷也是为了解决各个 ...

  2. (转载)JVM中的内存模型与垃圾回收

    转载自微信公众号:Java高级架构(Java-jiagou)-----看完这篇文章,我奶奶都知道JVM中的内存模型与垃圾回收了! 六.内存模型 6.1  内存模型与运行时数据区 Java虚拟机在执行J ...

  3. JVM学习笔记——内存模型篇

    JVM学习笔记--内存模型篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存模型部分 我们会分为以下几部分进行介绍: 内存模型 乐观锁与悲观锁 synchronized优化 内 ...

  4. 【JVM】JVM系列之内存模型(六)

    一.前言 经过前面的学习,我们终于进入了虚拟机最后一部分的学习,内存模型.理解内存模型对我们理解虚拟机.正确使用多线程编程提供很大帮助.下面开始正式学习. 二.Java并发基础 在并发编程中存在两个关 ...

  5. JVM学习--(二)内存模型、可见性、指令重排序

    我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存模型 首先我们思考一下一个java线程要向另外一个线程进行通信,应该怎么做,我们再 ...

  6. 深入理解JVM(6)——Java内存模型和线程

    Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM)用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果(“即Ja ...

  7. 【JVM】Java内存模型

    原文:多线程之Java内存模型(JMM)(一) 概述 多任务和高并发是衡量一台计算机处理器的能力重要指标之一.一般衡量一个服务器性能的高低好坏,使用每秒事务处理数(Transactions Per S ...

  8. jvm(12)-java内存模型与线程

    [0]README 0.1)本文部分文字描述转自“深入理解jvm”,旨在学习“java内存模型与线程” 的基础知识:   [1]概述 1)并发处理的广泛应用是使得 Amdahl 定律代替摩尔定律称为计 ...

  9. 看完这篇文章,我奶奶都知道什么是JVM中的内存模型与垃圾回收!

    扩展阅读:JVM从入门开始深入每一个底层细节 六.内存模型 6.1.内存模型与运行时数据区 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同数据区域. Java内存模型的主要目 ...

  10. 详解JVM中的内存模型是什么?

    强烈推荐 不管是找工作还是提升水平,都建议读一下<深入理解Java虚拟机>这本书,详细讲解了JVM中的内存管理.类加载过程.垃圾回收以及最重要的性能调优实战. 本博客也是参考了这本书,有不 ...

随机推荐

  1. 剑指offer-复杂链表的复制-链表-python

    题目描述 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head.(注意,输出结果中请不要返回参数中的节点引用,否 ...

  2. 逆向与反汇编实战(一)--PEiD分析复现

    1.准备 简介: PEiD(PE Identifier)是一款著名的查壳工具,其功能强大,几乎可以侦测出所有的壳,其数量已超过470 种PE 文档 的加壳类型和签名. 整个过程需要测试文件成品:htt ...

  3. vuex实现数据共享

    1.store.js结构 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Sto ...

  4. Windows 实用软件

    Useful tool Listary Ditto Winsnap Quick Look Myper Splash GifCam ScreenToGif Free Download Manage Si ...

  5. ZX树初步

    一些数据结构,比如线段树或平衡树,他们一般是要么维护每个元素在原序列中的排列顺序,要么是维护每个元素的大小顺序,若是像二者兼得那么就想想主席树. 给你多个不同的数字问你1-N第K大的数是多少 最简单的 ...

  6. EffectiveC++条款04:确定对象被使用前已先被初始化

    不要混淆赋值和初始化,对于大多数类型而言,比起先调用默认构造函数然后调用赋值操作符,只调用一次拷贝构造函数是高效的 对于内置类型,也需要成员初值列(member initialization list ...

  7. 如何设置Linux虚拟机的IP地址

    本文会详细的解释如何在Linux虚拟机下设置IP地址 我的虚拟机是CentOS 首先,打开你的虚拟机 1.修改主机名 修改完主机名之后,别忘了用:wq命令保存退出 然后我们来设置虚拟机的IP地址 首先 ...

  8. ARM指令集的最新版本包括针对JavaScript的优化

    在ARM指令集中,ARMv8.3添加了一个新的float-to-int指令,其错误和超出范围的值按照JavaScript的方式处理.以前[指令]获取JavaScript的语义要慢得多,JavaScri ...

  9. layui数据表格排序图标被超出的表头挤出去

    如果表头过长,会出现超出显示三个省略号,然后把排序图标挤出去,看不到了, 效果如下 解决办法就是给图标加定位,过长的时候加上 .show-sort{ position: absolute; right ...

  10. React Native 之FlatList

    1.新建项目 2.因为要用到导航跳转, 所以添加依赖,,这里拷贝这个: "dependencies": { "@types/react": "^16. ...