前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >举一个有趣的例子,让你轻松搞懂JVM内存管理

举一个有趣的例子,让你轻松搞懂JVM内存管理

作者头像
智慧zhuhuix
发布2020-08-14 16:31:10
3420
发布2020-08-14 16:31:10
举报

前言

在JAVA虚拟机内存管理中,堆、栈、方法区、常量池等概念经常被提到,对理论知识的理解也常常停留在字面意思上,比如说堆内存中存放对象,栈内存中存放局部变量,常量池中存放字符串常量表等,本篇文章通过一个有趣的例子,尽量将这些理论概念通过程序样例及图解的方式表达清楚,让我们更能深入底层知识。

例子
源码
  • StringJvm类中定义了一个CONST_STRING字符串常量,并赋值"Hello World";
  • main方法定义String类型的str1,str2变量,两个变量都赋值"Hello World";
  • 比较一下str1和str2的地址值是否相等 ;
  • new 一个String类型的str3,并通过构造函数初始化值为"Hello World";
  • 比较一下str3和str3的地址值是否相等。
代码语言:javascript
复制
/**
 * 理解JVM--堆内存中的对象
 * @author zhuhuix
 * @date 2020-05-19
 *
 */
public class StringJvm {
    public  static final String  CONST_STRING="Hello World";

    public static void main(String[] args) {

        String str1 =CONST_STRING;
        String str2 =CONST_STRING;
        System.out.println("str1==str2: "+ (str1 == str2));
		System.out.println("str1==CONST_STRING: "+ (str1 == CONST_STRING));
		 
        String str3 =new String(CONST_STRING);
        System.out.println("str3==str1: "+(str3 == str1));
    }
}
输出

先说两个比较结果:

  1. str1的地址值等于str2的地址值
  2. str1的地址值等于CONST_STRING的地址值
  3. str3的地址值不等于str1地址值,即也不等于str2值
在这里插入图片描述
在这里插入图片描述
图解

有些同学有可能会有疑问,明明四个字符串的值都是“Hello World”,地址值却有相等 ,也有不相等的,这里就会引入JVM的堆内存、栈内存,常量池的一些基本概念,我们直接上图再讲解说明:

在这里插入图片描述
在这里插入图片描述
  1. 定义静态常量CONST_STRING放入方法区;赋值时,JVM会在字符串常量池中放入"Hello World"字符串供共享使用,并将内存地址赋给静态常量。
  2. 定义str1变量并给该字符串赋值时,JVM首先会在字符串常量池中寻找字符串值相同的内存地址,并将该内存地址赋值str1变量。所以在程序中打印输出str1变量的地址值是否等 于静态常量CONST_STRING的地址值时,得到的结果是True.
  3. 定义str2变量时按以上2过程同样处理,也即str2的地址值等于CONST_STRING和str1的地址值。
  4. 在定义str3变量时,采用了强制new一个对象及构造方法传值方式处理,我们知道new一个对象,一定会在堆中分配内存给该对象,也即str3的地址值引用在堆内存中,所以str3的地址值肯定不等于str1的地址值。
深入分析

以上的图解只是JVM的理论上解释了为什么有些地址值相等,有些地址值不等,接下来我们创建一个获取地址值的工具类,来验证以上的理论知识。

首先我们利用Java中的Unsafe类创建一个工具类及静态方法,模仿C++手动管理内存的能力来获取对象的实际地址值。

代码语言:javascript
复制
/**
 * 获取JVM object的内存地址
 * @author zhuhuix
 * @date 2020-05-19
 *
 */
public class ObjectAddress {
    private static Unsafe unsafe;

    static
    {
        try
        {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe)field.get(null);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    public static long addressOf(Object o) {
        Object[] array = new Object[] {o};

        long baseOffset = unsafe.arrayBaseOffset(Object[].class);
        int addressSize = unsafe.addressSize();
        long objectAddress;
        switch (addressSize)
        {
            case 4:
                objectAddress = unsafe.getInt(array, baseOffset);
                break;
            case 8:
                objectAddress = unsafe.getLong(array, baseOffset);
                break;
            default:
                throw new Error("unsupported address size: " + addressSize);
        }

        return(objectAddress);
    }

接下来改造一下原来的程序:

代码语言:javascript
复制
/**
 * 理解JVM--堆内存中的对象
 * @author zhuhuix
 * @date 2020-05-19
 *
 */
public class StringJvm {
    public  static final String  CONST_STRING="Hello World";

    public static void main(String[] args) {

       System.out.println( "CONST_STRING的地址值:"+ObjectAddress.addressOf(CONST_STRING));

        String str1 =CONST_STRING;
        String str2 =CONST_STRING;
        System.out.println( "str1的地址值 :"+ObjectAddress.addressOf(str1));
        System.out.println("str1==str2: "+ (str1 == str2));
        System.out.println("str1==CONST_STRING: "+ (str1 == CONST_STRING));

        String str3 =new String(CONST_STRING);
        System.out.println("str3的地址值 :"+ ObjectAddress.addressOf(str3));
        System.out.println("str3==str1: "+(str3 == str1));

    }
}

输出结果如下: 这里我们实际看到了对象的实际地址值,静态常量CONST_STRING的地址值等于str1的地址值;str3的地址值不等于str1的地址值。

在这里插入图片描述
在这里插入图片描述
学以致用

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的intern()方法。

我们看下上面这段话,JVM告诉我们常量池有动态的特性,利用String类的intern()方法能够将当前String对象与常量池动态绑定起来。为了更好理解,我们再改造一下例子:

代码语言:javascript
复制
/**
 * 理解JVM--堆内存中的对象
 * @author zhuhuix
 * @date 2020-05-19
 *
 */
public class StringJvm {
    public  static final String  CONST_STRING="Hello World";

    public static void main(String[] args) {

       System.out.println( "CONST_STRING的地址值:"+ObjectAddress.addressOf(CONST_STRING));

        String str1 =CONST_STRING;
        String str2 =CONST_STRING;
        System.out.println( "str1的地址值 :"+ObjectAddress.addressOf(str1));
        System.out.println("str1==str2: "+ (str1 == str2));
        System.out.println("str1==CONST_STRING: "+ (str1 == CONST_STRING));

        String str3 =new String(CONST_STRING);
        System.out.println("str3 new String的地址值 :"+ ObjectAddress.addressOf(str3));
        System.out.println("str3==str1: "+(str3 == str1));
		//通过intern方法在程序运行运程中与常量池进行动态绑定
        str3=str3.intern();
        System.out.println("str3.intern()的地址值 :"+ ObjectAddress.addressOf(str3));

        System.out.println("str3.intern()后==str1: "+(str3 == str1));
    }
}

看下输出结果:我们发现利用此方法str3的地址值与str1地址一致了,也就是说str3也指向了常量池中初始的内存分配地址。

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

把以上改造后的代码,再用这张图说明就一清二楚了。

在这里插入图片描述
在这里插入图片描述
写在最后

JVM的架构设计是非常精妙的,需要深入底层进行剖析;在对系统架构的学习过程中,结合原理动手写样例代码,画原型图是我们学习路上必不可少的一步。

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 例子
      • 学以致用
        • 写在最后
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档