JVM I——JVM理解及内存区域划分

一、JVM介绍

JVM 即:java 虚拟机。

JVM最早的设计理念是:一次编写,多次运行。即,当时是为了让一份代码能够兼容多个操作系统的初衷。比如一份业务在不同的操作系统上有不同的代码(也就是一份相同的逻辑需要写多份),而Java为了解决这个问题就设计出了JVM来能够兼容多个操作系统的代码来执行。

但是随着技术的不断发展,现在很少将Windows的程序放在Linux上,所以Java还是保留了这个特性,不算优点也不算缺点。

JVM与其他的虚拟机有什么不同呢?

  • jvm:只虚拟了部分功能,相当于只为Java系列语言定制的虚拟机
  • VMvave、Virtual Box:虚拟出来是一台真正的机器

随着jvm的发展现在好多语言都可以在jvm中运行,比如PHP、phython等

二、理解JVM与操作系统的关系

1、JVM内部划分

从JVM与操作系统的工作角度,我们可以将JVM看作这几个部分:类加载器、运行时内存、动态内存管理器、执行引擎。它们各司其职都有自己不可替代的功能:

  • 类加载器: 负责从硬盘中查找类文件,然后将文件加载到运行时内存分配的空间中

  • 运行时内存: Java程序再需要内存的时候jvm就会在运行时内存中发分配一片空间

  • 动态内存管理器: 主要负责在运行时内存中为程序动态的分配/回收空间

  • 执行引擎: 当类加载器将类文件加载到内存中,执行引擎就使用字节码文件(Java中的方法)执行或操作一些数据,在执行的过程中如果需要申请内存就通过动态内存管理器来申请内存,最终将执行结果翻译成操作系统的API在真实的操作系统上运行

运行时内存是如何申请创建的呢?

一开始jvm相当于启动了一个进程来调度操作系统申请分配一份内存,申请到的这片内存就叫运行时内存;

2、JVM、操作系统、硬件的关系

在计算机中JVM、操作系统和硬件之间有着密不可分的关系。

  • JVM相当于扮演者对程序以及数据处理的中转站;
  • 操作系统来获取JVM处理好的结果通过API的形式对硬件进行读写操作;
  • 硬件基本就是用来存储数据的。

三者之间的关系见下图所示:
在这里插入图片描述

3、JVM、操作系统、硬件执行流程

我们根据上图来看一下他们之间的工作流程:

  • 首先,类加载器会将类文件加载到类加载器中
  • 类加载器再将类文件加载到运行时内存中;运行时内存又分为PC区、栈区、方法区等空间,具体后面介绍。关于类加载的相关内容见后续博客。
  • 此时,执行引擎就使用字节码文件(Java中的方法)执行或操作一些数据
  • 在执行引擎执行的过程中如果需要申请内存就通过动态内存管理器来申请内存
  • 执行引擎执行完之后,最终会将执行结果翻译成操作系统的API在真实的操作系统上运行。
  • 最后操作系统再通过API将数据读写到底层硬件上。

三、JVM运行时内存

从上面的介绍中,我们知道JVM主要分为这四个区域:运行时内存、类加载器、执行引擎,动态内存管理器;他们都是JVM中不可或缺的角色。

对于Java中数据的存储主要是在运行时内存区,而运行时内存又分为栈区(Java虚拟机栈和本地方法栈)、程序计数器(PC)、堆区、方法区、内存常量池区。

在jdk1.8中,jdk1.7的方法区就是jdk1.8的元数据区。

jdk1.8运行内存分布:
在这里插入图片描述
在jdk1.8的内存区域划分中,主要可以分为两大类:

  • 线程私有区域:程序计数器、Java虚拟机栈、本地方法栈
  • 线程共享区域:Java堆、方法区(即:元数据区)、运行时常量池

—开始只有一个Java主线程,只会为主线程提供PC区和栈区;直到Java的main线程调用其他方法方法开辟子线程才会提供子线程的PC区和栈区。

接下来我们就这两大分类来介绍内存中的各个区域。

1、线程私有区域

什么是线程私有?

由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们就把类似这类区域称之为"线程私有"的内存。

1)程序计数器

程序计数器: 标记该线程下一次代码行执行的位置;程序打断点也会依赖程序计数器。

  • 程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
  • 如果当前线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器值为空。

程序计数器中的值: 在方法执行的时候PC区的值一直在变化,因为PC保存的是下一次代码执行的位置,所以一直在变。

程序计数器内存大小: 虽然PC区的值一直在变,但是内存大小一直是那么大保持不变。

  • 程序计数器中的值是根据程序的执行进行变化的,但是大小是不变的,所以不会出现OOM情况(out of memoryerr:内存溢出异常)
  • 程序计数器内存区域是唯一一个在JVM规范中没有规定任何OOM情况的区域!

2)Java虚拟机栈

java虚拟机栈: 也就是我们常说的JVM中的栈,在每一个线程执行时,都对应有一个虚拟机栈,生命周期与线程相同。

  • 和线程相关:不同线程内,即使运行同一个方法,也是处于不同内存。
  • 和方法有关:即使是同一个线程,递归调用某个方法,每次调用都会生成该次方法调用的方法栈帧。
    在这里插入图片描述

java虚拟机栈方法的执行:

每个java方法被调用时都会创建一个栈帧,然后入栈,方法结束后出栈。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。栈区在方法执行的过程中会一直生成栈帧,某个方法执行结束就会销毁该方法的栈帧

  • 局部变量表: 方法进入的时候一些相关的方法信息,就由局部变量表保存一些相关的局部变量信息
  • 操作数栈: 操作了多少次数
  • 动态链接: 表示程序根据哪个方法连接过来的以及连接到哪个地方去的,也就是标识引用连接的地方
  • 方法出口: 有一个返回类型,返回到哪个地方,哪个地方去接收这些信息记录在方法出口

之前我们一直说的栈区域实际上就是此处的虚拟机栈,再详细一点,是虚拟机栈中的局部变量表部分

此区域一共会产生以下两种异常:

  1. 如果线程请求的栈深度大于虚拟机所允许的深度(-Xss设置栈容量),将会抛StackOverFlowError异常。
  2. 虚拟机在动态扩展时无法申请到足够的内存,会抛出OOM(Out0fMemoryError异常)

关于这两种异常将会在后续博客写出。

3)本地方法栈

本地方法栈与虚拟机栈的作用完全一样

本地方法栈与虚拟机栈的区别是:

  • 本地方法栈为虚拟机使用的Native方法服务,
  • 虚拟机栈为JVM执行的Java方法服务。

2、线程共享内存区域

线程共享内存区域:Java堆、方法区、运行时常量池

1)Java堆

我们在Java开发中离不开创建对象,而我们平时创建的示例对象和数组类型就是存储在Java堆中。

Java堆的作用:

  • Java堆用于存储对象实例 以及数组类型,在JVM启动时创建,所有的对象实例以及数组都要在堆上分配。
  • JVM规范中说到:“所有的对象实例以及数组都要在堆上分配”。
  • 如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM(Out of memoryError)。

Java堆在内存中的角色:

  • Java堆(Java Heap)是JVM所管理的最大内存区域(JVM百分之八九十的空间都是堆占有的)。
  • Java堆是垃圾回收器管理的主要区域,因此很多时候可以称之为"GC堆"。-------->垃圾回收角度
  • 根据JVM规范规定的内容,Java堆可以处于物理上不连续的内存空间中。Java堆在主流的虚拟机中都是可扩展的(-Xmx设置最大值,-Xms设置最小值)。
java 堆溢出

java 堆溢出:

1> 出现OOM的原因:

Java堆用于存储对象实例,只要不断的创建对象,并且保证GCRoots到对象之间有可达路径来避免来GC清除这些对象,那么在对象数量达到最大堆容量后就会产生内存溢出异常。关于垃圾回收的内容见博客:link.

简单的理解:Java堆就是存放实例对象以及数组的仓库,如果一直创建对象并且也不清理没用的对象,当对象的数量达到了Java堆内存的上限,对象就存不下了,这时候就会抛出OOM异常。

Java堆内存的OOM异常是实际应用中最常见的内存溢出情况。当出现ava堆内存溢出时,异常堆栈信息"java.lang.OutOfMemoryError"会进一步提示"Java heap space"。 当出现"Java heap space"则很明确的告知我们,OOM发生在堆上。

2> 设置Java堆内存参数:

我们在上面提到了Java堆可以通过参数设置它内存的最大/小值。现在我们就来看看如何设置。

在cmd窗口中输入: Java -X + 参数来指定JVM有关参数

在这里插入图片描述
上图中,我们输入Java -X 之后可以看到JVM设置有关参数的选项。

例如:java -Xms20m Main:设置Java堆初始大小为20M并运行Main程序。 见下图:

在这里插入图片描述

2)方法区

在JDK1.7的时候,有一个JVM内存区域中有一块方法区,主要存放虚拟机加载的类信息,静态变量,常量等。在JDK1.8中将方法区叫做元数据区。元数据区存放的东西和方法区相同,不过元数据区移动到本地内存中。

本地内存,又称堆外内存(Direct Memory),就是指机器内存中不是JVM管理的那部分内存,由操作系统管理。
在这里插入图片描述

  • 方法区与ava堆一样,是各个线程共享的内存区域。它用于存储已被虚拟机加载的类信息(主要是用来存储类信息的,主要是类中的方法)、常量、静态变量、即时编译器编译后的代码等数据
  • 在JDK8以前的HotSpot虚拟机中,方法区也被称为"永久代"(JDK8已经被元空间取代)。永久代并不意味着数据进入方法区就永久存在,此区域的内存回收主要是针对常量池的回收以及对类型的卸载。
  • JVM规范规定 :当方法区无法满足内存分配需求时,将抛出OOM异常。

3)运行时常量池

运行时常量池是方法区的一部分,在jdk1.8的时候随同方法区挪到了本地内存中。 如下图所示:

在这里插入图片描述

运行时常量池中存放字面量符号引用

  • 字面量: 字符串(JDK1.7后移动到堆中)、final常量、基本数据类型的值
  • 符号引用: 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符。

总结:

运行时内存中的数据主要有两个来源:

  1. 类文件加载进来的数据(放在方法区、常量池区为主)
  2. 运行期间产生的数据(动态)(栈+堆为主)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值