文档章节

Java的内存区域

多弗哥
 多弗哥
发布于 2017/06/04 22:50
字数 3064
阅读 2
收藏 0
点赞 0
评论 0
JVM

    根据《Java虚拟机规范(第2版)》的规定,Java虚拟机在执行Java程序时会把它所管理的内存划分为若干个不同的数据区域。虚拟机所管理得内存将会包括以下几个运行时数据区域:

1.  程序计数器

    程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以视作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

    由于Java虚拟机得多线程是通过线程切换并分配时间片的方式来实现得,在任意一个时间点,一个内核处理器都只会执行一条线程中的指令。因此,为了保证线程切换后能恢复到正确的执行位置,每条线程都会有一个独立的程序计数器,且互不影响。我们称这类内存区域是线程隔离的,为“线程私有”的内存。

    该内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

 

2.  虚拟机栈

    虚拟机栈(Virtual Machine Stacks) 也是线程私有的,它的生命周期与线程相同。

    它描述的是Java方法执行时的内存模型:每个方法在执行同时都会创建一个栈帧(Stack Frame,Java方法运行时的基础数据结构),用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈中入栈至出栈的过程。我们平常所说的比较粗的“Java内存分为堆内存和栈内存”,其中“栈内存”就是现在讲得虚拟机栈,或者是虚拟机栈中的局部变量表。

    局部变量表存放了编译器可知的各种基本数据类型、对象引用类型(reference)和返回地址类型(returnAddress ①)。局部变量表所需得内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是确定的,且在方法运行期间不会改变。所以,在Java虚拟机规范中,对这个区域规定了两种异常情况:

  • 如果线程请求的栈深度大于虚拟机所允许的最大值(即调用链过长),则抛出StackOverflowError异常 ②;
  • 如果虚拟机栈动态扩展时无法申请到足够的内存,则抛出OutOfMemoryError异常;

 

3.  本地方法栈

    本地方法栈(Native Method Stack) 与虚拟机栈的作用是相似的,它们之间的区别是虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

    在Java虚拟机规范中并没有强制规定本地方法栈所使用的语言、实现方式和数据结构,具体的虚拟机可以自由实现它,事实上,OpenJDK和SunJDK自带的HotSpot虚拟机就是直接将虚拟机栈和本地方法栈合二为一的。

    本地方法栈区域也会抛出StackOverflowError异常和OutOfMemoryError异常。

 

4.  Java堆

    对大多数应用来说,Java堆(Java Heap) 是Java虚拟机管理的最大一块内存。它是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存 ③。

    Java堆是GC(垃圾收集器)管理的主要区域,所以很多时候也称作“GC堆(Garbage Collected Heap)”。从内存回收的角度来看,由于现在垃圾收集器基本都采用的分代收集算法,所以Java堆可以再分为:新生代、老年代和永久代;新生代还可以再细分为:Eden空间、From Survivor空间、To Survivor空间等。

    从内存分配的角度来看,线程共享的Java堆中可能会划分出多个线程私有的本地线程分配缓冲区(TLAB,Thread Local Allocation Buffer)。

    根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。当前主流的虚拟机都可以按照JVM参数(-Xmx和-Xms)来扩展Java堆,在对象实例分配时,如果在堆中已经没有内存可以完成分配,且堆也无法再进行扩展时,将会抛出OutOfMemoryError异常。

 

5.  方法区

    与Java堆一样,方法区(Method Area) 也是线程共享的内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器输出的代码等数据。虽然Java虚拟机规范将方法区描述为堆的逻辑部分,但是它却被称为非堆(Non-Heap),目的是与Java堆区分开来。

    很多人都愿意把方法区称为“永久代(PermGen)”,本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区,或者说使用永久代来实现方法区GC而已,对于其他虚拟机(比如JRockit,J9等)是不存在永久代这个概念的。原则上,如何实现方法区属于虚拟机实现细节,不受虚拟机规范约束,但使用永久代来实现方法区,现在看来并不是一个好主意,因为这样更容易触碰到可用内存的上限(永久代有-XX:MaxPermSize的上限),而遇到内存溢出的问题。值得注意的是,从JKD1.7开始永久代PermGen逐渐被移除(移出原本放在永久代中的字符串常量池),最新的JDK1.8中已使用元空间(MetaSpace)代替永久代。

    相对而言,垃圾收集行为在这个区域是较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收成绩难以令人满意,尤其是类型的卸载,但是这个区域的回收确实是必要的。在Sun公司的BUG列表中,曾出现过若干个严重的BUG就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。

    当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

 

6.  运行时常量池

    运行时常量池(Runtime Constant Pool) 是方法区的一部分。Class文件中除了类的版本、字段、方法、接口等描述信息外,还有一项是常量池(Constant Pool Table),它用于存放编译期生成的各种字面量、符号引用,final变量值、类名和方法名常量,这部分内容将在类加载后存放到方法区的运行时常量池中。它们以数组形式访问,是调用方法与类联系及类的对象化的桥梁。一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

    运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能,也就是说并非预置入Class文件常量池的内容才能进入方法区的运行时常量池,程序运行期间生成的新常量也可放入池中,这种特性用的比较多的是String类的internd()方法。String.intern()是一个Native方法,它的作用是:如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的字符串的引用。不过JDK7的String#intern()方法的实现有所不同,当常量池中没有该字符串时,不再是在常量池中创建与此String内容相同的字符串,而改为在常量池中记录堆中首次出现的该字符串的引用,并返回该引用。所以,在JDK1.7之前运行时常量池是方法区的一部分,JDK1.7及之后版本已经将运行时常量池从方法区中移了出来,在Java堆(Java Heap)中开辟了一块内存区域存放运行时常量池。

 

7.  直接内存

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

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

    显然,本机直接内存分配不会受到Java堆大小的限制,但是,肯定还是会受到本机总内存(包括RAM以及SWAP区或者分页文件)的大小以及处理器寻址空间的限制,如果各个内存区域总和大于物理内存限制,将会导致动态扩展时遇到OutOfMemoryError异常。

 ∷  总结一下,Java类和对象在运行时的内存里是怎么样的呢

  • 程序运行时,类信息、方法字节码在方法区,对象实例及数组在堆里面;
  • 静态变量在方法区,字符串常量等常量在运行时常量池;
  • 线程调用方法执行时创建栈帧并压栈,方法的参数、局部变量和返回地址在栈帧的局部变量表;
  • 对象的实例变量和对象一起放在堆里,各个线程可以共享访问对象的实例数据;
  • 类信息、常量、静态数据一起放在方法区,各线程可以共享访问;
  • 程序计数器、虚拟机栈、本地方法栈随线程而生,随线程而灭;

 

注:

    ① returnAddress类型:与那些数值类的原始类型不同,returnAddress类型在Java语言之中并不存在相应的类型,也无法在程序运行期间更改returnAddress类型的值。

    ② 一个线程中的方法可能还会调用其他方法,这样就会构成方法调用链。

    ③ 为什么说几乎呢?随着JIT编译器(Just In Time)和内存逃逸分析技术的发展,栈上分配、标量替换优化技术有可能会在栈上分配对象实例。

    ④ 零拷贝(Zero-Copy)机制:它通常是指计算机在网络上发送文件时,不需要将文件内容拷贝到用户空间(User Space)而直接在内核空间(Kernel Space)中传输到网络的方式。在Zero Copy的模式中,避免了数据在用户空间和内存空间之间的拷贝,从而提高了系统的整体性能。Linux中的sendfile()方法、Java NIO中的FileChannel.transferTo()方法、Netty中的FileRegion都实现了零拷贝的功能。

 

 

参考自:

  • 《深入理解Java虚拟机》;
  • 《JVM高级特性与最佳实践》 ;

© 著作权归作者所有

共有 人打赏支持
多弗哥
粉丝 6
博文 29
码字总数 106641
作品 0
杭州
高级程序员
面试中关于Java虚拟机(jvm)的问题看这篇就够了

最近看书的过程中整理了一些面试题,面试题以及答案都在我的文章中有所提到,希望你能在以问题为导向的过程中掌握虚拟机的核心知识。面试毕竟是面试,核心知识我们还是要掌握的,加油~~~ 下面...

snailclimb ⋅ 05/12 ⋅ 0

【JVM】 java内存区域与内存溢出异常

前言 此系列博客是读《深入理解java虚拟机》所做的笔记整理。 No1. JVM内存管理这堵墙? 对C和C++的开发人员来说,在内存管理领域,他们既拥有每一个对象的“所有权”,也担负着每一个对象生...

binggetong ⋅ 05/07 ⋅ 0

Java堆和栈的区别,JVM的堆和栈的介绍

一、Java的堆内存和栈内存 Java把内存划分成两种:一种是堆内存,一种是栈内存。 堆:主要用于存储实例化的对象,数组。由JVM动态分配内存空间。一个JVM只有一个堆内存,线程是可以共享数据的...

光明辉煌 ⋅ 05/21 ⋅ 0

培训云计算学校,虚拟机基本结构讲解

我们要对JVM虚拟机的结构有一个感性的认知。毕竟我们不是编程人员,认知程度达不到那么深入。一个运行时的Java虚拟机实例的天职是:负责运行一个java程序。当启动一个Java程序时,一个虚拟机...

长沙千锋 ⋅ 05/17 ⋅ 0

JVM学习总结(一)运行时数据区

《深入Java虚拟机》这本书买了有一段时间了,当时看的时候就只是看,并没有边看边总结啥的,最后发现到脑子里面的根本所剩无几了。现在开始要好好归纳总结地再学习一遍。 运行时数据区域 JV...

hensemlee ⋅ 04/22 ⋅ 0

Java 面试知识点解析(三)——JVM篇

前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大部...

我没有三颗心脏 ⋅ 05/16 ⋅ 0

002. 深入JVM学习—JVM对象访问模式

Object obj = new Object(); 分析 --- Object obj:描述的是保存在栈内存之中,而后保存有堆内存的引用,这个数据会保存在本地变量表中(变量表描述有哪些对象,保存对象栈的位置,栈对应着堆...

影狼 ⋅ 06/22 ⋅ 0

深入理解java虚拟机阅读笔记(一)————java内存区域

第二章:Java内存区域与内存溢出 2.2 运行时数据区域 2.2.1 程序计数器: (1)、一块较小的内存空间 (2)、可看做当前线程执行的字节码的行号指示器 (3)、字节码解释器工作时通过改变这个...

qq_37468185 ⋅ 05/10 ⋅ 0

Java堆和栈的区别和介绍以及JVM的堆和栈

Java堆和栈的区别和介绍以及JVM的堆和栈 一、Java的堆内存和栈内存 Java把内存划分成两种:一种是堆内存,一种是栈内存。 堆:主要用于存储实例化的对象,数组。由JVM动态分配内存空间。一个...

代金券优惠 ⋅ 05/24 ⋅ 0

聊聊JAVA虚拟机中的垃圾收集器

前言 JAVA虚拟机的垃圾收集器是虚拟机内存的清道夫,它的存在让JAVA开发人员能将更多精力投入到业务研发上。了解垃圾收集器,并利用好这个工具,能更好的保障服务稳定性。这篇文章通过分析J...

lilugoodjob ⋅ 05/13 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 38分钟前 ⋅ 0

JavaScript零基础入门——(十一)JavaScript的DOM操作

JavaScript零基础入门——(十一)JavaScript的DOM操作 大家好,欢迎回到我们的JavaScript零基础入门。最近有些同学问我说,我讲的的比书上的精简不少。其实呢,我主要讲的是我在开发中经常会...

JandenMa ⋅ 今天 ⋅ 0

volatile和synchronized的区别

volatile和synchronized的区别 在讲这个之前需要先了解下JMM(Java memory Model :java内存模型):并发过程中如何处理可见性、原子性、有序性的问题--建立JMM模型 详情请看:https://baike.b...

MarinJ_Shao ⋅ 今天 ⋅ 0

深入分析Kubernetes Critical Pod(一)

Author: xidianwangtao@gmail.com 摘要:大家在部署Kubernetes集群AddOn组件的时候,经常会看到Annotation scheduler.alpha.kubernetes.io/critical-pod"="",以表示这是一个关键服务,那你知...

WaltonWang ⋅ 今天 ⋅ 0

原子性 - synchronized关键词

原子性概念 原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。 原子性的实现方式 在jdk中,原子性的实现方式主要分为: synchronized:关键词,它依赖于JVM,保证了同...

dotleo ⋅ 今天 ⋅ 0

【2018.06.22学习笔记】【linux高级知识 14.4-15.3】

14.4 exportfs命令 14.5 NFS客户端问题 15.1 FTP介绍 15.2/15.3 使用vsftpd搭建ftp

lgsxp ⋅ 今天 ⋅ 0

JeeSite 4.0 功能权限管理基础(Shiro)

Shiro是Apache的一个开源框架,是一个权限管理的框架,实现用户认证、用户授权等。 只要有用户参与一般都要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户...

ThinkGem ⋅ 昨天 ⋅ 0

python f-string 字符串格式化

主要内容 从Python 3.6开始,f-string是格式化字符串的一种很好的新方法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在本文的最后,您将了解如何以及为什么今...

阿豪boy ⋅ 昨天 ⋅ 0

Python实现自动登录站点

如果我们想要实现自动登录,那么我们就需要能够驱动浏览器(比如谷歌浏览器)来实现操作,ChromeDriver 刚好能够帮助我们这一点(非谷歌浏览器的驱动有所不同)。 一、确认软件版本 首先我们...

blackfoxya ⋅ 昨天 ⋅ 0

线性回归原理和实现基本认识

一:介绍 定义:线性回归在假设特证满足线性关系,根据给定的训练数据训练一个模型,并用此模型进行预测。为了了解这个定义,我们先举个简单的例子;我们假设一个线性方程 Y=2x+1, x变量为商...

wangxuwei ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部