JVM虚拟机学习笔记

2020-04-28 00:00:00 jvm 虚拟机 学习笔记

1.介绍

java虚拟机在执行java程序的过程中会把它所在管理的内存区域划分为若干个不通的数据区域。

这些区域各有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而销毁。

根据《java虚拟机规范》的规定,java虚拟机所管理的区域将会包含一下几个运行时数据区域,如下图所示(图片来自于《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明》):

《JVM虚拟机学习笔记》

2.程序计数器

程序计数器是一块较小的内存,它可以看作是当前线程所执行代码的行号指示器,在java虚拟机模型里面,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的指令,他是程序控制流程的指示器,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖它来完成。

java的多线程是通过线程轮流切换,分配处理器执行时间的方式来实现的,一个处理器(单核)只会执行一条线程中的指令。因此,为了线程切换之后能恢复到正确的执行位置,每条线程都需要独立的一个程序计数器。

各线程之间互不干扰,独立存储。我们称这内存储区域为 【线程私有】的内存。

如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是一个Native方法,这个计数器值应该为空(undefined)。此区域是JVM中唯一一个不存在OOM(OutOfMemoryError)情况的区域。

3.Java虚拟机栈

与程序计数器一样的,java虚拟机栈也是线程私有的,他的声明周期与线程相同。每个方法被执行的时候,java虚拟机都会同步创建一个栈帧。用于存储局部变量表,操作数栈,动态连接,方法出口等信息。每个方法被调用直至执行完毕的过程。对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表存放了java中的8种基本类型(boolean、int、long、short、float、byte、char、double)、对象引用(reference类型,他并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他对象相关的位置)、returnAddress类型(指向一条字节码指令地址)。

这些数据类型在局部变量表中的存储空间以局部变量槽(Solt)来表示。其中64位长度的long和double类型会占用两个Solt。其他数据类型只会占用一个。局部变量所需要的空间在编译期间完成分配。当进入一个方法时,这个方法在栈帧中需要分配多大的空间是完全确定的。在方法运行期间不会该表局部变量表的大小(Solt的数量)。

根据虚拟机规范,如果线程请求的深度大于虚拟机所运行的深度将会抛出 StackOverflowError异常,如果java虚拟机栈可以动态扩展,当栈扩展到无法申请足够的内存会抛出OutOfMemoryError异常。

HotSpot虚拟机的栈容量是不可以动态扩容的。Classic 虚拟机可以(书上说的),所以在HotSpot上不会出现因为虚拟机栈无法申请内存而抛出OutOfMemoryError异常(只要线程申请内存成功了就不会有OOM,申请失败时仍然会出现OOM)。

4.本地方法栈

本法方法栈与虚拟机栈所发挥的作用的差不多的,区别是虚拟机栈只是为虚拟机执行字节码(java方法)服务,本法方法栈是为虚拟机使用到本地方法服务。

与虚拟机栈同样如果栈深度溢出或者栈扩容失败时分别抛出StackOverflowError和OutOfMemoryError异常

5.Java堆

java堆是jvm内存管理中最大的一块,是被所有线程共享的一块区域。在虚拟机启动时创建。此内存区域唯一的目的就是存放对象实例。

在《jvm虚拟机规范》中对堆的描述是:”所有的对象实例以及数组都应当在堆上面分配”

随着Java语言的发展,现在已经能看到些许迹象表明日后可能出现值类型的支持,即使只考虑现在,由于即时编译技术的进步,尤其是逃逸分析技术的日渐强大,栈上分配、标量替换优化手段已经导致一些微妙的变化悄然发生,所以说Java对象实例都分配在堆上也渐渐变得不是那么绝对了。

Java堆是垃圾收集器管理的区域。因为他在一些资料中被称为GC堆。从内存回收的角度来看,由于现代垃圾收集器大部分都是基于分代收集器理论设计的。所以在堆中经常会出现(老年代、新生代、永久代、Eden空间、From Survivor空间、To Survivor空间)等名词。

如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

6.方法区

与Java堆一样,是线程共享的内存区域。它用于存储已被虚拟机加载的类型信息、常量、静态变量、字节码。

虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区分开来。说到方法区,不得不提一下“永久代”这个概念,尤其是在JDK 8以前,许多Java程序员都习惯在HotSpot虚拟机上开发、部署程序,很多人都更愿意把方法区称呼为“永久代”(PermanentGeneration),或将两者混为一谈。本质上这两者并不是等价的,因为仅仅是当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区而已,这样使得HotSpot的垃圾收集器能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作。但是对于其他虚拟机实现,譬如BEA JRockit、IBM J9等来说,是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一。但现在回头来看,当年使用永久代来实现方法区的决定并不是一个好主意,这种设计导致了Java应用更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小,而J9和JRockit只要没有触碰进程可用内存的上限,例如32位系统中的4GB限制,就不会出问题),而且有极少数方法(例如String::intern())会因永久代的原因而导致不同虚拟机下有不同的表现。当Oracle收购BEA获得了JRockit的所有权后,准备把JRockit中的优秀功能,譬如Java Mission Control管理工具,移植到HotSpot虚拟机时,但因为两者对方法区实现的差异而面临诸多困难。考虑到HotSpot未来的发展,在JDK 6的时候HotSpot开发团队就有放弃永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划了[1],到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出,而到了JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta-space)来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。 —-以上内容摘选至(《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明》)

根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。

    原文作者:lyp-make
    原文地址: https://www.cnblogs.com/lyp-make/p/12788704.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。

相关文章