转载

图解Java:技术体系与运行时数据区

图解Java:技术体系与运行时数据区

2020年初的疫情终于有所好转,这段时间里,啾啾也宅在家中也在持续学习,现在终于按捺不住想要出去谋求一份工作,来赚一些钱买零食。

图解Java:技术体系与运行时数据区

概述

很多初学者都会纠结一个问题,选一门什么语言作为自己的主力语言。虽然议论这个很容易陷入语言战争,但我个人推荐Java。虽然每过几年都会有声音唱衰Java,而且其间存在一种“Java老掉牙了,我们年轻人不能这么不酷,现在还学Java都是老古板”的心态,我认为这种逻辑属实憨憨。

因为这个系列主要是为了讲解JVM(Java虚拟机),那么关于Java的优势在这里我就不再赘述了,当你有兴趣点开这个标题,那么我就默认你认可Java这门语言,并且想要深入学习。

目前市场上很多初级Java工程师,每天都在做一些重复性的增删改差工作,其中一部分人就会产生这样的印象:Java天花板太低,就是CRUD,用用框架、调调API,实在是简单和无聊。

图解Java:技术体系与运行时数据区

Java的生态丰富是它一个极大的优势,它为应用层开发人员省去了大量的基础构建工作。此外,JVM的存在,屏蔽了底层机器和操作系统的差异,这也使得Java看上去“简单”了不少。但是,在日常工作的经验积累之中,我越发觉得JVM是初级开发向着中级开发的一个分水岭,假如你能够更加深入地理解JVM的设计与工作原理,就能写出最适合虚拟机运行和自优化的代码。此外,在进行故障排查时,拥有JVM相关知识能够帮助你快速定位与解决问题。

这些对自己的工作能力以及技术视野,都是一个很大的提升。

图解Java:技术体系与运行时数据区

Java技术体系

图解Java:技术体系与运行时数据区

这张图罗列得非常清楚。首先不要被这一堆名词吓到,并不是其中所有内容都要完全掌握的,我们只需要学习其中的重点。

基本概念我们要牢记,可以看到两个包含关系:JRE包含JavaSE API;JDK包含JRE。JRE是支持Java程序运行的标准环境,JDK是用于支持Java程序开发的最小环境。

众所周知,Java最初流行的原因之一就在于他的自动内存管理,开发人员不再需要手动地分配并且释放内存,这让上层开发人员觉得很舒服,但一切真的这么美好吗?

一个只会在应用层编写CRUD代码的程序员往往觉得自己已经掌握了Java,并能够流畅地使用它来编写一些程序,但是一旦面临内存泄漏和溢出的问题,可能就会束手无策了。如果长期处于这个阶段,那么升职涨薪确实是一件难事。这篇文章将向你介绍Java自动内存管理中Java内存区域。

JVM运行时数据区

这篇文章要讲的重点,就是运行时数据区。

程序在运行时,必然会产生大量的数据,面对这些数据,JVM是如何维护的?JVM划分了若干区域,来管理不同性质的数据,使一切变得更加井井有条。

首先来看这张图(相信大家已经看到过很多次了):

图解Java:技术体系与运行时数据区

在这张图中,我们可以了解如何通过参数来控制各区域的内存大小:

图解Java:技术体系与运行时数据区

图解Java:技术体系与运行时数据区

程序计数器

程序计数器在图中占了比较大的一块区域,看上去空间似乎是“虚拟机栈”和“本地方法栈”的空间之和?千万不要这么去理解,这只是绘图的问题,事实上程序计数器只占用较小的一块内存空间。

它主要用来指示当前线程执行字节码的行号,通过改变程序计数器指向的值,来不断读取下一条字节码指令。

可以这么理解,程序计数器主要负责程序控制流。因为一个程序计数器控制一条线程的执行状态,所以当多条线程在并发或并行时,每条线程都会拥有自己的程序计数器,我们把这种性质叫做“线程私有”,图中用白色区域来标示“线程隔离的数据区”,是一个意思。

程序计数器不会抛出OutOfMemory Error,这是因为程序运行过程中计数器中改变的只是值,而不会随着程序的运行需要更大的空间,也就不会发生溢出情况

图解Java:技术体系与运行时数据区

引申:CPU内部的寄存器中就包含一个指令计数器(Program Counter,PC),存放程序执行的下一条指令地址。在程序开始执行前,将程序指令序列的起始地址,即程序的第一条指令所在的内存单元地址送入PC,CPU按照PC的地址从内存中读取第一条指令。每一条指令执行时,CPU会自动修改PC的量至下一条指令的地址,指令之间的跳转离不开PC。JVM内存中的程序计数器也是一样的作用,它储存JVM当前执行字节码的地址。

虚拟机栈

继续来看线程隔离的数据区。当一个线程在执行Java代码,每个方法将要被执行时,都会生成一个栈帧,每个栈帧中存放着局部变量表、操作数栈、动态链接、方法出口等信息。每个方法被真正调用的时候,表现为该栈帧入栈与出栈,而存放这些栈帧的内存空间,就是虚拟机栈。

图解Java:技术体系与运行时数据区

每一个虚拟机栈与当前线程的生命周期一致,所以是“线程私有”的。

需要注意的是,上文提到了虚拟机栈中将存放局部变量表,局部变量表中只能存放编译期可知的8种Java基本数据类型、对象引用(指向对象本身的起始地址)以及返回类型(指向下一条字节码指令的地址)由于局部变量表在编译期就完全确定,所以它的大小是固定的,不会在运行时进行调整。

虚拟机栈可能抛出两种异常情况,第一种是著名的StackOverFlow Error,如果栈深度大于JVM允许的深度,将会抛出此异常,比如在一些递归调用中,假如跳出条件控制得不好,很容易发生此异常。

第二种还是著名的OutOfMemory Error,假如虚拟机栈容量可以动态扩展(取决于虚拟机类型),当虚拟机栈无法动态获取到更多的内存空间,将会抛出此异常。假如虚拟机栈不允许动态扩展(比如HotSpot虚拟机),当虚拟机栈在申请空间时就失败,也会抛出OutOfMemory Error。

本地方法栈

本地方法栈与虚拟机栈的作用是一样的,唯一的区别在于虚拟机栈用于存放Java方法的栈帧,而本地方法栈存放的是本地方法(Native Method)的栈帧。什么是Native Method?简单来说就是一个接口,用于在Java程序中调用非Java方法。

本地方法的作用主要为以下三点:

* 为了使用底层机器的某个特性,而这个特性Java API并不提供。

* 为了调用一个老的系统或者库,这个库不是用Java编写的。

* 为了增强程序的性能,将一段时间敏感的代码通过本地方法实现。

图解Java:技术体系与运行时数据区

Java堆是虚拟机是运行时数据区中所占内存空间最大的一个部分。可以看到,堆属于所有线程共享的数据区,主要用来存放Java对象,几乎所有的对象实例以及数组都会在堆中分配空间。之所以说“几乎”是因为随着Java的发展,这一点已经渐渐不再那么绝对了。

堆被垃圾回收器管理着,因为大部分垃圾回收器都是按照分代的思想设计,所以从逻辑上,堆又被分为多个区域。但需要注意的是,这是从垃圾回收器的角度而言的,并非堆本身一定要被分代。(这部分会在后面垃圾回收的章节中详细讲解)

堆的空间大小多数情况下都会被设定成可拓展的,一旦没有空间来进行分配,将会抛出OutOfMemory Error。

方法区

方法区容易和虚拟机栈混淆,因为方法区并不是存储“方法”,而虚拟机栈是存储和方法有关的栈帧的。

方法区主要用来存储已被JVM加载的类型信息、常量、静态变量、JIT编译后的缓存数据等。HotSpot初期设计团队为了简便,以设计Java堆永久代的方式来设计方法区,这样能够使得垃圾回收器能够像面对永久代一样回收垃圾。但是这并不等同于方法区就等价于永久代,事实上这是两个不在同一层面的概念。后来的经验证明,HotSpot初期这样的设计并不完善。在JDK6之后,HotSpot团队放弃永久代,采用本地内存来重构了方法区。

方法区由于存储的多为一些静态数据,所以在方法区上进行垃圾回收效果是比较差的,但有时候又是必要的,如果方法区无法进行新的内存分配,将会抛出OutOfMemory Error。

图解Java:技术体系与运行时数据区

-END-

这是我准备在2020年持续更新的一个系列

致力于用一种新方式讲解编程

难度会逐渐上升

希望得到你的 关注 转发

我和啾啾感激不尽:pray:

图解Java:技术体系与运行时数据区

原文  http://mp.weixin.qq.com/s?__biz=Mzg2ODAyNTgyMQ==&mid=2247484233&idx=1&sn=4d8b3df93e347baa14bbe3717e0b2d2d
正文到此结束
Loading...