JVM,我要把你 “开膛破肚” !

公众号:狸猫技术窝

做者:原子弹大侠,高级技术专家java

目录

1. 打怪升级,你绕不开JVM 程序员

2. JVM 区域划分 面试

3. 程序计数器 性能优化

4. Java虚拟机栈 bash

5. Java堆内存 网络

6. 方法区 / Metaspace 架构

7. 本地方法栈 并发

8. 堆外内存 jvm

9. 全文总结 高并发


1. 打怪升级,你绕不开JVM

JVM,对Java程序员进阶而言,是一个绝对绕不开,也不能绕开的话题。

在你打怪升级、进阶蜕变的路上,势必会遇到项目上线中各类OOM、GC等问题,此时JVM的功底就相当重要了。

这篇文章,咱们将从本身写的代码运行角度出发,将JVM“开膛破肚”。看看咱们写的代码,在JVM的各区域都干了些啥?

多说一句,对于Java工程师的面试,JVM也是必问的一环,所以不管从面试仍是实际工做,你都颇有必要夯实本身的JVM功底。

扯得有点远,赶忙拉回来,立刻进入正题!


2. JVM 区域划分

jvm的区域,大体有如下几块:

  • 程序计数器

  • 虚拟机栈

  • 方法区

  • 本地方法栈

接下来咱们将JVM当成一个生物体,上述部分就是其不一样器官。咱们将从本身写的Java代码如何经过JVM来运行这一角度,来分析JVM里这些“器官”是如何支撑咱们的Java代码跑起来的。


3. 程序计数器

假设咱们有以下的一个类,就是最最基本的一个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代码是会被翻译成字节码的,不一样字节码指令指挥计算机干不一样的事情。

那么在执行字节码指令的时候,JVM里的程序计数器做用是啥呢?

答案是:用来记录每一个线程当前执行的字节码指令的位置,即记录当前线程目前执行到了哪一条字节码指令。

在实际中,会有多个线程并发执行各类不一样的代码,因此每一个线程都有本身的程序计数器,专门记录当前线程目前执行到了哪一条字节码指令。

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


4. Java虚拟机栈

好,咱们接着来看。你们都清楚,Java代码执行时,必定是线程来执行某个方法中的代码。就算是最基础的 HelloWorld ,也会有一个main线程来执行main方法里的代码。

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

public void sayHello() {
        String name = "hello";
}复制代码

因此我们JVM的这个“器官”就要出场了,JVM必须有一块区域是来保存每一个方法内的局部变量等等数据的,这个区域就是Java虚拟机栈

为何须要这个区域?由于每一个线程都会去执行各类方法的代码,方法内还会嵌套调用其余的方法,因此每一个线程都要有本身的Java虚拟机栈

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

栈帧里就有这个方法的局部变量表 、操做数栈、动态连接、方法出口等东西。这里别的东西不太好理解,后面咱们再经过其余文章详细阐述,这里先理解一个局部变量就能够。

回到上面的例子,好比一个线程调用了上面写的“sayHello”方法,那么就会为“sayHello”方法建立一个栈帧,压入线程本身的Java虚拟机栈里面去。

在栈帧的局部变量表里就会有“name”这个局部变量,下图展现了这个过程。

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


这时会给“greeting”方法又建立一个栈帧,压入线程的Java虚拟机栈。

想一想为啥会这样?由于sayHello方法里开始执行greeting方法了,并且greeting方法的栈帧的局部变量表里有一个“greet”变量,它是greeting方法的局部变量。

下图展现了这个过程:

接着若是“greeting”方法执行完毕了,就会把“greeting”方法对应的栈帧从Java虚拟机栈里给出栈,而后若是“sayHello”方法也执行完毕了,就会把“sayHello”方法也从Java虚拟机栈里出栈。

这就是JVM中的Java虚拟机栈这个组件的做用。

这块你们须要记住的是:调用执行任何方法时,都会给方法建立栈帧,而后入栈。

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


5. Java堆内存

JVM中有另一个很是关键的区域,就是Java堆,用来存放咱们在代码中建立的各类对象的,好比下面的代码:

public void teach(String name) {
    Student student = new Student(name);
    student.study();
}复制代码

上面的 “new Student(name)” 就建立了一个Student类型的对象实例,这个对象实例里面会包含一些数据。相似Student这样的对象,就会存放在Java堆内存里。

而后方法的栈帧的局部变量表里,这个引用类型的“student”局部变量就会存放Student对象的地址。你能够认为局部变量表里的“student”指向了Java堆里的Student对象。

下图展现了这个过程:


6. 方法区 / Metaspace

这个方法区是在JDK 1.8之前的版本里,表明JVM中的一块区域,主要是放相似Student类本身的信息的,平时用到的各类类的信息,都是放在这个区域里的,还会有一些相似常量池的东西放在这个区域里。

可是在JDK 1.8之后,这块区域的名字改了,叫作“Metaspace”,能够认为是“元数据空间”这样的意思,固然主要仍是存放咱们本身写的各类类相关的信息。

7. 本地方法栈

在JDK不少底层API里,好比IO相关的,NIO相关的,网络Socket相关的,若是你们去看他内部的源码,会发现不少地方都不是Java代码。

不少地方都会去走native方法,去调用本地操做系统里面的一些方法,可能调用的都是c语言写的方法,或者一些底层类库,好比下面这样的:

public native int hashCode();

在调用这种native方法的时候,就会有线程对应的本地方法栈,这个里面也是跟Java虚拟机栈相似的,也是存放各类native方法的局部变量表之类的信息。

关于这块,这里就不展开讲了,后续有机会咱们再写文章专门阐述。


8. 堆外内存

还有一个区域,不属于JVM,经过NIO中的allocateDirect这种API,能够在Java堆外分配内存空间,而后经过Java虚拟机里的 DirectByteBuffer 来引用和操做堆外内存空间。

不少技术都会用这种方式,由于有一些场景下,堆外内存分配能够提高性能。

9. 全文总结

最后作一点总结:

  • Java代码经过JVM运行时,首先必定会一行一行执行编译好的字节码指令

  • 而后在执行的过程当中,对于方法的调用,会经过Java虚拟机栈来为每一个方法建立栈帧,入栈和出栈,并且栈帧里有方法的局部变量。

  • 对于对象的建立,会分配到Java堆内存里去

  • 对于类信息的存储,会放在方法区 / Metaspace这样的区域里

  • 另外有两块特殊的区域:

    • 本地方法栈:执行native方法时候用的栈,跟Java虚拟机栈是相似的

    • 堆外内存:能够在Java堆外分配内存空间来存储一些对象。


做者简介:

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

经历过每日百亿流量的互联网系统架构,尤为对上亿用户场景下的高并发系统架构设计以及性能优化相关领域有深刻的研究。

END


长按下图二维码,即刻关注【狸猫技术窝】 阿里、京东、美团、字节跳动 顶尖技术专家坐镇 为IT人打造一个 “有温度” 的技术窝!

相关文章
相关标签/搜索