《深入理解Java虚拟机》阅读——JVM运行时内存结构

前言

因为最近在准备春招实习嘛,就想着认真的阅读一下这本书,周志明老师的这系列书真的是经典了,我是在微信读书app上读的(因为实体书太贵了而且等寄过来就没时间了,说到微信读书app真的不得不夸一下,比起qq阅读,微信读书真是好太多了,页面整洁不说,书也很多,不会给你推荐一堆无脑言情小说,我觉得用来阅读一些专业书或者其他书用来提升思想也很不错(微信阅读记得打钱!继续说回《深入理解Java虚拟机》这本书,我读的是第二版,这一版不是最新的,当时Java最新的版本的jdk7,主要是微信读书里第三版只能试读,如果有实体书什么的,还是看最新版更好,jdk8比起jdk7的变化还是蛮多的。
这本书我在大概半年前斗胆读过一次,第一部分Java发展史看了一半就睡着了…当时的水平比现在还要低很多,所以真是不知天高地厚了害,这次在已经在b站看视频学了很多的jvm知识以及有了操作系统的部分理论知识后再读这本书轻松太多了,因为很多地方已经学习过了,就等于是一个查漏补缺的内容了,这里再推荐一个b站上的jvm课程(BV1yE411Z7AP)真讲的挺好的,但是还是建议要读一遍书。
这本书我还没看完,一点点更新吧,第一部分这次我是跳过了,因为很多文科的知识,而且以前也看过一次了,哪怕再多看几遍发展史,作为理科生的我也记不住的害,了解一下就好了叭。

jvm运行时内存结构

jvm运行的时候会管理着程序计数器、虚拟机栈、方法区、本地方法栈、堆这五个区域,如下图所示,粉色的表示是线程私有的区域,绿色的表示线程共享的区域。我们接着一个个说说这些内存区域干嘛用的,要放什么东西进去,每个jdk版本又有哪些区别。
jvm内存结构

程序计数器

我们先从程序计数器说起,因为它是最简单的一个区域了,占的内存也小。
我们都知道Java是一门编译+解释的语言,Java编写的程序会先编译成字节码文件,也就是.class结尾的文件,然后再一个一个的执行字节码指令,那么就需要一个区域来保存目前字节码指令执行到哪了,程序计数器就是干这个的。CPU里不是有个PC计数器嘛,记着下一条要执行的机器指令,pc不就是program counter,也不就是程序计数器。
关于程序计数器为什么是线程私有的,其实是很容易想明白的,一个程序计数器只能记下一次要执行哪一条字节码,每个线程要执行的内容可能不一样,所以一个线程要拥有一个私有的程序计数器才行。

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。

虚拟机栈

虚拟机栈是用来保存方法的信息的,每进入一个方法,就会在虚拟机栈里产生一个栈帧,栈帧中包括了局部变量表、操作数栈、动态链接、方法出口等信息,等方法一执行结束,这个栈帧的内存就会被回收。具体结构看下图。
Java虚拟机栈
栈这个结构是先进后出的,所以先进入的方法就先创建栈帧,且放在Java虚拟机栈的最底下,虚拟机栈最上面的栈帧就是当前活动栈帧,也就是当前在执行的方法。比如下面这段代码:

public class Main {
	public static void main(String[] args) {
		new Main().m1();
	}
	public void m1() {
		int i = 1;
		m2();
	}
	public void m2() {
		System.out.println("hello!");
	}
}

主线程启动之后先创建main()方法的栈帧,进入m1(),就会在主线程的虚拟机栈中创建一个m1的栈帧,然后入栈,然后m1()又进入了m2(),于是再创建一个m2的栈帧,入栈,此时栈顶是m2方法的栈帧,栈底是main方法的栈帧。等m2()运行结束,m2的栈帧被释放,此时栈顶栈帧就是m1的栈帧了。
局部变量表存放了编译器可知的各种基本数据类型,包括int、short、byte、long、boolean、float、double,以及对象引用类型和returnAddress类型。这个returnAddress就是指向了程序计数器的字节码指令的地址,记录的这个方法完成后应该返回到哪条指令。

其余的部分书上暂时未说到,等之后看到了再补充。

另外,因为每个线程可能会执行不同的方法,有不同的执行顺序,所以虚拟机栈也是线程私有的。同时,Java虚拟机规范规定如果线程请求的栈深度大于虚拟机栈所允许的深度,就会抛出StackOverflowError;如果虚拟机栈可以动态扩展,且扩展时无法申请到足够的内存,就会抛出OutOfMemoryError。

本地方法栈

本地方法栈跟虚拟机栈差不多啦,唯一的区别就是虚拟机栈存放的是java方法的信息,而本地方法栈存放的是本地方法的信息。Java虚拟机规范并没有对这个本地方法的语言、使用方式和数据结构做强制规定。也有虚拟机将二者合二为一的。
本地方法栈也会抛StackOverflowError和OutOfMemoryError异常的。

大多数情况下,Java堆是最大的一块内存区域。Java堆里存放的是对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但是随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。
这一块区域也是垃圾回收的主要区域。关于垃圾回收机制相关的知识,写在另一个博客里。
另外,堆是可扩展的,当要扩展时无法申请到足够的内存空间,就会抛出OutOfMemoryError。

方法区

方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
在Java虚拟机规范中,是把方法区放在堆里的,但是大多虚拟机在实现的时候都把方法区单独存放,所以它逻辑上是属于堆的,实现上不一定,因此它有个Non-Heap(非堆)的别名。
这里只说HotSpot虚拟机的实现。jdk1.8之前方法区是被叫做永久代的,jdk1.8的方法区叫做元空间。jdk1.6及1.6之前方法区是被放在jvm里的,而jdk1.7开始,方法区就被放在本地内存了,但是jdk1.8的字符串常量池还是放在堆里的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值