- java虚拟机遇到一个new指令
- 检查new引用代表的类是否被加载,解析和初始化
- 加载过
- 没有加载过,先执行相应类的加载过程
- 虚拟机为对象分配内存
- 对象所需要的内存大小在类加载过后便可以确定
- 为对象分配空间的过程等同于把一块确定大小的内存从java堆中划分出来。
- java堆绝对规整
- 绝对规整解释:所有用过的内存放在一边,空闲的内存放在另一边中间放着一个指针作为分界点的指示器。
- 分配过程:指针向空闲空间那边挪动一段与对象大小相等的距离。这种分配方式称为“指针碰撞”。
- java堆不规整
- 不规整解释:已使用的内存和未使用的内存相互交错,虚拟机维护一个列表,记录那些内存是可用的。
- 分配过程:分配时从列表中找一块足够大的空间划分给对象实例。并更新列表上的记录。这种分配方式称为“空闲列表”。
- java堆是否规整是由所采用的垃圾收集器是否带有压缩整理功能决定的。
- 对象创建时并发问题
- 描述:对象创建在虚拟机中是非常频繁的,因此在并发情况下是线程不安全的。可能指针正在为A对象分配内存,对象B又同时使用了原来的指针来分配内存。
- 解决方案
- 第一种方式:
- 分配内存的动作进行同步处理
- 第二种方式:
- 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲。哪个线程要分配内存就在哪个线程的TLAB上分配。只有TLAB用完并分配新的TLAB时才需要同步锁定。
- TLAB解释:TLAB全称ThreadLocalAllocBuffer,是线程的一块私有内存,如果设置了虚拟机参数 -XX:UseTLAB,在线程初始化时,同时也会申请一块指定大小的内存,只给当前线程使用,这样每个线程都单独拥有一个Buffer,如果需要分配内存,就在自己的Buffer上分配,这样就不存在竞争的情况,可以大大提升分配效率,当Buffer容量不够的时候,再重新从Eden区域申请一块继续使用,这个申请动作还是需要原子操作的。
- 虚拟机将分配到的内存空间都初始化为零值(不包括对象头),这一操作保证了对象的实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值
- 虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息。这些信息存在对象头(Object Header)之中。
- 执行对象初始化,即把对象按照程序员的意愿进行初始化。
- 前面的学习我们知道,java虚拟机栈保存的是对象的句柄或者对象地址。所以访问对象需要通过栈上的局部变量表(reference)来操作堆上的具体对象。因为reference只是一个只想对象的引用,并没有定义这个引用应该通过什么方式定位,访问堆中的对象的具体位置,所以对象访问的方法取决于虚拟机的实现方式。目前主流的访问方式有以下两种
- 句柄式:
- java堆中有一块内存作为句柄池,reference存储的就是对象的句柄地址。句柄包含了对象实例数据与类型数据各自具体的地址信息。
- 优点:对象被移动时(垃圾回收时,整理内存时,很有可能被移动)只会改变句柄中的实例数据指针,不用改变reference
- 直接指针访问:
- reference存储的直接就是对象地址
- 优点:快,省略了一次句柄到具体位置的访问时间。因为对象比较多,所以这个时间很可观