01 深入理解JVM的内存区域
先来看看JVM运行时候的内存区域,如下图:

大多数 JVM 将内存区域划分为 Heap(堆)、方法区、Stack(栈)、本地方法栈、程序计数器。其中 Heap 和 方法区 是线程共享的,Stack、本地方法栈 和 程序计数器 是非线程共享的。为什么分为线程共享和非线程共享的呢?请继续往下看。
首先我们熟悉一下一个 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 .class 为扩展名),每个 Java 程序都需要运行在自己的 JVM 上,被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
概括地说,JVM 初始运行的时候都会分配好 Heap 和 方法区,而 JVM 每遇到一个线程,就为其分配一个 Stack、本地方法栈 和 程序计数器。当线程终止时,三者所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与 Java 程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说是发生在 Heap 上)的原因。
1、Heap
Heap(堆)是 JVM 的内存数据区。Heap 的管理很复杂,是被所有线程共享的内存区域,在 JVM 启动的时候创建,专门用来保存对象。在 Heap 中分配一定的内存来保存对象,实际上也只是保存对象的属性值、属性的类型 和 对象本身的类型标记等,并不保存对象的方法(方法以栈帧的形式保存在 Stack 中)。而对象在 Heap 中分配好内存以后,需要在 Stack 中保存一个4个字节的 Heap 内存地址,用来定位该对象在 Heap 中的位置,便于找到该对象。Heap 是垃圾回收的主要场所。Heap 处于物理不连续的内存空间中,只要逻辑上连续即可。
2、方法区
Object Class Data(加载类的类定义数据) 是存储在方法区的。除此之外,常量、静态变量、JIT(即时编译器)编译后的代码也都在方法区。正因为方法区所存储的数据与堆有一种类比关系,所以它还被称为 Non-Heap。方法区也可以是内存不连续的区域组成的,并且可设置为固定大小,也可以设置为可扩展的,这点与堆一样。
垃圾回收在这个区域会比较少出现,这个区域的内存回收主要针对常量池的回收和类的卸载。
3、Stack
先来了解下 Java 指令的构成:
Java 指令由 操作码(方法本身)和 操作数(方法内部变量)组成。
1)方法本身是指令的操作码部分,保存在 Stack 中;
2)方法内部变量(局部变量)作为指令的操作数部分,跟在指令的操作码之后,保存在 Stack 中(实际上是简单类型(int, byte, short 等)保存在 Stack 中,对象类型在 Stack 中保存地址,在 Heap 中保存值);
栈也叫栈内存,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就 Over ,所以不存在垃圾回收。也有一些资料翻译成 Java 方法栈,大概是因为它所描述的是 Java 方法执行的内存模型,每个方法执行的同时创建栈帧(Strack Frame)用于存储局部变量表,操作栈(记录出栈、入栈的操作),动态链接,方法出口 等信息,每个方法被调用直到执行完毕的过程,对应着 栈帧 在 栈 的入栈和出栈的过程。
局部变量表 存放了编译期可知的各种基本数据类型(byte、short、int、long、float、double、boolean、char)、对象的引用( reference 类型,不等同于对象本身)和 returnAdress 类型(指向下一条字节码指令的地址)。局部变量表 所需的内存空间在编译期间完成分配,在方法运行之前,该局部变量表所需要的内存空间是固定的,运行期间也不会改变。
栈帧是一个内存区块,是一个数据集,是一个有关方法( Method ) 和运行期数据的数据集,当一个方法 A 被调用时就产生了一个栈帧 F1 ,并被压入到栈中,A 方法又调用了 B 方法,于是产生栈帧 F2 也被压入栈,执行完毕后,先弹出 F2 栈帧,再弹出 F1 栈帧,遵循 “先进后出” 原则。
4、本地方法栈
与 Stack 相似,Stack 为 JVM 提供执行 Java 方法的服务,本地方法栈 则为 JVM 提供使用 native 方法的服务。
5、程序计数器
程序计数器是一块较小的内存区域,作用可以看做是当前线程执行的字节码的位置指示器。分支、循环、跳转、异常处理和线程恢复等基础功能都需要依赖这个计数器来完成。
JVM 运行例子
我们来写一个简单的类,代码如下:
public class JVMShowcase {
// 静态类常量
public final static String ClASS_CONST = "I'm a Const";
// 私有实例变量
private int instanceVar = 15;
public static void main(String[] args) {
// 调用静态方法
runStaticMethod();
// 调用非静态方法
JVMShowcase showcase = new JVMShowcase();
showcase.runNonStaticMethod(100);
}
// 常规静态方法
public static String runStaticMethod() {
return ClASS_CONST;
}
// 非静态方法
public int runNonStaticMethod(int parameter) {
int methodVar = this.instanceVar * parameter;
return methodVar;
}
}
这个类没有任何意义,不用猜测这个类是做什么用的,只是写一个比较典型的类,然后我们来看看 JVM 是如何运行的,也就是输入 java JVMShowcase 后,我们来看 JVM 是如何处理的:
第 1 步, 向操作系统申请空闲内存。JVM 对操作系统说 “给我 64M(随便模拟数据,并不是真实数据) 空闲内存” ,于是,操作系统就查找自己的内存分配表,找了段 64M 的内存写上 “Java 占用” 标签,然后把内存段的起始地址和终止地址给 JVM, JVM 准备加载类文件。
第 2 步,JVM 分配内存。JVM 获得 64M 内存,就开始得瑟了,首先给 Heap 分配内存,然后给 Stack 也分配好。
第 3 步,文件检查 和 分析 class 文件。若发现有错误即返回错误。
第 4 步,加载类。由于没有指定加载器,JVM 默认使用 bootstrap 加载器,就把 rt.jar 下的所有类都加载,JVMShowcase 也被加载到内存中。我们来看看方法区,如下图:(这时候包含了 main 方法和 runStaticMethod 方法的符号引用,因为它们都是静态方法,在类加载的时候就会加载)

此时,Heap 是空,Stack 是空,因为还没有对象的新建和线程被执行。
- 第 5 步,执行方法。执行 main 方法。执行启动一个线程,开始执行 main 方法,在 main 执行完毕前,方法区如下图所示:
(public final static String ClASS_CONST = "I'm a Const"; )

在 方法区 加入了 CLASS_CONST 常量,它是在第一次被访问时产生的(runStaticMethod方法内部)。
Heap 内存中有两个对象 Object 和 showcase 对象,如下图所示:(执行了JVMShowcase showcase = new JVMShowcase(); )

为什么会有 Object 对象呢?因为它是 JVMShowcase 的父类,JVM 是先初始化父类,然后再初始化子类,不管有多少个父类都初始化。
在 Stack 内存中有三个栈帧,如下图所示:

于此同时,还创建了一个程序计数器指向下一条要执行的语句。
- 第 6 步,释放内存。运行结束,JVM 向操作系统发送消息,说 “内存用完了,我还给你” ,运行结束。
01 深入理解JVM的内存区域的更多相关文章
- 深入理解jvm之内存区域与内存溢出
文章目录 1. Java内存区域与内存溢出异常 1.1. 运行时数据区域 1.1.1. 程序计数器 1.1.2. java虚拟机栈 1.1.3. 本地方法栈 1.1.4. Java堆(Java Hea ...
- 深入理解 JVM 的内存区域
深入理解运行时数据区 代码示例: 1. JVM 向操作系统申请内存: JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间,根据内存大小找到具体的内存分配表,然后把内存段的起始地址和终 ...
- 深入理解JVM - Java内存区域与内存溢出异常 - 第二章
一 运行时数据区域 JVM在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间. 程序计数器 程序计数器(Program Counter ...
- JVM的内存区域划分
JVM的内存区域划分 学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内存又是如何划分的 ...
- JVM的内存区域划分以及垃圾回收机制详解
在我们写Java代码时,大部分情况下是不用关心你New的对象是否被释放掉,或者什么时候被释放掉.因为JVM中有垃圾自动回收机制.在之前的博客中我们聊过Objective-C中的MRC(手动引用计数)以 ...
- JVM的内存区域划分(转)
原文链接:JVM的内存区域划分 JVM的内存区域划分 学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内 ...
- 【java】JVM的内存区域划分
学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内存又是如何划分的呢? 由于Java程序是交由JVM执行 ...
- JVM(二)-内存区域之线程私有区
概述: 对于从事C.C++开发的程序员来说,在内存管理领域,他们既是拥有最高权力的"皇帝",又是从事最基础工作的劳动人民--既拥有每个对象的"所有权", 又担负 ...
- JVM的内存区域模型
首先要明白一个概念,就是JVM的内存区域划分与java的内存区域模型是两个不同的概念,前者指的是在java中jvm会将一个程序划分为哪些块来存储对应的数据,后者是一个更宏观上的j概念,指的是java线 ...
随机推荐
- DALI解码模块
DALI-MOD2接口模块使用手册 一.概述(联系人:张先生,电话:13923882807,QQ:813267849) 一.概述 欢迎使用本公司的DALI解码模块,该模块支持"DALI第一套 ...
- vue学习笔记(四)——Vue实例以及生命周期
1.Vue实例API 1.构造器(实例化) var vm = new Vue({ //选项 |-------DOM(3) | |-------el (提供一个在页面上已存在的 DOM 元素作为 V ...
- linux nvme的sendfile流程
在nvme的硬盘上使用sendfile系统调用,到底需要经过哪些流程? do_sendfile--->do_splice_direct-->splice_direct_to_actor-- ...
- java —— equals 与 ==
equals 众所周知,java 中的所有的类都继承自 Object 这个超类 ,他就是Java所有类的父类或祖先类,Object类里面有一个equals方法,并且提供了默认的实现,如下所示. pub ...
- python 爬取B站视频弹幕信息
获取B站视频弹幕,相对来说很简单,需要用到的知识点有requests.re两个库.requests用来获得网页信息,re正则匹配获取你需要的信息,当然还有其他的方法,例如Xpath.进入你所观看的视频 ...
- Servlet--ServletRequest接口,ServletResponse接口
ServletRequest接口 定义 public interface ServletRequest 定义一个 Servlet 引擎产生的对象,通过这个对象, Servlet 可以获得客户端请求的数 ...
- jdbc参数
JDBC连接池参数: jdbc.initialSize=0 //初始化连接 jdbc.maxActive=30 //连接池的最大数据库连接数,设为0表示无限制 j ...
- 【转】shell学习笔记(二) ——shell变量
在shell中有3种变量:系统变量,环境变量和用户变量,其中系统变量在对参数判断和命令返回值判断时会使用,环境变量主要是在程序运行时需要设置,用户变量在编程过程中使用量最多. 1 系统变量 变量名 ...
- 动态库Link error 一种可能
一般出现link error都是链接时找不到对应函数. 前些天出现这种错误,我反复地检查,都发现动态库里明确的是有对应函数的. 代码里有这样一句#pragma comment(lib,"li ...
- 01_什么是数据结构以及C语言指针回顾
一.数据结构是什么 如何把现实中大量而复杂的问题,以特定的数据类型和特定的数据存储结构保存到计算机的存储器中. 数据存储包括两方面:个体存储的集合.个体与个体之间的关系的存储 程序 = 算法 + 数据 ...