JVM - 结合代码示例彻底搞懂Java内存区域_线程栈 | 本地方法栈 | 程序计数器

简介: JVM - 结合代码示例彻底搞懂Java内存区域_线程栈 | 本地方法栈 | 程序计数器

2020062123300753.png


Pre

JVM-01Java内存区域与内存溢出异常(上)【运行时区域数据】

JVM-02内存区域与内存溢出异常(中)【hotspot虚拟机对象】

JVM-03内存区域与内存溢出异常(下)【OutOfMemoryError案例】


运行时数据区总览


20200621113313484.png


字节码文件被装载子系统装载到JVM中,字节码执行引擎负责执行这些字节码文件。

装载子系统和执行引擎都是C++的实现。

装载子系统: JVM-白话聊一聊JVM类加载和双亲委派机制源码解析

我们重点关注下运行时数据区域 ,先关注线程私有的这3个部分。


线程栈


概要


没给方法被执行的时候,JVM都会同步创建一个栈帧。

这个栈和数据结构的栈结构是一样的, FILO .


举个例子 ,方法A 中调用了方法B , 代码先执行方法A ,此时方法A 入栈, 然后调用方法B,这个时候方法B入栈 。 当方法B执行结束后,方法B出栈,回到方法A执行的地方,方法A继续执行,执行结束 ,方法A出栈。


20200621134337702.png


栈内部主要组成部分

【Java代码】

   public int doSomething() {
        int a = 1 ;
        int b = 2 ;
        int c = (a + b) * 10 ;
        return c;
    }


【javap -c 反汇编】

如何操作的,见文末 ,JVM字节码指令集也见文末

  public int doSomething();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: bipush        10
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn
}


0: iconst_1

1: istore_1 【i —> int类型】

iconst_1 是个什么鬼? 查查操作手册



20200621163350493.png


istore_1


20200621163507780.png

iconst_0 和 istore_0 是默认存放调用该方法的对象。

这里就涉及到两个组成部分 【局部变量】 + 【操作数栈】


局部变量

0x04  iconst_1  将 int 型 1 推送至栈顶
0x3c  istore_1  将栈顶 int 型数值存入第二个本地变量
0x05  iconst_2  将 int 型 2 推送至栈顶
0x3d  istore_2  将栈顶 int 型数值存入第三个本地变量

比对代码

int a = 1 ;
  int b = 2 ;

iconst_1 , 将 int 1 压入操作数栈 , istore_1 将栈顶 int 型数值存入第二个本地变量 ,这个时候 会先将 1 出栈,然后存入局部变量表。

istore_1 、istore_2 存入本地变量,就是放到了局部变量表。


操作数栈

0x04  iconst_1  将 int 型 1 推送至栈顶
0x05  iconst_2  将 int 型 2 推送至栈顶

这两步的意思 就是将代码中的 a 和 b 对应的值 1 和 2 压入 操作数栈 。

这个操作数栈 本身也是栈结构, FILO . 入栈 出栈


继续

int c = (a + b) * 10 ;
 return c;


 4: iload_1    将第二个 int 型本地变量推送至栈顶  ----> a的值 1 入栈 (操作数栈)
 5: iload_2    将第三个 int 型本地变量推送至栈顶  ----> b的值 2 入栈 (操作数栈)
 6: iadd       将栈顶两 int 型数值相加并将结果压入栈顶  ---->  计算 a+b =3,结果压入栈顶 (a ,b 出栈,计算结果,然后将a+b的结果压入操作数栈)
 7: bipush        10      将单字节的常量值(-128~127)推送至栈顶  -----> 10 入栈 操作数栈)
 9: imul       将栈顶两 int 型数值相乘并将结果压入栈顶  ----> 计算乘积 3 * 10 ,将30压入操作数栈
 10: istore_3  将栈顶 int 型数值存入第四个本地变量  ------>  给 c赋值 
 11: iload_3   将第四个 int 型本地变量推送至栈顶  ----> 压入操作数栈 
 12: ireturn   从当前方法返回 int  ----> 返回


上面的行号 7 到9 ? 没有9 。 我们这个常量10 也要占位置嘛 。

计算 结果肯定是CPU执行的嘛 ,只不过数据是存放在内存中的。

简言之,操作数栈,是程序运行期间需要临时存放数据的内存空间。


动态链接


符号引用 替换为 直接引用。

我们知道在类装载的过程中,有个过程是【解析】



20200621181521290.png


JVM-白话聊一聊JVM类加载和双亲委派机制源码解析

举个例子

 public static void main(String[] args) {
        Artisan artisan = new Artisan();
        artisan.doSomething();
    }

简单说当应用执行到artisan.doSomething()方法( 非静态方法),需要找到这个方法在方法区的常量池中的具体的位置,这个过程就是动态链接


方法区#运行时常量池 ,是方法区的一部分。 Class文件中的常量池表用于存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。


方法出口

   public static void main(String[] args) {
        Artisan artisan = new Artisan();
        artisan.doSomething();
    }
    public int doSomething() {
        int a = 1 ;
        int b = 2 ;
        int c = (a + b) * 10 ;
        return c;
    }


doSomething方法执行完要回到main方法中,方法出口中记录的就是记录main方法中的位置,不记录的话 ,不知道回到main方法中的哪一行继续执行哇~


小结



2020062123434479.png


程序计数器


简单理解,可以理解为 记录程序执行的位置。

线程私有。

Java多线程,当线程A没有抢到CPU的执行权,如果没记录程序执行的位置,等下次抢到CPU执行权的时候,这尼玛咋弄? 重新开始执行吗?


显然是不行的,所以需要程序计数器来给每个线程的执行到的行号做下标记。各个现场的程序计数器互不影响,独立存储。


我们来看看javap -c 处理的反汇编


20200621180031125.png


简单理解,可以理解为上面的行号, 实际上存储的是这行代码对应在内存中的指针位置。

字节码 由谁来执行? 肯定是字节码执行引擎嘛 ,所以 字节码执行引擎肯定知道程序的执行位置,这样 字节码执行引擎和程序计数器就关联起来了。


本地方法栈

native 方法 底层C++的


测试demo

package com.gof.test;
public class Artisan {
    public static void main(String[] args) {
        Artisan artisan = new Artisan();
        artisan.doSomething();
    }
    public int doSomething() { // public 类型
        int a = 1 ;
        int b = 2 ;
        int c = (a + b) * 10 ;
        return c;
    }
}



javap

用法: javap <options> <classes>
其中, 可能的选项包括:
  -help  --help  -?        输出此用法消息
  -version                 版本信息
  -v  -verbose             输出附加信息
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置


-c 对代码进行反汇编

 E:\Program Files\Java\jdk1.8.0_161\bin> ./javap -c D:\IdeaProjects\GOF23\target\classes\com\gof\test\Artisan.class > Artisan.txt


查看 Artisan.txt

Compiled from "Artisan.java"
public class com.gof.test.Artisan {
  public com.gof.test.Artisan();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/gof/test/Artisan
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #4                  // Method doSomething:()I
      12: pop
      13: return
  public int doSomething();
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_2
       3: istore_2
       4: iload_1
       5: iload_2
       6: iadd
       7: bipush        10
       9: imul
      10: istore_3
      11: iload_3
      12: ireturn
}


JVM字节码指令集手册

下载地址戳这里–>提取码:9ru5


相关文章
|
9月前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
546 18
|
9月前
|
Java 编译器 Go
【Java】(5)方法的概念、方法的调用、方法重载、构造方法的创建
Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的优点使程序变得更简短而清晰。有利于程序维护。可以提高程序开发的效率。提高了代码的重用性。方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。这种就属于驼峰写法下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。
376 4
|
9月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
504 5
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
1078 55
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
1068 6
|
9月前
|
存储 缓存 Java
我们来说一说 JVM 的内存模型
我是小假 期待与你的下一次相遇 ~
590 5
|
9月前
|
存储 缓存 算法
深入理解JVM《JVM内存区域详解 - 世界的基石》
Java代码从编译到执行需经javac编译为.class字节码,再由JVM加载运行。JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)区域,其中堆是GC主战场,方法区在JDK 8+演变为使用本地内存的元空间,直接内存则用于提升NIO性能,但可能引发OOM。
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
2825 1
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
1398 166
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
803 29
JVM简介—1.Java内存区域