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

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

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

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

多说一句,对于Java工程师的面试,JVM也是必问的一环,因此无论从面试还是实际工作,你都很有必要夯实自己的JVM功底。

扯得有点远,赶紧拉回来,马上进入正题!

2. JVM 区域划分

原文:https://zhuanlan.zhihu.com/p/67513763

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堆外分配内存空间来存储一些对象。

深扒JVM,对它进行“开膛破肚”式解析!的更多相关文章

  1. 从X86指令深扒JVM的位移操作

    概述 之所以会写这个,主要是因为最近做的一个项目碰到了一个移位的问题,因为位移操作溢出导致结果不准确,本来可以点到为止,问题也能很快解决,但是不痛不痒的感觉着实让人不爽,于是深扒了下个中细节,直到看到 ...

  2. 一文带你深扒ClassLoader内核,揭开它的神秘面纱!

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」. 如果觉得 「不错」 的朋友,欢迎 「关注 + 留言 + 分享」,文末有完整的获取链接,您的支持是我前进的最大的动力! 前言 Clas ...

  3. 扒一扒JVM的垃圾回收机制,下次面试你准备好了吗

      相信和小编一样的程序猿们在日常工作或面试当中经常会遇到JVM的垃圾回收问题,有没有在夜深人静的时候详细捋一捋JVM垃圾回收机制中的知识点呢?没时间捋也没关系,因为小编接下来会给你捋一捋. 一. 技 ...

  4. 深挖Jvm垃圾收集

    垃圾收集(Garbage Collection,GC),它的任务是解决以下 3 件问题: 哪些内存需要回收? 什么时候回收? 如何回收? 其中第一个问题很好回答,在 Java 中,GC 主要发生在 J ...

  5. 深扒那些艺术的CSS

    概览 使用单个div做css绘图,会充分利用到: before.after伪元素 使用border-radius.border来控制图形的形状. 使用叠加的box-shadow来创建多个相同的形状(可 ...

  6. 痞子衡嵌入式:深扒i.MXRTxxx系列ROM中集成的串行NOR Flash启动SW Reset功能及其应用场合

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRTxxx系列ROM中集成的串行NOR Flash启动SW Reset功能及其应用场合. 在串行 NOR Flash 热启动过程 ...

  7. 痞子衡嵌入式:深扒IAR启动函数流程及其__low_level_init设计对函数重定向的影响

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR启动函数流程及其__low_level_init设计对函数重定向的影响. 上一篇文章 <IAR下RT-Thread工程自定义 ...

  8. 痞子衡嵌入式:深扒IAR启动函数流程之段初始化函数__iar_data_init3实现

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR启动函数流程里的段初始化函数__iar_data_init3实现. 本篇是 <IAR启动函数流程及其__low_level_ ...

  9. 痞子衡嵌入式:深扒IAR启动函数流程之段初始化实现中可用的压缩选项

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是IAR启动函数流程里段初始化实现中可用的压缩选项. 接着 <IAR启动函数流程之段初始化函数__iar_data_init3实现& ...

随机推荐

  1. Python Day_2

    入门任何一门编程语言,前面总是离不开变量,字符串这些概念,而且这些东西在往后的日子里,有着至关重要的存在.因为不管我们写什么程序,都要用到变量以及字符串. 变量 首先,我们的变量在定义的时候,是不需要 ...

  2. 【深入浅出-JVM】(5):Java 虚拟机结构

    Java 虚拟机基本结构 Java 堆 新生代.老年代划分 栈帧 感谢您的耐心阅读,如果您发现文章中有一些没表述清楚的,或者是不对的地方,请给我留言,您的鼓励是作者写作最大的动力. 作 者 : @mo ...

  3. crontab 中curl命令无法正常执行

    这里所指curl无法执行Url情况是针对带参数的链接,方法体中无法获取参数的值. 比如: */7 * * * * curl http://localhost:8088/backening/sysOrd ...

  4. 关于红黑树(R-B tree)原理,看这篇如何

    学过数据数据结构都知道二叉树的概念,而又有多种比较常见的二叉树类型,比如完全二叉树.满二叉树.二叉搜索树.均衡二叉树.完美二叉树等:今天我们要说的红黑树就是就是一颗非严格均衡的二叉树,均衡二叉树又是在 ...

  5. springcloud-高可用部署

    1.场景描述 前端时间只简单介绍了下springcloud的高可用方案(springcloud高可用方案),今天详细介绍下如何实施springcloud的高可用部署. 2.解决方案 2.1 架构方案 ...

  6. 【二分讲解及例题】火车站台连锁店-C++

    首先我们先来从一个小游戏理解一下二分.(摘自程序员小灰的博客) 为什么说这样效率最高呢?因为每一次选择数字,无论偏大还是偏小,都可以让剩下的选择范围缩小一半. 给定范围0到1000的整数: 第一次我们 ...

  7. iPhone调试移动端webview

    一.模拟器调试 1.启动Xcode 2.选择菜单Xcode - Open Developer Tool - Simulator 3.启动Simulator后,选择Simulator菜单Hardware ...

  8. P2822组合数问题

    组合数问题(NOIP2016提高组Day2T1) Time Limit:1000MS  Memory Limit:512000K [题目描述] 组合数表示的是从n个物品中选出m个物品的方案数.举个例子 ...

  9. TestNG的静态方法mock的步骤

    最近团队内部对程序中使用大量的`静态方法`,而公司要求要有sonar扫描覆盖率的,因为在大量使用静态方法的地方若不mock,则覆盖率达不到.于是网上很少的文章讲解对静态方法的mock,大多都是如何使用 ...

  10. C#中Thread.IsBackground 属性

    Thread  thread.IsBackground =true; //Gets or sets a value indicating whether or not a thread is a ba ...