对象
- 对象的实例化
- 创建对象的方式
- new 对象
- 变形1:使用类的静态方法获得对象
- 变形2:xxxBuilder、xxxFactory的静态方法
- 反射
- Class的newInstance():反射的方式,只能调用空参的构造器,权限必须是public
- Constructor的newInstance(xxx):反射的方式,可以调用空参,带参的构造器,权限没有要求
- 使用clone():不调用任何构造器,当前类需要实现Cloneable接口,实现clone()
- 使用反序列化:从文件、网络中获取一个对象的二进制流
- 第三方库:Objenesis
- new 对象
- 创建对象的步骤:
- 1.对象对应的类加载、链接、初始化
- 2.为对象分配内存(这个阶段就可以确定对象的大小)
- 如果内存规整------指针碰撞(就是用一个指针指向内存已占用和未占用区域的分界线)
- 如果内存不规整------就是还在用的区域和没有用的区域混杂在一起------虚拟机要维护一个列表------空闲列表分配------记下没有使用的区域
- 一般是带有整理功能的虚拟机才会内存规整
- 3.处理并发问题
- 采用CAS+失败重试保证更新的原子性
- 每个线程预先分配一块TLAB
- 4.初始化分配到的空间------所有属性设默认值(零值),保证对象实例字段在不赋值时可以直接使用
- 5.设置对象的对象头(将对象的所属类,即类的元数据信息、对象的HashCode和对象的GC信息、锁信息等数据结构储存在对象的对象头中。这个过程的具体设置方式取决于jvm的实现)
- 6.执行init方法进行初始化(属性的显式初始化,代码块中初始化,构造器中初始化)
- 创建对象的方式
- 对象的内存分配
- 对象头(Header)
- 运行时元数据
- 哈希值
- GC分代年龄
- 锁状态标志
- 线程持有的锁
- 偏向线程ID
- 偏向时间戳
- 类型指针------指向类元数据InstanceKlass,确定该对象所属的类型
- 如果是数组,还需要记录数组的长度
- 运行时元数据
- 实例数据(Instance Data)
- 说明:它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)
- 规则:
- 相同宽度的字段总是被分配在一起
- 父类中定义的变量会出现在子类之前
- 如果CompactFields参数为true(默认为true):子类的窄变量可能插入到父类变量的空隙
- 对齐填充(Padding)(不是必须的,也没什么特别含义,就是用来占位的)
- 对象头(Header)
- 对象的访问定位
- 句柄访问
- 好处是 对象移动之后(标量替换,垃圾回收都会移动类),栈里面的变量不用改,很稳定
- 缺点是需要开辟额外空间,也不是一步到位地找到
- 直接引用(Hotspot采用)
- 好处是一步到位,不用开辟额外空间
- 句柄访问
直接内存
- 它是在Java堆外的、直接向系统申请的内存区间
- 不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域
- 所有用Java的内存分析工具,比如jvisualvm,JProfiler等,是看不出元空间,或者说是直接内存的大小的
- 程序产生的dump文件也看不到。所以,如果程序发生OOM,dump文件又比较小,有可能是涉及到NIO,直接内存的问题
- 来源于NIO,通过存在堆中的DirectByteBuffer操作Native内存
- 访问直接内存的速度会优于Java堆,即读写性能高
- 读写频繁的场合可以用直接内存
- Java的NIO库允许Java程序使用直接内存,用于数据缓冲区
- 也是有可能会OOM的,因为它受限于系统能给内存,并不是无限大。特别是当设置堆的内存特别大,导致剩下的内存不够的时候,就容易OOM
- 缺点
- 分配回收成本较高
- 不受jvm内存回收管理
- 直接内存大小可以通过MaxDirectMemorySize设置
- 如果不指定,默认与堆的最大值-Xmx参数值一致
- (对于直接内存有大小限制,我的理解是这个区域还是要向系统申请的,不是系统剩下多少就能用多少)