JVM之JAVA内存区域

JVM

JVM之Java内存区域

此系列为JVM(Java Vitrual Machine,Java虚拟机)系列介绍,主要是博主学习《深入理解Java虚拟机_JVM高级特性与最佳实践》所做一些总结。第一章当然要从JVM的各个组成部分进行介绍。

JVM简介

介绍JVM之前,无疑需要先介绍一下JDK、JRE这两个概念。

JDK:Java Ddvelopment Kit

JDK,Java Ddvelopment Kit,Java软件开发工具包。它是整个Java的核心组建,除了Java的基础库,它还包含了Java进行编译、调试Java程序的工具包,也就是说JDK包含了JRE。

JRE:Java RunTime Environment

JRE,Java RunTime Enviroment,Java运行环境。JRE的作用就是让我们的操作系统能够运行起Java程序,而在JRE中就包含着我们所要介绍的JVM以及核心类库。
于是我们可以知道,JDK、JRE、JVM的包含关系如下。
包含关系

Java发展史简述

1991年,一个名为Green Project 的计划孕育了Java的前身Oak(一种程序框架),它的目标是可以在诸多消费性电子产品上运行起来。
1995年互联网潮流兴起,Oak找到自身的发展思路化身为了Java。
1996年JDK 1.0发布,提供了一个纯解释执行的Java虚拟机(Sun Classic VM)实现。
1998年JVM内置JIT(Just In Time)即时编译器,同时JDK1.2发布了三个虚拟机来应对三个不同版本(EE、SE、ME)。
1999年 HotSpot虚拟机诞生,JDK升级为1.3版本。
···
JDK也一直在升级版本,而于2006年,JDK在锁与同步、GC、类加载等的实现上作出变动。
2018年JDK10发布同时Google、Oracle发生Android之战也以Google的败诉告终
···
至今JDK已经发布到JDK15版本。
在Java发展过程中,JVM也在发生着相应的变化。

JVM发展简述

虚拟机的始祖也就是1996年的Sun CLassic/Exact VM,它是Java语言首次拥有了商用的正式运行环境时但在的JVM,当时它还只能用纯解释器的方式执行Java程序,JIT即时编译器必须进行外挂才能使用。而也正是因为这一点,Java运行很慢的印象扎根于大众内心。
这里解释一下解释器执行与编译器执行的区别:

执行方式过程
解释器执行输入代码=>[解释器 解释执行]=>执行结果
编译器执行输入代码=>[编译器 编译]=>编译后的代码=>[执行]=>执行结果

编译器将输入代码编译为字节码(本地代码)并进行保存。

在这之后,HotSpot VM替换了Classic VM,成为了我们现在使用的最多的JVM,但是这一虚拟机并非Sun公司开发的,而是用“LongView Technologies”。
当然在JVM的发展史中还有着诸多其他的VM,但是目前我们应用最广泛且随着Java版本不断更新迭代的还数HotSpot VM。

Java虚拟机内存区域

Java与C++的最大区别就在于内存的管理方法,Java有虚拟机自动内存管理机制,内存动态分配且有垃圾回收技术支持,而C++的全部由自身进行管理与回收,因此Java更不容易出现内存泄漏和溢出的问题。
但这也是Java的弊端,当出现内存泄漏和溢出问题时,了解虚拟机如何工作哦才能排查做粗,修正问题。
下面将介绍一下Java虚拟机的内存分布情况。\

运行时数据区域

在执行Java程序过程中,JVM会把管理的内存怀氛围若干不同数据区域,根据《Java虚拟机规范》,大致分为五大区域:
程序计算器、虚拟机栈、本地方法栈、堆、方法区。这些区域统称为运行时数据区域。
JVM分区

程序计数器(Proogram Counter Register)

程序计数器占内存空间较小,是当前线程所执行的字节码指令指示器。可以类比于CPU的程序计数器(PC计数器)。
Java虚拟机的程序计数器主要用于存储下一条需要执行的字节码指令(或地址),字节码解释器通过改变计数器的指向,来选取下一条需要执行的指令。
Java虚拟机的多线程通过线程的轮流切换、分配处理器执行时间来实现的,即同一时刻只会执行一条线程的指令。
因此为了切换后能回到正确的执行位置,每个线程拥有只属于自身的程序计数器,即程序计数器是。
注:

  1. 程序计数器是唯一没有规定OOM(Out Of Memoory, 内存溢出)的区域
  2. 当运行Java方法时,计数器记录指令的地址;当运行本地方法,计数器的值为空。

Java虚拟机栈

与PC Register一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,其生命周期与线程保持一致,因此这一区域同样是线程安全的。
虚拟机栈描述的就是Java方法执行的线程内存模型:
每个方法被执行的时候,JVM会同步创建一个栈帧,栈帧是方法运行期十分重要的数据结构,而栈帧的主要作用就是用于存储局部变量表、操作数栈、动态连接、方法出口等,因此我们可以知道一个方法被调用执行至结束的过程,即为栈帧在虚拟机栈中入栈出栈的过程。
注:

  1. 栈的深度一般是有限度的,那么当方法栈的深度过大大于虚拟机所能允许的最大深度,此时虚拟机将抛出StackOverflow(栈溢出)异常。
  2. 当Java虚拟机栈动态申请的内存或者说动态扩展后的内存大于剩余内存时将会抛出OOM异常。

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈的作用类似,只不过虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则是为虚拟机执行本地(Native)方法服务。

Java堆

Java堆(Heap),是虚拟机管理的内存中最大的一。而正如C语言中的“堆”区域,这一部分内存是所有线程所共享的内存区域,可由开发人员自由动态分配的。在Java中,这一内存区域唯一存放的就是对象实例,根据《Java虚拟机规范》,“所有对象的实例和数组都应当在堆上分配”,同时Java堆拥有物理上不连续,逻辑上连续存储的特点。
除此之外,Java堆还被称作“GC堆”。因为它是垃圾收集器管理的内存区域。而对于GC堆,基于分代收集理论设计,又有着“新生代”、“老年代”、“永久代”等区域划分。要注意,这些区域划分并不是真实固定内存布局,而是一种设计风格罢了。
注:
Java堆既可以固定大小,也可以是可扩展的(当然是允许动态扩展的更吃香),但是当Java堆中没有内存完成实例分配,而堆还想扩展时,会抛出OOM异常。

方法区

方法区(Method Area)与Java堆相同,也是线程共享内存区域,主要用于存储已经被虚拟机加载之后的各种数据如类型信息、常量、静态变量、即时编译器编译后的代码缓存
在《Java虚拟机规范》中,方法区还被称为“非堆”(Non-Heap),用来和Java堆进行区分。其作用与Java堆相似,但是相比于Java堆,方法区可以选择不实现垃圾收集,然后这并不代表数据进入方法区后就永久保留了,该区域的回收工作主要针对该区域中常量池(运行时常量池)的回收以及类型的卸载,尤其是后者很难把控。
注:
既然方法区与Java堆有所相近,那么也很容易想到,方法区无法满足新的内存分配时,将会抛出OOM异常。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,Class文件除了有类的版本、字段、方法、借口等描述信息外,还有一项更重要的即为常量池表,用于存放编译期生成的各种字面量及符号引用,这一部分内容在类加载后存放在方法区的运行时常量池中。
至此,JVM运行时数据区域中几大区域的内容就介绍完了。

总结

博主也在慢慢学习JVM当中,前期先将JVM的运行时数据区都过了一遍,整理梳理了这几大区域的作用以及会爆出OOM异常的一些场景,希望能够使得大家对于JVM有个相对全面的了解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT 涓涓清泉

感谢打赏,我会更加努力写更好的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值