JVM 深入笔记(1)内存区域是如何划分的?
一个超短的前言
JVM 是一个从事 Java 开发的软件工程师的修炼之路上必然要翻阅的一座山。当你了解了 Java 的基本语言特性,当你熟悉了 Java SDK 中的常用 API,当你写过一些或大或小的程序后,你就会有去了解 JVM 的需求出现。如果你现在没有这种感觉,那么可能此时去了解 JVM 并不是一个好的时机,因为你不会带着问题去探索。
从本篇开始的系列博文,记录本人的 JVM 深入学习总结,其中结合了本人自己的一些经验,也参考了一些书籍和网络资源,然后根据自己的理解写出这些博文。如有版权问题,请伊妹儿我 :)
谨以此系列博文分享给我的朋友们。
目录
- JVM 简史
- JVM 内存区域划分
- 内存溢出场景模拟
- JVM 监控工具
- Garbage Collection
1 JVM 简史
屏蔽不同的硬件平台或操作系统上的环境差异,通过一个向上层提供统一编程接口来实现Java程序可移植性的软件层,我们称之为 Java 虚拟机(Java Virtual Machine,简称 JVM)。
虽然 Java 的发展史可以追溯到 1991年4月由著名的 James Gosling 领导的 Green Project 计划,但是 JDK 1.0 版本的正式发布是在 1996年的1月23日,该版本提供的 JVM 是一个纯解释执行的 Sun Classic VM,不过是以外部加载的方式来使用的。而该版本的 JDK 所包含的主要技术除了 JVM 之外,就是 Applet 和 AWT。当然,此前在 Java 还叫做 Oak 的时候就已经有了一个完整的编程语言的外形,而 1995年5月23日,Oak 正式更名为 Java,并由 Sun 公司发布了 Java 1.0 版本。
关于 Java 语言的背景,这里就不多说了,主要还是介绍 JVM 的发展历程。到 1998年发展出了 JDK 1.2,在该版本中 JVM 内置了 JIT (Just In Time) 编译器,而 JDK 1.2 中也曾有过 Sun Classic VM、Hot Spot VM 和 Sun Exact VM 三种虚拟机。其中 Hot Spot VM 和 Extract VM 都内置 JIT 编译器。1997年,Sun 收购了开发 Hot Spot VM 的名为 Longview Technologies 的公司。也从此该虚拟机改叫 Sun Hot Spot VM,当然那么一个前缀对于 Developers 来说是没所谓的。从 JDK 1.3 开始,Sun Hot Spot VM 成为 Sun 公司发布的 JDK 的默认 JVM。
目前活跃的商用 JVM 有 Sun Hot Spot、BEA JRockit 和 IBM J9。不过要说的是,JRockit 的主人 BEA 被 Oracle 收购了,而 Hot Spot 的主人被 Sun 公司在 2010 年也被 Oracle 收购了。因此 Hot Spot 和 JRockit 都隶属于 Oracle 公司。Oracle 曾称将会将这个两个 JVM 的优势相融合,产生一款新的 JVM,届时 Hot Spot 和 JRockit 也将进入历史博物馆了。JVM 的鼻祖 Sun Classic VM 早已被淘汰使用了,而 曾在 JDK 1.2 中灵光乍现过的 Sun Extract VM 也已经退出了历史舞台。另一个由 Apache 基金会主导的 Harmony 项目也有很大的影响,且间接由其催生的 Dalvik 虚拟机,为 Google Android 的火爆发展做出了巨大的贡献。在应用于手机、平板电脑、IVI、PDA 等设备上的嵌入式 JVM 领域,除了 Dalvik,还有 KVM、CDC Hot Spot、CLDC Hot Spot 等 JVM 也较有影响力。
从本文开始的系列博文《JVM 原理与实战》中所有实验性程序的环境,都是 Mac OS X 10.7.3,JDK 1.6.0 Update 29,Oracle Hot Spot 20.4-b02。
2 初识 JVM 内存区域划分
大多数 JVM 将内存区域划分为 Method Area(Non-Heap), Heap, Program Counter Register, Java Method Stack,Native Method Stack 和 Direct Memomry(注意 Directory Memory 并不属于 JVM 管理的内存区域)。前三者一般译为:方法区、堆、程序计数器。但不同的资料和书籍上对于后三者的中文译名不尽相同,这里将它们分别译作:Java 方法栈、原生方法栈和直接内存区。对于不同的 JVM,内存区域划分可能会有所差异,比如 Hot Spot 就将 Java 方法栈和原生方法栈合二为一,我们可以同城为方法栈(Method Stack)。
首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?
概括地说来,JVM 每遇到一个线程,就为其分配一个程序计数器、Java 方法栈和原生方法栈。当线程终止时,两者所占用的内存空间也会被释放掉。栈中存储的是栈帧,可以说每个栈帧对应一个“运行现场”。在每个“运行现场”中,如果出现了一个局部对象,则它的实例数据被保存在堆中,而类数据被保存在方法区。
我们用上面这一小段文字就描述完了每个内存区域的基本功能。但是这还比较粗糙,下面分别介绍它们的存储对象、生存期与空间管理策略。
2.1 程序计数器
- 线程特性:私有
- 存储内容:字节码文件指令地址(Java Methods),或 Undefined(Native Methods)
- 生命周期:随线程而生死
- 空间策略:占用内存很小
这个最简单,就先捡它说吧。程序计数器,是线程私有(与线程共享相对)的,也就是说有 N 个线程,JVM 就会分配 N 个程序计数器。如果当前线程在执行一个 Java 方法,则程序计数器记录着该线程所执行的字节码文件中的指令地址。如果线程执行的是一个 Native 方法,则计数器值为 Undefined。
程序计数器的生存期多长呢?显然程序计数器是伴随线程生而生,伴随线程死而死的。而它所占用的内存空间也很小。
2.2 Java 方法栈与原生方法栈
Java 方法栈也是线程私有的,每个 Java 方法栈都是由一个个栈帧组成的,每个栈帧是一个方法运行期的基础数据结构,它存储着局部变量表、操作数栈、动态链接、方法出口等信息。当线程调用调用了一个 Java 方法时,一个栈帧就被压入(push)到相应的 Java 方法栈。当线程从一个 Java 方法返回时,相应的 Java 方法栈就弹出(pop)一个栈帧。
其中要详细介绍的是局部变量表,它保存者各种基本数据类型和对象引用(Object reference)。基本数据类型包括 boolean、byte、char、short、int、long、float、double。对象引用,本质就是一个地址(也可以说是一个“指针”),该地址是堆中的一个地址,通过这个地址可以找到相应的 Object(注意是“找到”,原因会在下面解释)。而这个地址找到相应 Object 的方式有两种。一种是该地址存储着 Pointer to Object Instance Data 和 Pointer to Object Class Data,另一种是该地址存储着 Object Instance Data,其中又包含有 Pointer to Object Class Data。如下两图所示。
图1·句柄方式的
图2·直接方式
第一种方式,Java 方法栈中有 Handler Pool 和 Instance Pool。无论哪种方式,Object Class Data 都是存储在方法区的,Object Instance Data 都是存储在堆中的。
原生方法栈与 Java 方法栈相类似,这里不再赘述。
2.3 堆
堆是在启动虚拟机的时候划分出来的区域,其大小由参数或默认参数指定。当虚拟机终止运行时,会释放堆内存。一个 JVM 只有一个堆,它自然是线程共享的。堆中存储的是所有的 Object Instant Data 以及数组(不过随着栈上分配技术、标量替换技术等优化手段的发展,对象也不一定都存储在堆上了),这些 Instance 由垃圾管理器(Garbage Collector)管理,具体的算法会在后面提到。
堆可以是由不连续的物理内存空间组成的,并且既可以固定大小,也可以设置为可扩展的(Scalable)。
2.4 方法区
通过(2)中 Java 方法栈的介绍,你已经知道了 Object Class Data 是存储在方法区的。除此之外,常量、静态变量、JIT 编译后的代码也都在方法区。正因为方法区所存储的数据与堆有一种类比关系,所以它还被称为 Non-Heap。方法区也可以是内存不连续的区域组成的,并且可设置为固定大小,也可以设置为可扩展的,这点与堆一样。
方法区内部有一个非常重要的区域,叫做运行时常量池(Runtime Constant Pool,简称 RCP)。在字节码文件中有常量池(Constant Pool Table),用于存储编译器产生的字面量和符号引用。每个字节码文件中的常量池在类被加载后,都会存储到方法区中。值得注意的是,运行时产生的新常量也可以被放入常量池中,比如 String 类中的 intern() 方法产生的常量。
2.5 直接内存区
直接内存区并不是 JVM 管理的内存区域的一部分,而是其之外的。该区域也会在 Java 开发中使用到,并且存在导致内存溢出的隐患。如果你对 NIO 有所了解,可能会知道 NIO 是可以使用 Native Methods 来使用直接内存区的。
Reference
[1] 关于 Exact VM 的资料较少,我是在《深入理解 Java 虚拟机》一书中首次了解到的。
-
如果这篇文章帮助到了您,欢迎您到我的博客留言,我会很高兴的。
转载请注明来自“柳大的CSDN博客”:blog.csdn.net/Poechant
-
相关推荐
《深入理解JVM内存区域》 Java虚拟机(JVM)是Java语言的运行环境,支持多种语言,包括Scala、Kotlin、Groovy等。虚拟机历史了解即可,无需关注Hotspot。 JVM内存区域主要分为五部分:程序计数器、虚拟机栈、本地...
### 深入解析 JVM 内存区域 #### 一、Java内存区域概述 Java虚拟机(JVM)作为Java程序的运行环境,负责管理和分配内存资源。为了更好地理解和掌握JVM内部的工作机制,本篇文章将重点介绍JVM中的几个关键内存区域:...
理解这些内存区域的划分和工作方式,对于优化内存使用和避免内存泄漏至关重要。 三、类加载机制 JVM的类加载机制包括加载、验证、准备、解析和初始化五个阶段。其中,加载是找到类的.class文件并读入内存;验证...
本文将从JVM内存模型、内存区域划分、垃圾收集机制以及相关工具的使用等方面进行详细的探讨。 一、JVM内存模型 JVM内存主要分为五个区域:堆内存(Heap)、虚拟机栈(Java Stack)、方法区(Method Area)、本地...
Java虚拟机(JVM)是Java程序运行的基础,它的历史发展和内存回收机制是Java开发者必须深入了解的关键领域。本文将详细探讨JVM的发展历程以及内存管理中的垃圾回收机制。 一、JVM的历史发展 1. **早期阶段**:1995...
理解这些内存区域的工作原理对于识别和解决内存泄漏、性能瓶颈以及理解垃圾收集机制至关重要。例如,新生代的对象如果频繁存活,可能会导致大量对象晋升到老年代,进而可能导致老年代过早耗尽,引发Full GC。通过...
3. **内存区域**: - 程序计数器:记录当前线程执行的字节码指令地址。 - Java虚拟机栈:每个方法对应一个栈帧,存储局部变量表、操作数栈、动态链接和方法出口等信息。 - 本地方法栈:与JVM栈类似,但服务于Java...
逻辑结构主要包括Java源码编译器、JVM执行引擎、类加载器等组件,而物理结构则涵盖了堆、栈、本地方法栈、方法区等内存区域。JDK与JRE的区别在于,JDK包含了开发工具和JRE,而JRE仅包含运行时环境。 Java代码的编译...
堆内存(Heap)是JVM中最大的一块内存区域,用于存储对象实例。堆是线程共享的,它被划分为新生代(Young Generation)和老年代(Old Generation),新生代又细分为Eden空间、From Survivor空间和To Survivor空间,...
4. 垃圾收集:JVM的自动内存管理关键在于垃圾收集,包括可达性分析、标记-清除、复制、标记-整理、分代收集等算法,以及新生代、老年代、永久代(或元空间)等区域划分。 5. 类型系统:JVM支持基本类型、引用类型...
堆内存是Java中用于存储对象实例的区域,它在JVM内存模型中占据核心位置。在深入理解堆内存结构及其设置参数之前,我们需要明确几个概念: 1. **堆内存结构**:在Java堆内存中,主要分为两大块:**新生代(Young ...
【标题】:“6.1.5.JVM终结篇笔记1”主要探讨了JVM的深入理解和垃圾收集(GC)优化,包括GC发生时机、实验环境准备、GC日志及其分析。 【描述】:本笔记首先从宏观角度重新认识JVM,强调了JVM的物理结构,特别是...
在压缩包中,"1JVM入门.pdf"可能涵盖了JVM的基本概念和工作流程,"2JVM浅出笔记.pdf"可能进一步解释了JVM的关键特性,"8笔记10.pdf"到"7笔记7.pdf"、"5笔记5.pdf"、"6笔记6.pdf"、"4笔记4.pdf"、"3笔记.pdf"可能分别...
这份JVM相关的笔记包含了深入理解JVM内部工作机制的关键代码资源,是学习和优化Java应用程序的重要参考资料。下面,我们将深入探讨Java与JVM的相关知识点。 1. **类加载机制**:JVM通过类加载器(ClassLoader)将...
这篇笔记将深入探讨JVM的工作原理、内存管理、类加载机制以及优化策略,帮助读者全面理解JVM并提升Java程序的性能。 一、JVM概述 Java虚拟机作为一个抽象的计算机,它具有指令集、硬件架构和操作系统,使得Java...
JVM根据对象生命周期的不同,将堆内存划分为新生代和老年代,分别采用不同的回收策略。 五、性能优化 1. 参数调优:通过调整JVM启动参数,如-Xms、-Xmx设定堆大小,-XX:+UseConcMarkSweepGC选择垃圾收集器等,来...
- **堆**:所有线程共享的内存区域,用于存储对象实例。 - **栈**:线程私有的,主要用于存储局部变量等信息。 - **本地方法栈**:与栈类似,但主要用于存储本地方法调用的信息。 - **程序计数器**:当前线程所执行...
1. **内存管理**:JVM内存分为堆内存(Heap)和非堆内存(Non-Heap),包括年轻代(Young Generation)、老年代(Tenured Generation)以及永久代(PermGen,Java 8后被元空间(Metaspace)取代)。了解每个区域的...
Java虚拟机(JVM)是Java程序的核心组成部分,它负责解析和执行字节码,同时管理内存区域。本文主要探讨虚拟机的历史、运行时数据区域以及内存区域的配置。 首先,虚拟机的历史简述,虽然这里并不需要深入,但我们...
1. 堆(Heap):所有对象实例都在堆中分配内存,是JVM中最大的一块内存区域,且被所有线程共享。堆分为新生代和老年代,新生代又细分为Eden区和两个Survivor区(From、To)。 2. 方法区(Method Area):存储类信息...