概述:

  对于从事C、C++开发的程序员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”,又是从事最基础工作的劳动人民——既拥有每个对象的“所有权”,

又担负着每一个对象从开始到终结的维护职责。

  对于java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为没一个new操作去配对的free/delete(C、C++语言对对象的删除和内存释放操作),

不容易出现内存泄漏和内存溢出问题,看起来由虚拟机管理内存一切看起来很美好。不过,也正是java把控制内存的权力交给了java虚拟机,一旦出现内存泄漏

和内存溢出方面的问题,如果不了解虚拟机是怎么使用内存的,那排查错误、修正问题将会成为一项异常艰难的工作。

运行时数据区:

  java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有些区域会随着

虚拟机进程的启动而一直存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。如下图所示:

我们知道JVM也属于一种特殊的操作系统,那这些数据区域跟我们最常用的windows哪些部分相对应呢。我们可以吧windows的CPU+缓存+主内存和JVM的执行引擎+

操作数栈+(栈、堆)对应起来,这样更加利于我们去理解JVM。

虚拟机栈:

  从上图可见,java虚拟机栈是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是java方法执行的线程内存模型:每个方法被执行的时候,java虚拟机都会

同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、返回地址等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到

出栈的过程。我们来通过一段非常简短的代码来演示虚拟机栈的作用:

/**
* @ClassName StackTest
* @description:
* @author:liuyi
* @Date:2020/11/23 23:45
*/
public class StackTest { public static void main(String[] args) {
A();
} static void A(){
B();
} static void B(){
C();
} static void C(){ }
}

  当我们运行main方法,虚拟机会开启一个线程,同时为当前线程划分一块内存区域作为当前线程的虚拟机栈。同时在执行每个方法的时候都会打包成一个栈帧。

比如 main 开始运行,打包一个栈帧送入到虚拟机栈。C 方法运行完了,C 方法出栈,接着 B 方法运行完了,B 方法出栈、接着 A 方法运行完了,A 方法出栈,

最后 main 方法运行完了,main 方法这个栈帧就出栈了。这个就是 Java 方法运行对虚拟机栈的一个影响。虚拟机栈就是用来存储线程运行方法中的数据的。而

每一个方法对应一个栈帧。入栈过程如下图所示:

上图描述了整个main方法调用的入栈和出栈的过程,需要注意的是栈帧出栈之后就没了,栈帧没得GC的说法。

栈帧详解:

  栈帧大体都包含四个区域:(局部变量表、操作数栈、动态连接、返回地址)

  • 局部变量表:顾名思义就是局部变量的表,用于存放我们的局部变量的(方法中的变量)。首先它是一个 32 位的长度,主要存放我们的 Java 的八大基础数据
类型,一般 32 位就可以存放下,如果是 64 位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的 Object 对象,我们只需要存放它的一个引用地址即可。
  • 操作数栈:存放 java 方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的 java 数据类型,所
以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的。操作数栈本质上是 JVM 执行引擎的一个工作区,也就是方法在执行,才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的。
  • 动态连接:Java 语言特性多态(后续章节细讲,需要结合 class 与执行引擎一起来讲)。
  • 方法出口:正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)。

我们来通过分析一个简单的方法来理解栈帧中各个区域是如何运作的,代码如下:

/**
* @ClassName User
* @description:
* @author:liuyi
* @Date:2020/11/25 20:51
*/
public class User {
public static int work(){
int a = 2;
int b = 3;
int c = a*b;
return c;
}

public static void main(String[] args) {
System.out.println(work());
}
}

 当该程序运行的时候,JVM会为其分配虚拟机栈,并生成对应的栈帧,如下图所示:

我们通过反汇编命令查看work方法的字节码如下:

我们看到work方法一共由10条字节码组成,我们来逐步分析。

打开 https://cloud.tencent.com/developer/article/1333540查看字节码指令

现来看iconst_2对应的含义,如图

所以第1个字节码是将一个值为2的数字加载到操作数栈。再来看 istore_0的含义,如图

  所以第2个字节码的含义就是将第一步中放入到操作数栈的数字放到局部变量表中,位置为0。所以前面两个字节码对应的java代码就是int a = 2;那么显而易见3和4两个字节码对应的

就是int b = 3;到这里,大家心里肯定会有疑问,为什么不直接将值放到局部变量表呢?我们接着分析,你就明白了。

  继续来看第5和第6两个字节码:iload_0和iload_1,它们的含义是将局部变量表中位置0和1的两个数加载到操作数栈中,接着我们来看关键的第7个字节码:imul,它代表的意思

是相乘,就是将操作数栈中的数字进行乘法运算,我们知道相乘是需要运算的,所以此时要交给执行引擎运算,运算完成之后再将运算的结果返回到操作数栈。所以操作数栈的作用

就是为jvm高速的计算提供缓冲区。

  接着来看第8个字节码:istore_2,它的含义就是将计算的结果放入局部变量表,到这里int c = a*b;就执行完了。然后再来看第9和第10个字节码,它们的含义是将局部变量表的值再

压入操作数栈,最后返回。至此,整个方法执行结束,以上就是栈帧中各个区域在方法执行中的运作流程。

虚拟机栈大小的设置:

  虚拟机栈的大小缺省为 1M,可用参数 –Xss 调整大小,例如-Xss256k。

参数官方文档(JDK1.8):https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html。

我们可以看到linux的建议配置为1M,至于windows为啥没有,博主大胆猜想可能跟微软和Oracel两家公司竞争有关吧,毕竟微软开发.net就是和java竞争的。

虚拟机栈相关的程序异常:

  • StackOverflowError异常:如果线程请求的栈深入大于虚拟机所允许的深度,将抛出StackOverflowError异常,通常是由无线递归导致的,如下面的代码

  • OutOfMemoryError:如果java虚拟机的容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。这种情况基本很少出现,也很难模拟,这里就不演示了。

程序计数器:

  与虚拟机栈一样,程序计数器也是线程私有的。程序计数器是一块很小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,就如上面反汇编User.class看到的一样。每一个字节码都有自己的序号:

   如上图所示,虽然这些序号是由顺序的,但是并不一定是依次递增,如果某给字节码占用的空间很大,那么它的序号相较于前一个序号就差距更大。

在java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的执行器,分支、

循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器完成。

  它还有另外一个作用,我们知道在java中可以开启成百上千个线程,但是我们一般的电脑CPU也就8个左右。java虚拟机的多线程是通过线程轮流切换、

分配处理器执行时间方式来实现的,那么切换后虚拟机是怎么知道以前运行的位置,继续运行的呢?这个时候,程序计数器就起到了决定性的作用,因为

程序计数器是线程独有的,所以不会相互影响,当切回到当前线程,根据程序计数器记录的序号,继续执行对应的字节码即可。

  在JVM中,只有执行java方法的时候,程序计数器才会记录正在执行的虚拟机字节码指令的地址,如果正在执行的是本地(Native)方法,这个计数器

则应为空(Undefined)。但是这里会产生一个疑问,如果刚好在执行Native方法的时候线程切换了,那切回来之后该怎么找到对应的位置呢?这里,我猜测

JVM可能规定了 在执行Native本地方法的时候,禁止切换当前线程(如不正确,请指正)。xianc

本地方法栈:

  本地方法栈与虚拟机栈的作用非常相似,其区别只是虚拟机栈为java方法服务,而本地方法栈专门为Native本地方法服务。需要注意的是,HotSpot直接把

本地方法栈和虚拟机栈合并了。

总结:

  本篇文章介绍了JVM的内存区域之线程私有区域,主要介绍了虚拟机栈的各个组成部分以及java方法是怎么通过虚拟机栈来实现执行的,接着介绍了程序计数器的作用

最后简述了本地方法栈。下一章,我们将要分析JVM内存区域的线程共享数据区,主要包括堆、方法区、运行时常量池以及直接内存等内容。

JVM(二)-内存区域之线程私有区的更多相关文章

  1. 一、JVM — Java内存区域

    Java 内存区域详解 写在前面 (常见面试题) 基本问题 拓展问题 一 概述 二 运行时数据区域 2.1 程序计数器 2.2 Java 虚拟机栈 2.3 本地方法栈 2.4 堆 2.5 方法区 2. ...

  2. JVM——Java内存区域

    一,概述: Java跟C++不同,在内存管理区域C++程序员拥有着最高权力,但是正是因为如此,所以C++程序员要照顾这个对象的生老病死,从创建到消亡都是由程序员决定的. 但是Java程序员在虚拟机的自 ...

  3. jvm的内存区域介绍

    什么是jvm? JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的 ...

  4. 深入理解JVM - Java内存区域与内存溢出异常 - 第二章

    一 运行时数据区域 JVM在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间. 程序计数器 程序计数器(Program Counter ...

  5. JVM的内存区域划分

            JVM的内存区域划分 学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内存又是如何划分的 ...

  6. JVM的内存区域划分以及垃圾回收机制详解

    在我们写Java代码时,大部分情况下是不用关心你New的对象是否被释放掉,或者什么时候被释放掉.因为JVM中有垃圾自动回收机制.在之前的博客中我们聊过Objective-C中的MRC(手动引用计数)以 ...

  7. 01 深入理解JVM的内存区域

    先来看看JVM运行时候的内存区域,如下图: 大多数 JVM 将内存区域划分为 Heap(堆).方法区.Stack(栈).本地方法栈.程序计数器.其中 Heap 和 方法区 是线程共享的,Stack.本 ...

  8. JVM的内存区域模型

    首先要明白一个概念,就是JVM的内存区域划分与java的内存区域模型是两个不同的概念,前者指的是在java中jvm会将一个程序划分为哪些块来存储对应的数据,后者是一个更宏观上的j概念,指的是java线 ...

  9. JVM的内存区域划分(转)

    原文链接:JVM的内存区域划分 JVM的内存区域划分 学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆.栈以及静态数据区.那么在Java语言当中,内 ...

随机推荐

  1. linux中nginx中配置端口转发

    域名指向主机IP地址,通过域名:8080才能访问网站,去掉后面的8080:或者其他的端口号,直接使用域名访问网站 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处 ...

  2. String字符串性能优化的探究

    一.背景 String 对象是我们使用最频繁的一个对象类型,但它的性能问题却是最容易被忽略的.String 对象作为 Java 语言中重要的数据类型,是内存中占用空间最大的一个对象,高效地使用字符串, ...

  3. puk2367 拓扑排序

    Description The system of Martians' blood relations is confusing enough. Actually, Martians bud when ...

  4. Nodejs在VSCode下代码智能提示

    在学习Nodejs的过程中发现vscode下默认没有提示,在网上也测试了传统的一些方法,都不好用,最后找到这个npm install --save-dev @types/node

  5. abp(net core)+easyui+efcore实现仓储管理系统——出库管理之五(五十四)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统--ABP总体介绍(一) abp(net core)+ ...

  6. 【SpringBoot】07.SpringBoot文件上传

    SpringBoot文件上传 1.编写html文件在classpath下的static中 <!DOCTYPE html> <html> <head> <met ...

  7. 面经手册 · 第17篇《码农会锁,ReentrantLock之AQS原理分析和实践使用》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 如果你相信你做什么都能成,你会自信的多! 千万不要总自我否定,尤其是职场的打工人.如 ...

  8. 完全卸载node.js

    1.通过控制面板卸载node.js 2.删除安装所在文件夹下的nodejs文件夹[我的是 C:\Program Files\nodejs] 3.删除C:\Users\xxx(自己电脑的名字)下的.np ...

  9. java enum 枚举值

    public enum PieChartEnum { PIE00("pie00"), PIE10("pie10"), PIE11("pie11&quo ...

  10. SpringBoot第十集:i18n与Webjars的应用(2020最新最易懂)

    SpringBoot第十集:i18n与Webjars的应用(2020最新最易懂) 一,页面国际化 i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符 ...