JVM - 进入Java虚拟机的真实世界

JVM - 进入Java虚拟机的真实世界

相信对Java编程有了一定程度了解的同学,多多少少都已经听说过、了解过Java虚拟机。就算你还未开始学习Java编程但已经打算计划去学习,那你也肯定听说过一本书《深入理解Java虚拟机 JVM高级特性与最佳实践 》。在我当时正计划踏入Java这个大家庭的时候,我也提前买了一堆书籍,其中就有它。直到两年后,我对自己技术的不满足并且制定了一系列计划之后,我才重新翻开这本书。过去的我对JVM的了解仅仅只是冰山一角,从这里开始我们一起慢慢去进入Java虚拟机真实的世界。
相信大家学习Java那就一定对Java语言为何保持着优势并且经久不衰十分清楚,其中的一个配件功不可没-JVM,这是塑造了Java代码一处编译、处处运行的根本。如果对其他编程语言,例如C或者C++有了解的同学应该能够很清楚,在C和C++当中对于内存的管理,开发者拥有至高无上的权利,但同时开发者也需要对其中每一对象从创建到销毁都进行维护。
而对于Java开发者来说,虚拟机的自动内存管理机制能够在大部分情况下都帮助我们管理好我们所使用的内存,对于已经十分成熟的虚拟机技术很少会出现内存泄漏以及内存溢出的问题,这对于我们Java开发者来说简直就是一个十分愉悦的事情。但与此同时,这样完善的内存管理机制也是一把利刃。一旦出现内存泄漏或者溢出等问题,若我们对虚拟机真正的管理机制一知半解甚至不清楚,那问题将会十分难以解决甚至是致命的。

1.探索虚拟机的内存区域

 当我们运行一个Java程序的时候,JVM会将Java程序在运行过程中所用到的内存进行管理起来,将其分成若干个不同的数据区域。每个区域都有其独特定义、用途以及特性。这里我们先来看一下程序运行时数据区是如何分配的。

1.1 运行时数据区

 对于运行时数据区的分配,从JDK8开始还进行了一些改变。

JDK1.8之前
在这里插入图片描述
JDK1.8
在这里插入图片描述

 这里我们可以很清晰地看到JDK1.8起彻底将方法区移除并替换成了直接内存中新增加的元空间,另外把运行时常量池放在了堆内分配。
 另外每个数据区分布也不同,主要区别是线程共享以及线程私有。这里我们大概看一下,下面会对每个数据区单独介绍。

  1. 线程共享:堆、方法区
  2. 线程隔离/私有:虚拟机栈、本地方法栈、程序计数器

1.2 程序计数器

1.2.1 程序计数器是什么?

程序计数器实际上是一块较小的内存空间,我们可以把它看作是当前线程所执行的字节码的行号指示器。字节码解释器运行时主要就是通过修改当前线程的程序计数器的值,来依次读取需要执行的字节码指令,我们程序中的分支、循环、跳转、异常处理、线程恢复等基础功能都是依赖程序计数器来完成的。

1.2.2 为什么使用程序计数器?

 我们都知道线程是一个独立的执行单元,由CPU所控制执行的。对操作系统有了解的同学应该听说过时间片,其实在我们应用程序运行过程中每个线程都被会分配一个时间段,也就是CPU分配给各个程序运行的时间。当我们同时运行多个应用程序时,其实本质上是每个线程不停轮流切换去使用CPU的资源,看似是多个应用程序在同时执行,其实本质上若只有一个CPU(内核),则一次只能处理一个时间片中的指令。
 所以为了线程来回切换时能够恢复到上一次正确的执行位置,每个线程都会自己维护一个独立的程序计数器,独立存储且不受其他线程的影响,是一块线程私有的内存空间。

 这里我们根据实际情况来做一个讲解。这里我们先定义一个简单的User类。

public class User{
	
	private Integer age;

	private String name;

	public Integer getAge(){
		return age;
	}

	public String getName(){
		return name;
	}
}

 我们对其进行编译之后,通过javap -l查看编译后的字节码文件。
在这里插入图片描述
 这里可以看到每一个方法上开始都会有对应的行号,这就是我们程序计数器所需要记录的数据。
在这里插入图片描述
 例如我们在时间片1时,CPU将资源分配给了线程1,此时线程1执行到了getAge()方法的位置,CPU将时间片分配给了线程2,此时线程1的程序计数器就会记录当前getAge()所在行。并将时间片切换至线程2。
在这里插入图片描述
 线程2在时间片2内又执行到了getName()的位置,此时线程2的程序计数器也会将getName()的位置记录下来,并切换到时间片3。假设时间片分配是均匀的,则时间片3时线程1恢复执行,这时就需要使用到之前线程1的程序计数器所记录的位置用以恢复到上一次线程1所执行的字节码所在行。多个线程之间就是这样来回切换运行的。
 这里因为线程正在执行的是一个Java方法,所以这个计数器记录的是正在执行的虚拟机字节码指
令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。
 由于程序计数器是JVM默认分配的不由开发者控制,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。所以程序计数器也是唯一一个不会出现OutOfMemoryError的内存区域,

ps:这里还是推荐大家有时间读一读《深入理解Java虚拟机 JVM高级特性与最佳实践 》这本书,虽然与最新的技术有一些差异,但是其中除了对JVM有详细的讲解,还对Java语言以及虚拟机的发展历史有不错的介绍。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章