死磕P7: JVM内存划分必知必会(二)

这是「死磕P7」系列第 002 篇文章,欢迎大家来跟我一起 死磕 100 天,争取在 2025 年来临之际,给自己一个交代。

接上篇:「死磕P7: JVM内存划分必知必会(一)

今天我们继续来探究 JVM 内存划分,第一篇主要搞清楚 JVM 的内存划分都有哪些,本篇主要介绍各个区域到底是干嘛的,也是一篇死记硬背的内容。

来巩固一下昨天的内存区域划分图,以后就以 1.8 为准吧,毕竟占有率高,1.8 中JVM 内存区域划分为:方法区(元空间),堆,程序计数器,虚拟机栈,本地方法栈。

整理了一份思维导图帮助您快速记忆(不过看完也会忘,多看几遍,自己按自己的理解画出来会好点):

下面来具体罗列一下各区域的作用,内容比较多,随便拎出来一项都能深入挖掘不少东西,说真的,今天一天时间净在那研究虚拟机栈了,其实发现完全不需要研究太深,研究的深反而考不高分,毕竟80% 的题目都不需要那么深的造诣,把时间花在刀刃上,赶紧抓后面更重要的才对。

就好像考试做题一样,肯定先易后难,后面有空再探究难的题目会更好,先用简单的练练自信心,说不定后面的做完,前面的难题就迎刃而解了也有可能。

记不住没关系,哪可能一遍就记住啊,必须得来回看,来回边用边记,光靠背是分分钟忘记的,你也可以尝试自己写下来,画一画,左右脑并用。

线程共享与独享

JVM 区域划分分为了线程共享和线程独享两大类,方法区(元空间,以后统称为方法区吧)和堆是线程共享的,也就相当于一个大水缸,大家(线程)都来大水缸里取水喝。

虚拟机栈,本地方法栈和程序计数器是线程独享的,相当于大家(线程)自己的水杯,自己喝自己的。

程序计数器

程序计数器(Program Counter Register)是JVM中一块较小的内存区域,可以看做当前线程所执行的字节码的行号指示器(其实就是记录代码执行到了哪里)。

因为一个处理器(CPU)在同一个时刻只会执行一个线程的指令,但一个线程中有多个指令(可以理解为多行代码),为了在线程切换时可以恢复到正确的执行位置,会为每个线程设置一个独立的程序计数器,等到下次轮到这个线程执行的时候就接着上次执行的位置继续执行。

补充一点线程切换的概念,因为CPU只负责计算,并且计算速度很快,但是程序要干的事情可能不仅仅需要计算,可能还有读写文件(IO操作),这部分是不需要计算的,CPU会闲着,这时操作系统会将CPU计算资源分配给其他线程进行使用,就是CPU是这个线程使用一会,那个线程使用一会,这就叫线程切换。

记住一点就行:程序计数器是记录每个线程字节码执行位置的,并且是线程独享的且不会发生OOM等异常的区域。

额外一点:如果执行的是 Native 方法,程序计数器为空。

虚拟机栈

当启动一个新线程的时候,Java虚拟机都会为它分配一个Java虚拟机栈。

一个程序至少会有一个线程,大多数情况下可能是有一个主线程,我们以物业公司的物业经理做类比吧,当业主需要服务的时候,物业经理可以安排保安或保洁人员去干活,这个保安保洁人员就可以称为一个子线程,多个保安保洁人员也就是多个子线程了呗,统一听从物业经理的安排。

每个线程会干一个或多个活,对应多个方法,线程启动就会创建对应的虚拟机栈,栈是一种数据结构,具有先进后出(后进先出)的特点,好比你往桶里放衣服(哈哈,谁把衣服放桶里啊),最后放进去的先被拿出来,我说我怎么一个夏天总是穿两件衣服呢,哈哈,洗完入栈,穿的时候出栈(后进先出),下面的衣服轮不到穿啊!

Java 虚拟机栈处理的对象是方法,每个方法在执行的时候,虚拟机栈都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。

每个方法从被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

我们以打扫房屋为例,看看是怎么入栈出栈的:我这个屋子是一室一厨一主卧带独立卫生间的。

// 打扫房屋
public void cleanHouse() {
    // 打扫厨房
    cleanKitchen()
    // 打扫卧室
    cleanBedRoom()
}

// 打扫厨房
public void cleanKitchen() {

}

// 打扫卧室
public void cleanBedRoom() {
    // 打扫套卫
    cleanBashRoom()
}

// 打扫套卫
public void cleanBashRoom() {

}

// main 方法
public void main() {
    // 打扫房屋
    cleanHouse()
}

上面的伪代码涉及 5 个方法,还有方法的嵌套,算是比较常见的一个线程任务了,线程从 main 方法开始执行

  • main 方法入栈,main 方法中调用 cleanHouse 方法

  • cleanHouse 方法入栈,cleanHouse 方法调用 cleanKitchen 方法

  • cleanKitchen 方法入栈

  • cleanKitchen 执行完毕,出栈,cleanHouse 方法继续执行 cleanBedRoom 方法

  • cleanBedRoom 方法入栈,cleanBedRoom 方法调用 cleanBashRoom 方法

  • cleanBashRoom 方法入栈

  • cleanBashRoom 执行完毕,出栈,回到 cleanBedRoom

  • cleanBedRoom 执行完毕,出栈,回到 cleanHouse

  • cleanHouse 执行完毕,出栈,回到 main

  • main 执行完毕,出栈

  • 程序执行完毕

示意图如下:

虚拟机栈会为每一个方法创建一个栈帧,栈帧即对应上图中栈中的一个个方块,如 cleanBashRoom 方块,栈帧里又包含 局部变量表,操作数栈,动态链接,返回地址,方法出口等等。

好了,兄弟,别再深入了,什么是局部变量表?什么是操作数栈?什么是动态链接?都别管了,先跳过去吧!我看了一天了,也没敢往文章里写,有点浪费时间,还打击自信心。

方法执行完成后,方法就会出栈,方法退出有 2 种:

1,正常的退出,不报异常时的退出

2,异常的退出,出现异常了还没有捕获处理

虚拟机栈中会遇到 2 种异常:StackOverflowError, OutOfMemoryError

1,StackOverflowError,采用固定大小的虚拟机栈,如果线程请求的栈太深虚拟机将会抛出一个StackOverflowError 异常,比如无限递归调用

2,OutOfMemoryError,如果虚拟机栈可以动态扩展,并且在扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutOfMemoryError 异常

本地方法栈

这个可以跳过,跟虚拟机栈基本一样,区别是调用的是本地方法,在 HotSpot VM 中合并到了虚拟机栈。

也别关心什么是本地方法了,99% 的情况下用不到,先略过。

方法区

在 1.8 中好像没啥说的了,只有类信息了,知道现在叫元空间,并且放置到了直接内存(JVM内存之外)就可以了吧。

方法区也会出现 OOM 异常。

JVM 中最重要的就数堆了,堆是 JVM 中最大的一块内存区域,因为几乎大部分的对象实例和数组是在堆中创建,还包括新移入进来的字符串常量池和静态变量

堆也是 垃圾回收(GC)的最主要的区域,后面还会介绍到,这里先知道堆为了垃圾回收的方便,又分成了如下不同的区域即可。

注意,细分的原因是为了 GC 的方便,因为 JVM GC 是采用的分代垃圾回收机制,垃圾回收也是常考点,下一遍介绍垃圾回收吧。

总结

本文主要梳理了 JVM 的内存区域划分及简单介绍了其特点,由于 JVM 内容比较多,对于时间紧任务重的兄弟们来说记住最主要的就行了,先考 80 分再说吧!

感谢大家的阅读,如果有任何异议的地方,欢迎指正,也欢迎大家+v: hyx2011 与我深入交流。

小福利

文末小福利,作为资深囤货达人,购置或转存了上千 T 的各种资源,反正我也学不完,如有需要,可以公号: 新质程序猿 找到我,直接送您,能帮助到大家也算是有所福报吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值