Java教程

一个类的奇幻旅程 —— 类的加载与对象内存布局

本文主要是介绍一个类的奇幻旅程 —— 类的加载与对象内存布局,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

最近在学习JVM和Java高级特性,有了一些感悟,在此总结,如果有不对的地方,希望大家指出。

假设我们编写了一个java类,代码如下。

public class Student
{
    static final String schoolName = "中南林业科技大学";
    public String name;

    private Student(String name)
    {
        this.name = name;
    }

    public void speak()
    {
        System.out.println("我叫" + name + ",就读于" + schoolName);
    }

    public static void main(String[] args) 
    {
        String name = new String("黄超");
        Student student = new Student(name);
        student.speak();
    }
}

编写完成后,打开项目文件夹,我们可以看到一个名为 student.java 的文件。这就是我们编写的源文件。

现在我们执行打开 cmd ,执行 javac 命令,将它编译为一个字节码文件,这样就获得了一个名为 student.class 的文件。

接下来,我们开始执行这个类。
当我们输入执行命令后,Java虚拟机首先会加载这个类,为此JVM会自顶向下选用适当的类加载器。由于这个类是我们自己编写的,最后JVM会使用application加载器。

在加载的过程中,JVM按照类名,会读取磁盘上的 Student.class ,它是一个二进制字节流(Class字节码)。JvM会将读取的内容构建为一个class对象,也就是Student这个类的模板,然后将它放入方法区。

方法区在不同的jdk版本中,处于内存的不同的位置,对于jdk8而言,它位于Java堆的元空间中,之前这个位置被称为永生区。

加载完成后,还有链接初始化两个步骤。在链接阶段JVM将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求。同时,会将静态变量 schoolName 放入方法区的常量池。在初始化阶段,JVM会执行模板类中的静态代码块,由于我们这个类没有,所以不会执行。

完成这一切之后,就开始正式执行了。
首先是执行主函数,JVM会将主函数放入Java虚拟机栈的栈底,为它分配一个栈帧。栈帧中存放了它的方法名、参数、运行时的临时变量。同时还有一个指向方法区中函数实体的引用。

然后就是逐行执行函数。

当函数执行到第三行时,在这里我们用 new 关键字,创建了一个 String 类型的对象 name 。

这时,JVM首先会实例化一个 String 对象放入空间的常量池,它的值为 “黄超” 。

然后虚拟机会在 main 的栈帧中选择一个页面,存放一个 String 类型的引用。
同时在堆中开辟一片区域,用于存放这个对象的实例。
对象的实例会首先被放在伊甸园区,经历一轮GC它后进入幸存区,经历15轮GC后会进入老年区。但这都是后话,在我们这个程序中,它因该是不会进入老年区的。
现在我们来分析以下对象实例在堆中伊甸园区是怎么组织的。它包含三个部分对象头实例数据对齐填充
对象头主要包括两类信息:第一类被称为Mark Word,是用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态、线程持有的锁、偏向线程ID、偏向时间戳等。第二类是类型指针,用于确定对象是哪个类的实例,即指向方法区的类模板。
实例数据可以认为就是“黄超”这个字符串本身。
对其填充则是为了起占位作用,HotSpot 虚拟机要求对象的起始地址必须是8的整数倍,因此需要对齐填充。

将对象实例放入堆后,JVM会将栈中的对象引用指向堆中的实例。

随后继续执行下一行。

在这里,我们创建了一个 Student 对象,与之前创建 String 对象的过程大致相同。但要注意,这一次,堆空间中 name 存放的是一个引用,而不是一个数据。这个引用指向的正是我们之前创建的那个字符串对象,也就是作为构造函数参数传入的 name 对象。

继续, main 将会调用 student 对象的 speak 方法。它通过对象的引用,访问到堆中的对象实例,然后通过对象实例中的类型信息找到它的模板类,也就是在最开始,JVM放入方法区的那个Class类。最后它会在方法区中找到 speak 方法。
JVM随后就会将找到的这个方法压栈。
执行 speak 方法,会向控制台打印一串字符串,然后结束,退栈。之后 main 方法也会结束,退栈。这会导致Java虚拟机栈变为空,即程序执行完毕。

但是我们先不着急执行完程序,在它打印之前,也就是 speak 方法刚完成入栈的那一刻,我们来看看JVM的内存是什么情况。

内存分布情况
虽然有些杂乱,但这正和我们之前分析的整个程序执行过程的一样。
最后,speak 函数执行,控制台打印出一串字符串。随后结束程序。
看完这里,一个类的旅程也就差不多结束了。

这篇关于一个类的奇幻旅程 —— 类的加载与对象内存布局的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!