java如何引用对象 如何创建对象( 二 )


main()栈帧中的局部变量表包含两个变量:args和school 。
主线程的虚拟机栈的栈帧结构如下图:

java如何引用对象 如何创建对象

文章插图
main()方法想要将school这个局部变量实例化,就需要执行School这个类的实例化 。
那么new School()发生了什么呢?我们接下来详细分析之前的5个步骤 。
判断对象的类是否已经加载
当虚拟机遇到new这个指令时,会首先检查这个指令的参数能否在元空间的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,即判断元空间中是否包含这个类的类元信息 。
我们通过javap -v -p Test.clas查看Test类的字节码信息:
Classfile/E:/study/javacodegirl/src/main/java/com/study/test/code/girl/base/jvm/Test.classLastmodified2021-2-21;size352bytesMD5checksum2df3d394ac88d2aa4da9d27f848067c5Compiledfrom"School.java"classcom.study.test.code.girl.base.jvm.Testminorversion:0majorversion:52flags:ACC_SUPERConstantpool:1=Methodref5.14//java/lang/Object."":()V2=Class15//com/study/test/code/girl/base/jvm/School3=Methodref2.14//com/study/test/code/girl/base/jvm/School."":()V4=Class16//com/study/test/code/girl/base/jvm/Test5=Class17//java/lang/Object6=Utf87=Utf8()V8=Utf8Code9=Utf8LineNumberTable10=Utf8main11=Utf8([Ljava/lang/String;)V12=Utf8SourceFile13=Utf8School.java14=NameAndType6:7//"":()V15=Utf8com/study/test/code/girl/base/jvm/School16=Utf8com/study/test/code/girl/base/jvm/Test17=Utf8java/lang/Object{com.study.test.code.girl.base.jvm.Test();descriptor:()Vflags:Code:stack=1,locals=1,args_size=10:aload_01:invokespecial1//Methodjava/lang/Object."":()V4:returnLineNumberTable:line28:0publicstaticvoidmain(java.lang.String[]);descriptor:([Ljava/lang/String;)Vflags:ACC_PUBLIC,ACC_STATICCode:stack=2,locals=2,args_size=10:new2//classcom/study/test/code/girl/base/jvm/School3:dup4:invokespecial3//Methodcom/study/test/code/girl/base/jvm/School."":()V7:astore_18:returnLineNumberTable:line30:0line31:8}SourceFile:"School.java"
在main()中new指令的参数是2,我们可以在Constant pool中找到2对应的类信息 。
如果没有这个类的信息,那么就会按照双亲委派模型加载School类 。
类的加载过程:加载、连接、初始化,其中连接包括:验证、准备、解析 。
执行类的加载的是类加载器,它分为:启动类加载器、扩展类加载器、应用类加载器和自定义加载器 。
School类是ClassPath下的文件,它的类加载是应用类加载器,当应用类加载器按照ClassLoader+包名+类名查找对应的.class文件时,如果找不到这个文件就会抛出ClassNotFoundException异常,如果找到了则进行类的加载,并生成对应的Class类对象 。这个时候在元空间中就有了School的类元数据了 。
【java如何引用对象 如何创建对象】为对象分配内存空间
接下来就需要计算对象占用的空间大小,基本类型除了long和double是8个字节,byte和boolean是1个字节,char和short是2个字节,其他基本类型都是4个字节,引用类型也是4个字节 。
内存大小计算好之后在堆中划分一块内存空间给新对象 。大部分情况下,对象是在新生代的Eden区中分配,如果此时Eden区没有足够的内存空间进行分配,虚拟机将发起一次Minor GC 。但是当我们为一个很长的字符串或者数组分配内存时,这种类型的大对象需要连续的内存空间,可以直接在老年代进行分配,这样做可以避免Eden和两个S区发生大量的内存复制 。但是大对象可能会导致连续空间不足而提前触发GC,我们开发中也应该尽量避免大对象 。
内存分配有两种方式:指针碰撞和空闲列表分配 。
指针碰撞:当内存使用的GC算法是标记整理或者复制算法时,内存是规整的,此时我们为对象分配内存只需要移动指针位置就可以 。Serial和ParNew使用的GC回收算法是标记复制算法,内存的分配就是指针碰撞的方式 。

推荐阅读