引言

对于C++程序员,内存分配与回收的处理一直是令人头疼的问题。Java由于自身的自动内存管理机制,使得管理内存变得非常轻松,不容易出现内存泄漏,溢出的问题。

不容易不代表不会出现问题,一旦内存泄漏或溢出的情况发生,调试起来会变得非常困难。这就要求我们对虚拟机的内存区域有深入的理解。最终能够判断内存方面的异常发生时,具体在JVM中的位置。

内存区域

JVM运行时,首先需要类加载器(ClassLoader) 加载所需类的字节码,加载完毕交由执行引擎执行,执行过程中需要一段空间来存储数据(类比CPU与主存)。这段内存空间的分配和释放过程正是我们所关心的,称为运行时数据区

对于CS相关从业者,深入理解操作系统的内存的层次结构,分配与垃圾收集过程都是大有裨益的。同理,欲定位内存问题的出现区域,必须剖析运行时数据区

运行时数据区

如上图所示,运行时数据区包括:程序计数器(即PC寄存器),Java 虚拟机栈(VM Stack),Java 堆(Heap),方法区(Method Area),本地方法栈(Native Method Stack)。下面带领大家深入理解各个数据区域

JVM实际上就是一台虚拟的计算机,目的是为了实现"一次编译,处处执行"。所以,在理解运行时数据区时,完全可以与操作系统系统 内存,寄存器类比学习。

程序计数器

每条虚拟机中的线程都有自己的寄存器,称之为程序计数器(PC)。为了保证线程之间的独立性,因而PC内的空间是线程私有的。

  • 线程私有只能本线程访问的区域,其他线程无权访问。

程序计数器的作用

虚拟机中的多线程通过线程轮转调度,为每条线程分配时间片来实现并发执行。同一时刻,处理机只能执行一条线程。当切换到另外一条线程时,若不保存当前未执行完线程的执行位置,下次处理机再执行这条线程时,又要重新开始执行。这种情况显然是不能容忍的。

引入程序计数器的目的,就是为了记录线程的执行情况,便于下次切换后进行线程恢复

程序计数器的机制

如何记录线程的执行情况? 其实也并不复杂,只需要记录正在执行的虚拟机字节码指令的地址。如果运行的是Native(本地)方法,计数器的值为Undefined

程序计数器是唯一没有OutOfMemoryError异常的区域。

Java 虚拟机栈

每个Java方法执行时,需要分配内存空间来存储局部变量表操作数栈动态链接方法出口等信息。将这部分内存称之为栈帧(Stack Frame)。虚拟机栈用于存储栈帧,是Java方法执行的内存模型

显然我们需要为每个执行的方法分配栈空间,因此Java虚拟机栈也是线程私有的。

虚拟机栈的作用

虚拟机栈记录Java方法执行的过程。每个方法开始执行时,为之创建一个栈帧记录信息;方法执行到完成的过程,对应栈帧在虚拟机栈中入栈到出栈的过程

局部变量表

局部变量表是栈帧中的重要部分。存放编译期定义的基本数据类型, 对象引用(相当于对象地址),及returnAddress类型(字节码指令地址)。

局部变量表空间在编译期间分配,执行方法的过程中不会改变其大小。

异常

  1. 当线程请求的栈深度大于所允许的深度,抛出StackOverflowError异常。
  2. 长度不够时,虚拟机栈可进行动态扩展,申请内存。若无法申请到足够的内存,抛出OutOfMemoryError异常。

本地方法栈

本地方法栈与虚拟机栈类似,区别是虚拟机栈记录执行的Java方法,本地方法栈则记录Native方法。

本地方法栈同样会抛出StackOverflowErrorOutOfMemoryError异常。

Java 堆

Java堆用于存储对象实例,为所有对象分配内存空间

所有对象实例都要在堆上分配空间,因此Java堆是所有线程共享区域。对象的生命周期结束后,Java堆还要负责内存回收,因此Java堆也常被称之为GC堆(Garbage Collected Heap)。

内存模型

从内存回收的角度,Java堆可以分为新生代(Young Generation)与老生代(Old Generation)。这种划分的方式,是为了更好的回收内存(老生代内存会被优先回收)。

如图,新生代还可以分为Eden空间From Survivor空间To Survivor空间

永久代(Permanent Generation)用于存储静态类型数据,与垃圾收集器关系不大。

注意:本图展示的是JVM堆的内存模型,JVM堆内存包括Java堆区域永久代区域。因此,永久代不属于Java堆

异常

Java堆同样可扩展(-Xmx与-Xms参数)。若堆中内存已无法为对象实例分配且无法再扩展,抛出OutOfMemoryError异常。

方法区

方法区存储类信息常量静态变量等数据,是线程共享的区域。为与Java堆区分,方法区还有一个别名Non-Heap(非堆)。

方法区≠永久代

方法区就是永久代?并非如此。

HotSpot虚拟机选择用永久代来实现方法区,从而省去了为方法区编写内存管理代码的工作。这只是一种实现方式,其他虚拟机(BEA JRockit,IBM J9)都不存在永久代这一概念。

通过永久代来实现方法区容易造成内存溢出,未来也可能会被替代。

在虚拟机规范中,方法区的实现没有明确的规定,因此不能将方法区等同于永久代。

异常

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

运行时常量池

运行时常量池(Runtime Constant Pool)用于存放编译期生成的各种字面量和符号引用。

运行时常量池具备动态性,使得运行期间也可将新的常量放入池中。例如String类的intern() 方法。

package intern;

public class Main1 {
public static void main(String[] args) {
String s0= "I'm coding";
String s1=new String("I'm coding");
String s2=new String("I'm coding");
System.out.println( s0==s1 );
System.out.println( s0==s1.intern());
s2=s2.intern();
System.out.println( s0==s2 ); }
}

输出结果

false
true
true

本例中,s0直接保存在常量池,s1与s2的对象实例存储在Java堆中。==直接比较对象的hashCode,因此第一行输出false。s1.intern()方法返回s1在常量池中的引用,没有则创建。

s1存放的字符串已经在常量池中存在,直接返回s0的引用,第二行输出true

同理,s2接收了s2.intern()的返回值,字符串值与s0相同,第三行输出true

运行时常量池是方法区的一部分,因此受方法区内存的限制。当无法申请到内存时,抛出OutOfMemoryError异常。

总结

对于JVM的内存管理, 最重要的还是与OS内存管理知识进行类比以及结合实践来学习。理解JVM内存区域的目的也是为了在工程中出现内存相关异常时能够准确的定位所在区域,及时处理。

后续我们将在本文的基础上来理解对象的创建过程以及OutOfMemoryError异常


作者: I'm coding

链接ACFLOOD

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

如果您觉得本文对您有所帮助,就给俺点个赞吧!

走进JVM【二】理解JVM内存区域的更多相关文章

  1. JVM探秘:Java内存区域

    本系列笔记主要基于<深入理解Java虚拟机:JVM高级特性与最佳实践 第2版>,是这本书的读书笔记. 概述 Java 虚拟机为程序员分担了很多内存管理的工作,不再像 C/C++ 那样容易出 ...

  2. 【搞定Jvm面试】 Java 内存区域揭秘附常见面试题解析

    本文已经收录自笔者开源的 JavaGuide: https://github.com/Snailclimb ([Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识)如果觉得不错 ...

  3. 1.JVM中的五大内存区域划分详解及快速扫盲

    本博客参考<深入理解Java虚拟机>这本书 视频及电子书详见:https://shimo.im/docs/HP6qqHx38xCJwcv9/ 一.快速扫盲 1. JVM是什么   JVM是 ...

  4. 【JVM.1】java内存区域与内存溢出

    鲁迅曾说过:Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进来,墙里面的人想出去. 一.虚拟机内存分布 Java虚拟机在执行Java程序的过程中会把它所管理的内存 ...

  5. JVM笔记(一) Java内存区域

    Java 内存区域 总概 java虚拟机在执行java程序的过程中,会把它管理的内存划分为几个不同的数据区域.每当运行一个java程序时,就会启动一个虚拟机. 具体的区域如图所示: 同时,方法区 与 ...

  6. JVM那些事儿之内存区域

    相信绝大多数java开发者或多或少的都应该知道jvm,但是有多少人又深入去了解过,笔者深感自身能力的不足,去看了些资料,觉得还是有必要整理下自己的学习记录,时常回头看看,多看多实践提升自己的能力,故开 ...

  7. JVM笔记-运行时内存区域划分

    1. 概述 Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域.它们各有用途,有些随着虚拟机进程的启动一直存在(堆.方法区),有些则随着用户线程的启动和结束而建立 ...

  8. JVM参数配置 java内存区域

    java内存区域 一些基本概念 http://www.importnew.com/18694.html https://www.cnblogs.com/wangyayun/p/6557851.html ...

  9. JVM 之:Java 内存区域与内存溢出

    内存区域 Java 虚拟机在执行 Java 程序的过程中会把他所管理的内存划分为若干个不同的数据区域.Java 虚拟机规范将 JVM 所管理的内存分为以下几个运行时数据区:程序计数器.Java 虚拟机 ...

  10. JVM虚拟机20:内存区域详解(Eden Space、Survivor Space、Old Gen、Code Cache和Perm Gen)

    1.内存区域划分 根据我们之前介绍的垃圾收集算法,限定商用虚拟机基本都采用分代收集算法进行垃圾回收.根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法.大批对象死去.少量 ...

随机推荐

  1. What is 软件工程

    话说新的一学期,有一门叫软件工程的专业课,作为计算机科学与技术专业的学生,在上课前有几个问题 1.软件工程顾名思义是学软件,可是软件这个词范围还是挺大的,那到底学的是哪方面,是编程?设计APP?还是一 ...

  2. 12.17 Daily Scrum

      Today's Task Tomorrow's Task 丁辛 实现和菜谱相关的餐厅列表. 实现和菜谱相关的餐厅列表.             邓亚梅             美化搜索框UI. 美 ...

  3. 《Linux内核分析》第七周: 可执行程序的装载

    LINUX内核分析第七周学习总结--可执行程序的装载 杨舒雯(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/ ...

  4. 《linux内核设计与实现》读书笔记——第三章

  5. layui使用记录

    一.layui表格渲染 如果后台返回的实力类里面包含另一个实体类,那么需要使用如下方式取出相应的值 var tableResult = table.render({ elem: '#' + Serve ...

  6. HDOJ2032_杨辉三角

    这是一道水题,思路很简单,把杨辉三角先求出来,然后按照输入将相应的层数的杨慧三角输出即可. HDOJ2032_杨辉三角 #include<stdio.h> #include<stdl ...

  7. redis演练

    如何查看所有的key:keys * 如何查询某个key的value:get keyname

  8. Oracle面试题(基础篇)

    1. Oracle跟SQL Server 2005的区别? 宏观上: 1). 最大的区别在于平台,oracle可以运行在不同的平台上,sql server只能运行在windows平台上,由于windo ...

  9. git 快捷键

    实际上就是弄了个别名 $ git config --global alias.st status $ git config --global alias.ci commit $ git config ...

  10. SQL ROUND函数的使用

    SQL ROUND函数的使用   SQL ROUND函数是对数据进行制定精度的取值. 第一个参数是取值的数据,第二个参数是精度,第三个参数是数据取值模式(四舍五入还是截断),其中第三个参数是可选参数, ...