专栏名称: 石杉的架构笔记
专注原创、用心雕琢!十余年BAT一线大厂架构经验倾囊相授
目录
相关文章推荐
白鲸出海  ·  突发!Amazon Appstore宣布停止运营 ·  7 小时前  
广东信息通信业  ·  广东省通信管理局召开2025年非应邀商业电子 ... ·  12 小时前  
广东信息通信业  ·  广东省通信管理局召开2025年非应邀商业电子 ... ·  12 小时前  
阿里开发者  ·  LLM 联网搜索,到底是咋回事? ·  21 小时前  
百度智能云  ·  百度智能云xDeepSeek:单机可部署满血 ... ·  4 天前  
51好读  ›  专栏  ›  石杉的架构笔记

搞 Java 的,不懂 JVM 说得过去吗?

石杉的架构笔记  · 公众号  ·  · 2019-04-23 08:30

正文

公众号后台回复 “ 资料

获取作者独家秘制学习资料


本文转自公众号:狸猫技术窝

作者:原子弹大侠,阿里巴巴高级技术专家


概述


很多人想要到阿里巴巴、美团、京东等互联网大公司去面试,但是现在互联网大厂面试,一般都必定会考核JVM相关的知识和实践经验。


毕竟线上系统写好代码部署之后,每个工程师都必须关注JVM相关的东西,比如OOM、GC等问题.


所以一起来看看JVM的最基本的区域划分以及工作原理,这个基本上是互联网公司面试必问。



区域划分


jvm的区域划分如下所示:



大致就是分为:程序计数器,虚拟机栈,堆,方法区,本地方法栈,这几个部分。


接下来我们从 自己写好的Java代码如何通过JVM来运行 的角度,来分析一下JVM里这些区域是如何支撑我们的Java代码跑起来的。



程序计数器


假设我们有如下的一个类,就是最最基本的一个HelloWorld而已:


public class HelloWorld {

public static void main(String[] args) {

System.out.println("Hello World");

}

}


上面那段代码首先会存在于 “.java” 后缀的文件里,这个文件就是java源代码文件,但是这个文件是面向我们程序员的,计算机看不懂这段代码


所以此时就得通过编译器,把“.java”后缀的源代码文件编译为“.class”后缀的字节码文件。


这个“.class”后缀的字节码文件里,存放的就是对你写出来的代码编译好的字节码了。


这个字节码才是计算器可以理解的一种语言,而不是我们写出来的那一堆代码。


这个字节码看起来大概是下面这样的:



说明一下 :这段字节码并不是完全对照着HelloWorld那个类来写的,就是给一段示例,让大家知道“.java”翻译成的“.class”是大概什么样子。


比如这里的“0: aload_0”这样的东西,就是“ 字节码指令 ”,他对应了一条一条的机器指令,计算机只有读到这种机器码指令,才知道具体应该要干什么。


比如说字节码指令可能会让计算机从内存里读取某个数据,或者把某个数据写入到内存里去。


总之,各种各样的指令,就会指示计算机去干各种各样的事情。


所以现在首先明白一点: 我们写好的Java代码是会被翻译成字节码的,对应各种字节码指令。 那么Java代码通过JVM跑起来的第一件事情就明确了。


接下来,在执行字节码指令时,JVM里的程序计数器就是用来记录每个线程当前执行的字节码指令的位置的,记录当前线程目前执行到了哪一条字节码指令。


因为会有多个线程来并发的执行各种不同的代码,所以 每个线程都有自己的一个程序计数器 ,专门记录当前这个线程目前执行到了哪一条字节码指令了


下图更加清晰的展示出了他们之间的关系。




Java虚拟机栈


Java代码在执行的时候,一定是线程来执行某个方法中的代码,哪怕上面那个最基础的HelloWorld代码,也会有一个main线程来执行main方法里的代码。


在方法里,经常会定义一些方法内的局部变量,比如下面这样,就在方法里定义了一个局部变量“name”。


public void sayHello() {

String name = "hello";

}


所以JVM必须有一块区域来保存每个方法内的局部变量等数据,这个区域就是 Java虚拟机栈


每个线程都会去执行各种方法的代码,方法内还会嵌套调用其他的方法,所以 每个线程都有自己的Java虚拟机栈


如果线程执行了一个方法,那么就会为这个方法调用创建对应的一个 栈帧


栈帧里有这个方法的局部变量表 、操作数栈、动态链接、方法出口等东西,但是这里别的不太好理解,先理解一个局部变量就可以。


举例,比如一个线程调用了上面写的 “sayHello” 方法,那么就会为“sayHello”方法创建一个栈帧,压入线程自己的Java虚拟机栈里面去。


在栈帧的局部变量表里就会有“name”这个局部变量。


下图展示了这个过程。



接着如果“sayHello”方法调用了另外一个“greeting”方法 ,比如下面那样的代码:



这时会给“greeting”方法又创建一个栈帧,压入线程的Java虚拟机栈里,因为开始执行“greeting”方法了。


而且“greeting”方法的栈帧的局部变量表里会有一个“greet”变量,这是“greeting”方法的局部变量。



接着如果“greeting”方法执行完毕了,就会把“greeting”方法对应的栈帧从Java虚拟机栈里给出栈。


然后接下来如果“sayHello”方法也执行完毕了,就会把“sayHello”方法也从Java虚拟机栈里出栈。


这就是JVM中的 “ Java虚拟机栈 ” 这个组件的作用: 调用执行任何方法的时候,都会给方法创建栈帧,然后入栈


而在栈帧里存放了这个方法对应的局部变量之类的数据,包括这个方法执行的其他相关的信息,方法执行完毕之后就出栈。




Java堆内存


JVM中有另外一个非常关键的区域,就是Java堆,这里就是存放我们在代码中创建的各种对象的,比如说下面的代码:


public void sayHello(String name) {

Student student = new Student(name);

student.study();

}


上面的 “new Student(name)” 这个代码,就是创建了一个Student类型的对象实例,这个对象实例里面会包含一些数据。


比如说这个Student的“name”就是属于这个对象实例的数据,类似Student这样的对象,就会存放在Java堆内存里。


Java堆内存区域里会放入类似Student的对象,然后方法的栈帧的局部变量表里,会存放这个引用类型的“student”局部变量,即存放Student对象的地址。


相当于你可以认为局部变量表里的“student”指向了Java堆里的Student对象。


看下图会更加清晰一些。





方法区 / Metaspace


这个方法区是在JDK 1.8以前的版本里,代表JVM中的一块区域.


他主要是放类似Student类自己的信息的,平时用到的各种类的信息,都是放在这个区域里,还会有一些类似常量池的东西放在这个区域里。


但是在JDK 1.8以后,这块区域的名字改了,叫做“ Metaspace ”,可以认为是“ 元数据空间 ”这样的意思,当然他主要还是存放我们自己写的各种类相关的信息。








请到「今天看啥」查看全文