前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[JVM] JVM自动内存管理机制(一)

[JVM] JVM自动内存管理机制(一)

作者头像
架构探险之道
发布2019-09-09 15:50:49
4920
发布2019-09-09 15:50:49
举报
文章被收录于专栏:架构探险之道架构探险之道

[JVM] JVM自动内存管理机制(一)

手机用户请 横屏获取最佳阅读体验, REFERENCES中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。

平台

地址

CSDN

https://blog.csdn.net/sinat_28690417

简书

https://www.jianshu.com/u/3032cc862300

个人博客

https://yiyuery.github.io/NoteBooks/

Target

JVM系列学习知识

文本主要就JVM结构和字节码文件,进行分析来展开JVM的学习,后续系列文章会从JVM的多个方面的进行知识总结。

JVM: Java Vitual Machine

解决Java程序在一处开发,多处运行的的低层依赖问题。

JVM 结构

分层

  • 类加载子系统
  • 运行时数据区
  • 执行引擎

类加载子系统

Java 类加载器 子系统在运行时第一次引用类时加载,链接和初始化类文件。它负责从文件系统,网络或任何其他来源加载类文件。Java、Bootstrap、Extension和Application(System)类加载器中使用了三个默认的类加载器。

运行时数据区域

程序计数器

作用:字节码解释器工作时根据改变计数器的值来选取下一条需要执行的字节码指令。范围:线程私有

Java 虚拟机栈

作用:描述的是Java方法执行的内存模型:每个方法在运行时都会创建一个栈帧,用于存储方法运行时需要的数据。范围:线程私有

本地方法栈

作用:本地方法栈为虚拟机使用到的Native方法服务

Java 堆

作用:所有数据对象实例以及数组基本都在堆上进行分配 范围:线程共享

方法区

作用:用于存储已被虚拟机加载的类信息、常量、静态变量即时编译后的代码等数据。范围:线程共享

  • 运行时常量池:存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池中存放

直接内存

作用:NIO数据缓存,用于Java堆中直接操作Native堆的数据。

字节码文件

定义一个简单类

代码语言:javascript
复制
public class App {

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

    public static void main(String[] args) {
        App app = new App();
        App app2 = new App();
        System.out.println(app.calculate());
        System.out.println(app2.calculate());

    }
}

输出字节码文件

编译:javacApp.java字节码文件:javap-cApp.class>App.txt

App.txt

代码语言:javascript
复制
Compiled from "App.java"
public class src.main.java.com.example.jvm.App {
  public src.main.java.com.example.jvm.App();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public int calculate();
    Code:
    //以下为字节码指令,前面的数字为程序计数器待记录的值,和程序运行到哪一行有关
       0: iconst_1 //将一个常量加载到操作数栈 1
       1: istore_1 //将一个数值从操作数栈存储到局部变量表 a=1
       2: iconst_2 //将一个常量加载到操作数栈 2
       3: istore_2 //将一个数值从操作数栈存储到局部变量表 b=2
       4: iload_1  //将一个局部变量加载到操作栈a
       5: iload_2  //将一个局部变量加载到操作栈b
       6: iadd     //将栈顶两int型数值相加并将结果压入栈顶
       7: bipush        10 //将常量10加载到操作数栈
       9: imul      //将栈顶两int型数值相乘并将结果压入栈顶
      10: istore_3  //将一个数值从操作数栈存储到局部变量表 c=(1+2)*10=30
      11: iload_3   //将一个局部变量加载到操作栈c
      12: ireturn   //ireturn(当返回值是boolean、byte、char、short和int类型时使用)方法返回指令

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class src/main/java/com/example/jvm/App
       3: dup
       4: invokespecial #3                  // Method "<init>":()V
       7: astore_1
       8: new           #2                  // class src/main/java/com/example/jvm/App
      11: dup
      12: invokespecial #3                  // Method "<init>":()V
      15: astore_2
      16: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: aload_1
      20: invokevirtual #5                  // Method calculate:()I
      23: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      26: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      29: aload_2
      30: invokevirtual #5                  // Method calculate:()I
      33: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      36: return
}

方法栈帧字节码分析

代码语言:javascript
复制
public int calculate();
    Code:
    //以下为字节码指令,前面的数字为程序计数器待记录的值,和程序运行到哪一行有关
       0: iconst_1 //将一个常量加载到操作数栈 1
       1: istore_1 //将一个数值从操作数栈存储到局部变量表 a=1
       2: iconst_2 //将一个常量加载到操作数栈 2
       3: istore_2 //将一个数值从操作数栈存储到局部变量表 b=2
       4: iload_1  //将一个局部变量加载到操作栈a
       5: iload_2  //将一个局部变量加载到操作栈b
       6: iadd     //将栈顶两int型数值相加并将结果压入栈顶
       7: bipush        10 //将常量10加载到操作数栈
       9: imul      //将栈顶两int型数值相乘并将结果压入栈顶
      10: istore_3  //将一个数值从操作数栈存储到局部变量表 c=(1+2)*10=30
      11: iload_3   //将一个局部变量加载到操作栈c
      12: ireturn   //ireturn(当返回值是boolean、byte、char、short和int类型时使用)方法返回指令

详细分析

输出堆栈大小、各方法的 locals 及 args 数,以及class文件的编译版本

javap-verboseApp.class>App3.txt

代码语言:javascript
复制
Classfile /Users/xiazhaoyang/Capsule/repository/gitee/architectrue-adventure/code-examples/code-jvm-analysis/src/src/main/java/com/example/jvm/App.class
  Last modified Sep 5, 2019; size 543 bytes
  MD5 checksum e51368eac3771cd969c9f3d3f91bcaed
  Compiled from "App.java"
public class src.main.java.com.example.jvm.App
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // src/main/java/com/example/jvm/App
   #3 = Methodref          #2.#18         // src/main/java/com/example/jvm/App."<init>":()V
   #4 = Fieldref           #20.#21        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Methodref          #2.#22         // src/main/java/com/example/jvm/App.calculate:()I
   #6 = Methodref          #23.#24        // java/io/PrintStream.println:(I)V
   #7 = Class              #25            // java/lang/Object
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               calculate
  #13 = Utf8               ()I
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               SourceFile
  #17 = Utf8               App.java
  #18 = NameAndType        #8:#9          // "<init>":()V
  #19 = Utf8               src/main/java/com/example/jvm/App
  #20 = Class              #26            // java/lang/System
  #21 = NameAndType        #27:#28        // out:Ljava/io/PrintStream;
  #22 = NameAndType        #12:#13        // calculate:()I
  #23 = Class              #29            // java/io/PrintStream
  #24 = NameAndType        #30:#31        // println:(I)V
  #25 = Utf8               java/lang/Object
  #26 = Utf8               java/lang/System
  #27 = Utf8               out
  #28 = Utf8               Ljava/io/PrintStream;
  #29 = Utf8               java/io/PrintStream
  #30 = Utf8               println
  #31 = Utf8               (I)V
{
  public src.main.java.com.example.jvm.App();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 24: 0

  public int calculate();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         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
      LineNumberTable:
        line 27: 0
        line 28: 2
        line 29: 4
        line 30: 11

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
        //#2指向Constant pool的#2->#19类文件的引用
         0: new           #2                  // class src/main/java/com/example/jvm/App
         //复制栈顶数值(数值不能是long或double类型的)并将复制值压入栈顶
         3: dup
         //指令用于调用一些需要特殊处理的实例方法,包括实例初始化(<init>)方法、私有方法和父类方法。
         4: invokespecial #3                  // Method "<init>":()V
         //将栈顶引用型数值存入第二个本地变量
         7: astore_1
         8: new           #2                  // class src/main/java/com/example/jvm/App
        11: dup
        12: invokespecial #3                  // Method "<init>":()V
        //将栈顶引用型数值存入第三个本地变量
        15: astore_2
        //访问类字段(static字段,或者称为类变量)和实例字段(非static字段,或者称为实例变量)的指令 #20,#21 (类,静态方法)
        16: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        //将第二个引用类型本地变量推送至栈顶
        19: aload_1
        //指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式
        20: invokevirtual #5                  // Method calculate:()I
        23: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        26: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        29: aload_2
        30: invokevirtual #5                  // Method calculate:()I
        33: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
        36: return
      LineNumberTable:
        line 34: 0
        line 35: 8
        line 36: 16
        line 37: 26
        line 39: 36
}
SourceFile: "App.java"

流程解析

代码语言:javascript
复制
public int calculate() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }

前文提到的,每个方法对应的再JVM中的存储结构就是一个栈帧,每个栈帧里有自己的程序计数器,上面的这个简单的 calculate在运行时的流程大致如下:

  • 1放入操作数栈,
  • 1出栈,存到局部变量a中
  • 局部变量a放到操作数栈
  • 2入操作数栈
  • 2出栈,存到局部变量b中
  • b放到操作数栈中
  • a,b两个操作数进行相加操作,再将结果3压入栈
  • 10 入操作数栈
  • 3和10进行相乘,并将结果30再次入操作数栈
  • 30 出栈,存到局部变量c中
  • 方法出口指向下一个栈帧起始位置

REFERENCES

JVM 字节码指令

  • jvm理论-字节码指令
  • Java字节码浅析(一)
  • Java字节码浅析(二)
  • Java字节码浅析(三)

JDK 命令行

  • javap的基本用法

JVM

  • 什么是Java虚拟机?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构探险之道 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • [JVM] JVM自动内存管理机制(一)
    • JVM 结构
      • 类加载子系统
      • 运行时数据区域
    • 字节码文件
      • 流程解析
        • REFERENCES
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档