前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【jvm】01- java内存结构分析

【jvm】01- java内存结构分析

作者头像
envoke
发布2020-09-17 14:20:53
4770
发布2020-09-17 14:20:53
举报

java内存结构分析

java内存结构

我们根据线程是否共享将java内存结构分成两部分:

代码语言:javascript
复制
线程共享区域
		堆
		方法区(1.8成为元区间)
线程独占区域
		栈
		本地方法栈
		PC寄存器(程序执行到的位置)
在这里插入图片描述
在这里插入图片描述

java栈结构分析:

我们先看一下栈的结构图

在这里插入图片描述
在这里插入图片描述

接下来我们详细看一下每一个部分具体作用

栈帧

每一个方法的执行就是一个栈帧,而且在栈内存中遵循先进后出的原理。听到这里,是不是感觉不是很懂(大佬直接忽略)? 我们来看一个示例:

在这里插入图片描述
在这里插入图片描述

这里先提一个小的概念: 每一个方法就是一个栈帧 入栈:方法执行的时候就会入栈,放的栈的底部。 出栈:方法执行结束就会出栈。 1.,当main方法开始执行,就会进行入栈(压栈)操作,main方法就在整个栈结构的最底部 2. main方法里调用add方法,add方法也是一个栈帧,进行了入栈操作 3. 当add方法执行结束,add方法会执行出栈(弹栈)操作。 4. add方法执行结束,main方法也会执行完毕 5. 这样就可以印证了栈的先进后出原理

局部变量表

用户存放方法参数和方法运行途中生成的变量

操作数栈

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈/入栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。

动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。

返回地址

当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理。

这里我们运用反汇编指令查看目录结构

类文件进行编译

代码语言:javascript
复制
javac  StackStructure.java

类文件进行反汇编编译

代码语言:javascript
复制
javap -c  StackStructure

然后截图看下反汇编后的add方法

代码语言:javascript
复制
 public static void add();
    Code:
       0: bipush        100      
       2: istore_0
       3: bipush        100
       5: istore_1
       6: iload_0
       7: iload_1
       8: iadd
       9: istore_2
      10: return

我们将相应的汇编指令放在这里

bipush 将一个8位带符号整数压入栈 (这里的栈指的是操作数栈) istore_0 将int类型值存入局部变量0 istore_1 将int类型值存入局部变量1 iload_0 从局部变量0中装载int类型值 iload_1 从局部变量1中装载int类型值 iadd 执行int类型的加法 istore_2 将int类型值存入局部变量2

我们通过反汇编指令来分析一下栈的各个结构的作用,我们对比上面的汇编指令进行相应的翻译

  1. 将100整数压入操作数栈
代码语言:javascript
复制
0: bipush        100      
在这里插入图片描述
在这里插入图片描述

2. 将int类型的100存入局部变量表的a中

代码语言:javascript
复制
 2: istore_0
在这里插入图片描述
在这里插入图片描述

3. 将100整数压入操作数栈

代码语言:javascript
复制
 3: bipush        100
在这里插入图片描述
在这里插入图片描述

4. 将int类型的100存入局部变量表的b中

代码语言:javascript
复制
5: istore_1
在这里插入图片描述
在这里插入图片描述

5. 从局部变量表a中装载int类型值100到操作数栈

代码语言:javascript
复制
 6: iload_0
在这里插入图片描述
在这里插入图片描述

6. 从局部变量表b中装载int类型值100到操作数栈

代码语言:javascript
复制
   7: iload_1
在这里插入图片描述
在这里插入图片描述

7. 在操作数栈中执行加法操作

代码语言:javascript
复制
   8: iadd
在这里插入图片描述
在这里插入图片描述

8. 将计算的结果200存入局部变量表c中

代码语言:javascript
复制
   9: istore_2
在这里插入图片描述
在这里插入图片描述

9. 最后将结果给返回即可

代码语言:javascript
复制
   10: return

运行时常量池

代码语言:javascript
复制
public class Test2 {
    public static void main(String[] args) {
        String s1 ="abc";
        String s2 = "abc";
        String s3 = new String("abc");
        System.out.println(s1 == s2);  // true
        System.out.println(s3 == s1);  // false
        System.out.println(s3.intern() == s1);   //true
    }
}

我们先分析前两个比较结果

在这里插入图片描述
在这里插入图片描述

String s1 = "abc"是存放在字符串常量池中,而new出来的对象是存放在堆中,所以前两个结果成立

但是为s3.intern() == s1的结果也是为true呢?我们再来看下一张图解

在这里插入图片描述
在这里插入图片描述

调用intern()方法,会把堆中的"abc"转移到方法区的字符串常量池中,并且覆盖原来的“abc”(字符串常量池类似于一个hashSet,转移的值会覆盖原来的值)。所以,三个对象此时都指向同一个常量“abc”。

对象的创建过程

类加载的执行流程图
在这里插入图片描述
在这里插入图片描述
对象创建的过程:
  1. new对象
  2. 根据参数在常量池中定位类符号的引用
  3. 判断类引用是否存在,存在则说明类已经加载,可以直接使用
  4. 找不到的情况下说明类还未加载,需要在堆内存中开辟内存空间
  5. 然后是类的属性初始化
  6. 类的构造方式初始化
对象内存分配方式

整个过程中,我们详细看如何在堆内存中开辟空间 有两种方案: 指针碰撞 空闲列表

指针碰撞

我们先看指针碰撞的情况

假设现在的堆内存是一块连续的空间,我们new了一个obj1。obj1加入到堆内存中,且会有一个指针指向obj1,obj2加入的时候也是同理,指针指向obj2

在这里插入图片描述
在这里插入图片描述

我们再看下一种情况,当多线程情况下new 出obj3和obj4,如何开辟内存空间呢?

在这里插入图片描述
在这里插入图片描述

这里会采用CAS算法,obj3和obj4的线程争抢锁,谁能拿到,谁就先执行并且再堆内存中开辟相应的内存空间。

空闲列表

堆内部有一个列表来存储我们堆中空闲的地方。我们创建对象则去找列表中对应的空闲区域去创建我们的对象。

在这里插入图片描述
在这里插入图片描述

堆是否规整有我们垃圾回收器来决定的 ,如果垃圾回收器使用的是标记压缩算法,那么他会规整的分配我们的对象

多线程的情况下: 空闲列表则采用我们的本地线程分配缓存,线程占满则采用我们的cas加锁方式,再去分配本地缓存分配一部分区域。

我们这里抛出一个问题,对象创建以后除了在堆上还会在哪里?

代码语言:javascript
复制
public class StackStructure {
    public void a() {
        StackStructure stackStructure = new StackStructure();
    }
    public StackStructure b() {
        StackStructure stackStructure = new StackStructure();
        return stackStructure;
    }
}
栈上分配:

a()方法里面声明的这个对象并没有返回给外部,或者给外部使用,所以会存在栈里面。即成为栈上分配。当声明的对象太大了以后也会造成内存逃逸,被分配到堆里面去

内存逃逸:

b()方法里面的对象返回出去了,就会从栈上逃逸,分配到堆内存里面。就造成了内存逃逸

对象结构分析

对象头 hash值、gc分代年龄、持有锁信息、 类型指针:方法区存储class对象(这个唯一的);例如:new Test().getClass() == new Test().getClass();

对象实例数据 主要存放我们自身的 属性变量,包括父类属性等。

对象填充数据 使用数据填充,没有实际的意义 HotStop 虚拟机指定对象大小必须是8个字节的整数倍。如果不是8个字节则,使用此进行填充

对象的内存引用分析

对象的内存引用有两种方式: 直接引用 句柄引用

直接引用图解

对象的直接引用,当obj对象更改时,速度较快,但是每次都需要更换对象的引用地址

在这里插入图片描述
在这里插入图片描述
句柄池引用
在这里插入图片描述
在这里插入图片描述

obj对象更改以后,不会更改A的引用,只需要把句柄池里面的引用更改就好了,效率比直接引用低

具体选择那种引用方式,是根据不同的虚拟机来选择的

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-03-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • java内存结构分析
  • java内存结构
  • java栈结构分析:
    • 栈帧
      • 局部变量表
        • 操作数栈
          • 动态连接
            • 返回地址
            • 运行时常量池
            • 对象的创建过程
              • 类加载的执行流程图
                • 对象创建的过程:
                  • 对象内存分配方式
                    • 指针碰撞
                      • 空闲列表
                        • 栈上分配:
                        • 内存逃逸:
                    • 对象结构分析
                      • 对象的内存引用分析
                        • 直接引用图解
                          • 句柄池引用
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档