关于对象(面试官喜欢问的无聊的问题)

对象的创建过程

仅仅是创建:
1.calss loading加载class文件
2.linking

  • verification 检查class文件是否符合JVM规定
  • resolution 将类、属性,转换成直接引用原有的是符号引用
  • preparation 仅仅是给静态成员变量赋默认值

3.Initializing 调用初始化方法给静态成员变量赋初始值
前面三步 基本class已经加载完毕!
4.new 申请对象内存(在堆里开辟空间)
5.成员变量赋默认值
6.调用构造方法< init >

  • 成员变量顺序赋初始值
  • 执行构造方法语句

7.调用父类的构造方法
以上对象就基本创建成功。

对象在内存中的布局

首先对象包括普通对象及数组对象

普通对象

普通对象是由4个部分组成:

  • markword:没有具体的翻译哈,用来记录锁信息,GC的分代年龄(经过多少次垃圾回收还没有被回收掉的次数),还记录hashCode();
  • classPorinter:记录当前对象的class类型,例如T.class; data(实例数据):成员变量之类的属性;
  • padding(对齐):
    由于现代计算机都是由16,32,64位组成,为了提高效率不会一位一位的读取,而是一行或者8位的去读,所以对齐就是把对象位数对齐为被8整除的数。
数组对象

跟上面一样,就是多了个4位的数组长度。
证明:

public class Test {
    public static void main(String[] args) {
        System.out.println("Object普通对象:"+ObjectSizeAgent.sizeOf(new Object()));
        System.out.println("int数组对象:"+ObjectSizeAgent.sizeOf(new int[] {}));
        System.out.println("带实例数据:"+ObjectSizeAgent.sizeOf(new Y()));
    }

    private static class Y {
        //8 _markword
        //4 _oop指针
        int id;         //4
        String name;    //4
        int age;        //4
        byte b1;        //1
        byte b2;        //1
        Object o;       //4
        byte b3;        //1
    }
}
		输出结果:
		Object普通对象:16
		int数组对象:16
		带实例数据:32

普通对象Object 中的markword 8位 classPorinter 4位 实例数据空的,8+4=12 ,所以padding对齐4位=16 被8乘除。后面都是一样的

注意:这里是开启了压缩指针,和实例数据的引用类型,默认都是开启的,可以指定。Oops跟压缩指针不是一个东西

-XX:+UseCompressedClassPointers 为4位 不开启为8位、
-XX:+UseCompressedOops 为4位 不开启为8位

把参数设置成关闭的运行一波:-XX:-UseCompressedClassPointers

输出结果:
Object普通对象:16
int数组对象:24
带实例数据:40
//普通对象的话没有变 因为没有开启压缩指针 所以classProinter = 8位 加markword的8 其他都没有8+8=16
//其他同比

继续关闭Oops:-XX:-UseCompressedOops,这里就是把成员变量的引用类型关闭压缩了。两个同时都是关闭的哦

输出结果:
Object普通对象:16
int数组对象:24
带实例数据:48
//主要看Y这个类:markword 8,classPorinter 8,int id 4,String name(这里关闭压缩所以是8),
			   int age 4,byte b1 1 byte b2 1 Object o 没有开启压缩 所以是4  byte b3 1
			   8+8+4+8+4+1+1+8 +1=41 最后padding对齐7位 =48

注:一个是classProinter,一个是对象里的引用类型的压缩,不用搞混了。

Hotspot开启内存压缩的规则(64位机)
  • 4G以下,直接砍掉高32位
  • 4G - 32G,默认开启内存压缩 ClassPointers Oops
  • 32G,压缩无效,使用64位
  • 内存并不是越大越好

附带查看对象大小的类:Agent是在类加载的时候做的拦截做的一个操作。

1.创建项目:

package com.yedi;

import java.lang.instrument.Instrumentation;

public class ObjectSizeAgent {
    private static Instrumentation inst;

    public static void premain(String agentArgs, Instrumentation _inst) {
        inst = _inst;
    }

    public static long sizeOf(Object o) {
        return inst.getObjectSize(o);
    }
}

2.src目录下创建META-INF/MANIFEST.MF

Manifest-Version: 1.0
Premain-Class: com.yedi.ObjectSizeAgent //必须指定好Class

然后打成jar放到要运行的项目中加上vm参数:-javaagent:D:\java\code\ObjecSize\out\artifacts\ObjecSize_jar\ObjecSize.jar 指定生成的jar,就是先去这个jar。
之后就可以用了:
在这里插入图片描述

对象头具体包括什么?

对象头:接上部分的markword

hotspot/src/share/vm/oops/markOop.hpp中的说明:

//  32 bits:
//  --------
//  hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
//  size:32 ------------------------------------------>| (CMS free block)
//  PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
//  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
//  size:64 ----------------------------------------------------->| (CMS free block)
//
//  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
//  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
//  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
//  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

可以看到有两个 32 64位的 看64位的:

哈希码(hash) GC分代年龄(age) 锁标识位:

  • 01 无锁
  • 01 偏向锁
  • 00 轻量级锁
  • 10 重量级锁

偏向锁标识位(biased_lock)

  • 0 无锁
  • 1 偏向锁

偏向线程ID(JavaThread*) 偏向时间戳(epoch)
这里就可以看到其实锁信息是3个位

锁状态存储信息偏向锁标志(是否是偏向锁)锁标志
无锁状态hashCode,GC分代年龄001
偏向锁线程ID、偏向时间戳、GC分代年龄101
轻量级锁指向栈中锁记录的指针00
重量级锁指向互斥量(重量级锁)的指针 Monitor10
GC标记11

锁这些都是在synchronized 的锁升级的机制介绍了这里不做笔记。

对照上面的注释就可以看到很明白了!
这里指的主要的是:
当一个对象已经计算过identity hash code,它就无法进入偏向锁状态;
当一个对象当前正处于偏向锁状态,并且需要计算其identity hash code的话,则它的偏向锁会被撤销,并且锁会膨胀为重量锁;
重量锁的实现中,ObjectMonitor类里有字段可以记录非加锁状态下的mark word,其中可以存储identity hash code的值。或者简单说就是重量锁可以存下identity hash code。
请注意:用户自定义的hashCode()方法所返回的值跟这里讨论的不是一回事。

对象怎么定位

主流的访问方式有两种:

  • 句柄
  • 直接指针

如果是句柄访问的话,java堆里会开辟两块空间来划分句柄池,引用中存储的就是句柄的地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息。
使用句柄来访问的最大好处就是引用中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而引用本身不需要被修改。
在这里插入图片描述
如果是直接指针的话,引用就直接指向堆里的实例数据,在间接指向数据类型。
直接引用只需要考虑到java堆中怎么存放数据类型,引用中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销。使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问在Java中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本
在这里插入图片描述

先发布,后面还有几题整理下更新,有错别字求提示。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搞数学的小混混

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值