Java中对象的创建

new一个“对象”

 没有对象怎么办,那就new一个对象呗。

  那么关于Java中的对象到底是怎么创建出来的,在这里总结一下。

  Java对象的创建由五部分组成如下图:

图片

  1. 类加载检查

       通过new关键字,创建对象都会使用堆内存。当Java虚拟机遇到一条字节new码指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过,如果没有,那必须执行相应的类加载过程(双亲委派机制)。

  2. 内存分配

        在类加载检查完成后,Java虚拟机将会为新生对象分配内存。对象所需的内存在类加载完成后就可以确定。为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定(主要看垃圾回收器所使用的垃圾回收算法是否会产生内存碎片),而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

  • 标记-清除算法(Mark-Sweep)

       标记清除算法首先出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象。

       缺点:一是执行效率不稳定;二是会产生内存碎片的问题,内存空间碎片过多会导致以后程序在运行过程中需要分配较大对象的时候无法找到足够的连续内存而不得过早的触发一次垃圾收集动作。

  • 标记-复制算法(Mark-Copy)

       标记复制算法是将内存区域按照大小划分为大小相等的两块,每次只使用其中的一块(半区复制)。

       缺点:虽然解决了内存碎片的问题,但是代价是将可用内存减少了一半,空间浪费过多。

  • 标记-整理算法(Mark-Compact)

       根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

图片

 

图片

       选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的。

 

注意:内存分配中的并发问题

    在虚拟机中,对象的创建是非常频繁的行为,所有就会产生一个重要的问题,就是线程安全。在Java虚拟机中,采用以下两种方式来保证线程安全:

  1. CAS+失败重试:CAS(Compare-and-Swap)体现的是一种乐观锁的思想。所谓乐观锁就是,每次不加锁,而是假设不产生冲突而去完成某项操作,如果因为冲突失败就重试,知道成功为止。虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。

  2. TLAB(Thread Local Allocation Buffer,本地线程分配缓存):为每一个线程预先在Eden区分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于或者TLAB中的内存已用尽时,再采用CAS进行内存分配。

    3.初始化零值:内存分配完成后,虚拟机必须将分配到的内存空间(但不包括头对象)都初始化为零,如果使用了TLAB的话,这个动作也可以提前到TLAB分配时顺便进行,这一步操作保证了对象可以不赋初始值就可以直接使用,使程序能访问到这些字段的数据类型所对应的零值。

    4.设置对象头:初始化零值完成后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到这个类的元数据信息、对象的哈希码、对象的GC分代年龄等,这些信息存放在对象头中。

 

补充:对象头包括两类信息:

  1. 第一类是用于存储对象自身的运行时数据。(与对象自身定义的数据无关的额外存储成本。加锁与不加锁这些信息是不一样的。)

  2. 第二类是类型指针(class 指针),即对象指向它的类型元数据的指针,JVM通过这个指针来确定该对象是哪个类的实例。

    5.执行init方法:这时候从虚拟机的角度,一个新的对象就已经产生了。但从Java程序的角度,对象创建才刚刚开始,因为<init>方法还没有做执行,所有的字段还都是零,所以一般来说,执行new指令之后会接着执行<init>方法,把对象初始化,这样一个对象才算完全创建出来。

public class CreateTest {

    public static void main(String[] args) {
        Person person=new Person();
        person.setName("Alice");
    }
}

使用javap -v命令查看字节码文件:

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class Pojo/Person
         3: dup
         4: invokespecial #3                  // Method Pojo/Person."<init>":()
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String Alice
        11: invokevirtual #5                  // Method Pojo/Person.setName:(Ljava/lang/String;)V
        14: return

字节码文件中的invokespecial #3就是就是去常量池中查#3编号,结果是:

 #3 = Methodref          #2.#23         // Pojo/Person."<init>":()V  #23 = NameAndType        #8:#9          // "<init>":()V

即就是对Person进行初始化。然后转去23号,又进行了一次初始化。以上就是一个对象创建的全过程。

附上一道题,有兴趣的可以看一下:

String s1="a";
String s2="b";
String s3="a"+"b";//ab
String s4=s1+s2;//在堆中 new String("ab")
String s5="ab";
String s6=s4.intern();

//问
System.out.println(s3==s4);//false
System.out.println(s3==s5);//true
System.out.println(s3==s6);//true

String x2=new String("c")+new String("d");//new String("cd")
String x1="cd";//"cd"
x2.intern();

System.out.println(x1==x2);//false
//问:如果调换了最后两行代码的位置呢,如果是jdk1.6呢?
/*jdk1.8
String x2=new String("c")+new String("d");//new String("cd")
x2.intern();
String x1="cd";//"cd"
System.out.println(x1==x2);//true 都是常量池对象,也都同时存在堆中
*/
/*jdk1.6
String x2=new String("c")+new String("d");//new String("cd")
x2.intern();//"cd"
String x1="cd";//"cd"
System.out.println(x1==x2);//false
*/

 

主要知识点:Sting类和常量池(对象是在常量池中还是堆中)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值