引言

对于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方法。

本地方法栈同样会抛出StackOverflowError与OutOfMemoryError异常。

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异常。

走进JVM【二】理解JVM内存区域相关推荐

  1. 【JVM进阶之路】二:Java内存区域

    1.运行时数据区 Java 虚拟机定义了若干种程序运行期间会使用到的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁.另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线 ...

  2. JVM中的五大内存区域划分详解

    一.快速扫盲 1. JVM是什么 JVM是Java Virtual Machine的缩写,即咱们经常提到的Java虚拟机.虚拟机是一种抽象化的计算机,有着自己完善的硬件架构,如处理器.堆栈等,具体有什 ...

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

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

  4. Java虚拟机:JVM 主要组成部分与内存区域

    一.JVM 主要组成部分: JVM包含两个子系统和两个组件,分别为: Class loader(类装载子系统):根据给定的全限定名类名来装载class文件到运行时数据区的方法区中 Execution ...

  5. 深入理解Java虚拟机(二)Java内存区域与内存溢出异常

    一.前言 对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切都很美好 ...

  6. JVM实战与原理---内存区域分配

    JVM实战与原理 目录 内存区域分配 1. 程序计数器 2. Java虚拟机栈 3. 本地方法栈 4. 堆 5. 方法区 6. 运行时常量池 内存区域分配 章节目的:明白虚拟机中的内存是如何划分?每块 ...

  7. 基本数据类型的成员变量放在jvm的哪块内存区域里?

    几个月前自己提问的一个问题没人回答,现在突然翻到,自己回答下: 问题: 比如 class{ private int i; } 如上代码,之前一直以为基本数据类型都是放在虚拟机栈中的,最近看了<深 ...

  8. 【深入理解JVM-Java内存区域】

    众所周知C,C++的内存管理是由开发人员控制的(什么malloc,free之类的),但是在java中这个权利交给了jvm(java虚拟机)由虚拟机的自动内存管理机制管理.这样为java程序员带来了好处 ...

  9. 深度理解java jvm,深度理解JVM

    深入理解java虚拟机 要讲的内容 了解历史 垃圾回收机制 性能监控工具 性能调优案例实战 认识类的文件结构 类加载机制 字节码执行引擎 虚拟机编译及运行时优化 Java线程高级 1. 环境搭建 安装 ...

  10. jvm深入理解:内存分配与回收策略(优先在Eden分配、大对象直接进入老年代、长期存活的对象将进入老年代、动态对象年龄判定、空间分配担保)

    出入:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) Java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以及自动回收分配给对象的内存. 象的内存分配,从 ...

最新文章

  1. 内容推荐 | 生信技术与前沿内容知识库
  2. ubuntu su进入root权限
  3. springmvc教程(3)
  4. 【CentOS Linux 7】实验4【Shell编程】
  5. Linux RedHat7.0 上vsftp配置
  6. Java 字符串,byte[],16进制的字符串互转
  7. #ifndef.#define, #endif 的用法
  8. eclipse--android开发环境搭建教程
  9. C#LeetCode刷题之#169-求众数(Majority Element)
  10. mybatis 动态SQL-foreach标签
  11. [转载]Qt之获取本机网络信息_vortex_新浪博客
  12. Axure| 旋转控件或者图片
  13. scrollTop、scrollHeight、offsetTop、offsetHeight、clientTop、clientHeight区别
  14. 数学分析高等代数考研试题荟萃[更新至2017年12月15日]
  15. 网络中的延迟和抖动问题
  16. 书香小说APP界面设计
  17. Unity实现扇形Slider进度条加载功能
  18. android 摄像头同时打开方式,Android,同时打开前置和后置摄像头
  19. P2770【USACO 2014 January Gold】难度系数
  20. Windows 查看文件大小

热门文章

  1. java fx choicebox_JavaFX:具有图像和文本的ChoiceBox
  2. 群晖 上传 源文件不存在_群晖NAS连接百度网盘报错?原因是这样的
  3. python写前端图形界面_如何Tkinter模块编写Python图形界面
  4. 【渝粤教育】国家开放大学2018年春季 0077-21T古代汉语专题 参考试题
  5. 【渝粤教育】 国家开放大学2020年春季 2136管理会计 参考试题
  6. 【渝粤教育】电大中专学前儿童语言教育 (6)作业 题库
  7. 国家开放大学2021春1108钢结构(本)题目
  8. [渝粤教育] 西南科技大学 动态网页设计(JSP) 在线考试复习资料
  9. 无线网络拓扑结构简析
  10. 矢量图标库如何引入html,Iconfont矢量图标库在网站中的使用方法