前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >元空间在哪里

元空间在哪里

作者头像
书唐瑞
发布2022-06-02 14:53:35
9300
发布2022-06-02 14:53:35
举报
文章被收录于专栏:Netty历险记Netty历险记

在很长的一段时间里,我一直在思考一个问题,元空间到底在哪里?

现在的互联网,关于JVM,关于内存布局,关于优化JVM等知识,多如牛毛.

然而,元空间到底在哪里?堆外内存到底在哪里? 虽然有相关的文章谈及它们,但并不是我想要的答案,为了把它们掰扯清楚,让自己有一个较清晰的认知,不人云亦云, 自己对它们做了一点分析和研究,分享给大家,与大家一起交流.

依赖的包和测试代码如下所示

代码语言:javascript
复制
<dependency>
    <groupId>org.jctools</groupId>
    <artifactId>jctools-core</artifactId>
    <version>2.1.2</version>
</dependency>

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>
代码语言:javascript
复制
package com.infuq.memory;

import org.jctools.util.UnsafeAccess;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import sun.misc.Unsafe;

public class AddressExample {

    int value;

    private AddressExample() {
        this(20);
    }

    private AddressExample(int value) {
        this.value = value;
    }

    public static void main(String[] args) throws Exception {

        // 创建一个堆对象
        AddressExample addressExample = new AddressExample();
        long heap = VM.current().addressOf(addressExample);
        System.out.println("heap address:\t 0x" + Long.toHexString(heap));

        // 打印对象的布局(包括对象头等信息)
        System.out.println(ClassLayout.parseInstance(addressExample).toPrintable());

        // Class对象
        long clazz = VM.current().addressOf(AddressExample.class);
        System.out.println("clazz address:\t 0x" + Long.toHexString(clazz));

        // 申请30M的直接内存
        Unsafe unsafe = UnsafeAccess.UNSAFE;
        long direct = unsafe.allocateMemory(30 * 1024 * 1024);
        System.out.println("direct address:\t 0x" + Long.toHexString(direct));

        // 每5秒打印一次对象的value属性值, value初始值20 .
        while (true)
        {
            Thread.sleep(5000);
            System.out.println(addressExample.value);
        }

    }
}

以上代码,借助第三方工具包,可以获取一个对象在堆内存的起始地址, 对象头信息, 以及申请30M的直接内存.

编译程序

javac -d . -classpath ".:./lib/*" AddressExample.java

运行程序

代码语言:javascript
复制
java -classpath ".:./lib/*" 
-Xms50M -Xmx50M 
-XX:MaxDirectMemorySize=32M 
-XX:MetaspaceSize=12M 
-XX:MaxMetaspaceSize=16M 
-XX:-UseCompressedClassPointers 
-XX:-UseCompressedOops 
com.infuq.memory.AddressExample

根据输出信息可知

对象在堆中的地址=0x7f64890775a8

class对象的地址=0x7f6489076778

申请30M直接内存的地址=0x7f64661ff010

继续分析

查看进程的maps文件信息

由于对象在堆中的地址=0x7f64890775a8, 它处在图中所示的7f6489000000-7f648c200000空间范围内. 而这个范围的空间大小=(7f648c200000 减 7f6489000000) / 1024 / 1024 = 50M, 它等于我们运行程序时设置的堆空间大小 -Xms50M -Xmx50M.

class对象的地址=0x7f6489076778, 它也在堆空间7f6489000000-7f648c200000范围内.

继续分析

由于程序中申请了30M的直接内存,它的地址=0x7f64661ff010.

它处在上图中所示的7f64661ff000-7f6468000000空间范围内. 而这个范围的空间大小=(7f6468000000 减 7f64661ff000) / 1024 / 1024 = 30M, 它等于我们运行程序时申请的30M内存.

继续分析

在之前的打印中,打印出了对象头信息, 对象头中包含一个指针,这个指针指向元空间中的对象元信息.

指针地址=0x7f6488c00730

此处的指针地址涉及大小端问题, 需要从后向前读才是正确的指针地址

指针地址=0x7f6488c00730处在7f6488702000-7f6488e80000这个地址空间范围.而这个范围的空间大小=(7f6488e80000 减 7f6488702000) / 1024 / 1024 = 7M.

综上分析, 堆内存, 元空间, 直接内存 , 分别'分布在'Java进程的不同区域. 虽然元空间和直接内存都属于本地内存, 但它们都归属于Java进程里的空间.

读者要对进程虚拟地址空间有一定的了解

以上测试实验是在阿里云ECS上进行的, 由于某些原因, 无法继续实验, 接下来, 我在虚拟机VirtualBox上继续接下来的实验. 代码依然是上面的代码, 只是把代码放到VirtualBox上运行而已.

继续实验

再次重新运行程序,得到如下内容

根据之前的分析, 将上图中打印的地址归属到不同的区域上,可以得出如下概图

由于普通对象的对象头中包含指向元数据的指针, 因此可以看到图中, 有一个普通对象指向元数据的箭头.

我们读取了元数据的信息, 信息中有个指针指向了Class对象.

关于如何读取一个对象的内存信息, 后期讲解.

在Klass源码中定义了一个指向Class对象的属性.

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

本文分享自 Netty历险记 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档