【对象实例化】入门知识点详解
对象实例化这部分理论和实践都很重要。虽然这部分内容不难理解,但是若想要彻底理解对象实例化的全过程仍需要对JVM有很深的理解。希望大家先把本章节涉及的基础部分吃透,然后根据自己能力自行深入挖掘,这样才能在面试中展现出自己的亮点。
下面先以问答的形式给出本节的线索,大家学习完本节后可以自问自答,检测下学习效果:
- 什么是对象实例化?
- 什么条件下才能执行对象实例化?
- 对象实例化大致分为哪些步骤?
- 对象的内存布局如何安排?
- 通过访问对象都能获取什么信息?
- 能讲讲Java-HelloWorld的全过程都发生了什么操作?
3.1知识点入门理解
3.1.1 原理解释
3.1.1.1 什么是对象实例化?
对象实例化有别与类加载。类具有抽象概念,加载了一个类的class文件将在内存的方法区中生成唯一的一个Class对象。而对象实例化一般将在堆中生成一个该类的实体,这个实体是我们实际接触和操作的目标。对象实例化最直观的体现如下:
Apple apple = new Apple();
apple.setColor("Red");
显然,new关键字导致了Apple类产生了一个实例化的对象。
3.1.1.2 对象实例化后保存在内存中的什么位置?
对象实例化后一般保存在堆中。堆的概念在JVM内存模型篇中已详细叙述,这里再举一个例子。堆可以理解为“垃圾”堆,从某种意义上来说,JVM主要在堆中进行垃圾回收。然而,随着JVM的迭代优化,有一些特殊情况使得实例化后的对象并不保存在堆空间上,当涉及JIT与栈上分配时就不会发生对象在堆空间分配内存。JIT相关内容在类加载章节中已有介绍,忘记的同学可以回顾下。
3.1.2 对象实例化过程
对象实例化大致过程如下:
- 类加载检查
- 分配内存
- 初始化零值
- 设置对象头
- 执行init方法
下面分阶段一一进行介绍。
3.1.2.1 类加载检查
实例化目标类前需要完成类加载,这是执行实例化的必要前提。当虚拟机执行new指令时,必须通过下面全部检查:
- 指令涉及的参数是否可在常量池中定位到相关符号引用
- 该符号引用对应的类是否完成类加载
这里再简单回顾下类加载的过程。
- 加载
- 通过类的全限定名搜索类文件,获取其存储的二进制字节流;
- 将二进制字节流所存储的静态结构加载到方法区中作为运行时结构;
- 在内存中生成一个Class对象作为静态结构的代理;
- 连接
- 验证
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
- 准备
- 分配内存
- 类变量初始化
- 解析
- 目标:类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符
- 验证
- 初始化
- 执行类构造器
()方法的过程
- 执行类构造器
3.1.2.2 分配内存
对象实例化需要为对象在内存上的分配空间,这就引起了以下几个子问题:
- Q1:在哪为对象分配内存?
- Q2:为对象分配多少内存?
- Q3:为对象分配内存时有哪些策略?
Q1解答
对象一般在堆空间分配空间。根据不同的GC策略,刚创建的对象所处位置略有不同。
以Serial、ParNew和ParallelScanage为代表的新生代垃圾回收器,其新生代内存空间无论是物理层面还是逻辑层面都具有连续的特性。新创建的对象进入Eden区,大部分对象都具有朝生夕灭的特点,少部分对象在经过晋升移动到老年代。当新建对象过大或被判定为长期存活的对象可能直接进入老年代,因此对象未必直接进入新生代的Eden区。
而采用G1作为垃圾回收器时,新生代和老年代空间未必物理连续,但是仍旧有新生代和老年代的概念,普通对象仍旧在E区进行内存分配,而大对象和长期存活的对象在O区进行内存分配。这里如果有同学对GC部分的内容不理解,可以暂时跳过,等看完GC部分的章节再回来理解。
Q2解答
为对象分配多少内存这个问题具有唯一确定的解。对象分配需要多大内存空间在类加载完成后就已经确定了。对象所需空间不会因为不同对象属性值的不同而发生改变。
Q3解答
在执行内存分配时一般有两种策略:
- 指针碰撞
- 空闲列表
采用指针碰撞时,利用一个指针作为已划分内存和未划分内存的分界点,通过不断移动指针记录未划分内存的起始地址。如下图,虚线填充部分表示该部分内存已分配:
空闲列表则应用于堆内存空间不连续的场景。虚拟机只能通过维护可用内存的列表记录可用内存。如下图,虚线填充部分表示该部分内存已分配,通过访问列表的record项可知哪块内存可用。
两种策略的选择很大程度上依赖Java堆是否规整:
- 例如,serial和paraNew自带标记整理,适用于指针碰撞。既然前面两种垃圾回收器保证对堆内存进行整理,那么堆空间必然可以分为已使用部分和未使用部分,分割点即为一个指针。
- 例如,CMS基于标记清除,适用于空闲列表分配内存。因为CMS本身不能对堆空间进行整理,所以可能存在内存碎片化的情况,导致查找和分配内存的复杂度较高。为了在分配内存时提高效率,不对堆空间全局扫描查找可用的内存空间,而采用预分配的思想从本地列表中查找可用内存。
3.1.2.3 初始化零值
内存分配完毕后,虚拟机将对该对象执行初始化。执行初始化是为了避免业务代码不显式赋值也能正常取值(通常是零值或false)。这里需要和类加载中的初始化注意区分,关键在于类加载中的设置目标是静态变量,即所有对象公有的变量;而对象实例化中,初始化的设置目标为类的成员变量。若在TLAB内分配内存,则初始化时机在分配TLAB内存时。
TLAB,全称Thread Local Allocation Buffer。JVM利用该结构为每一个线程预先在Eden区分配小部分内存(默认为Eden区大小的1%)。
当JVM执行内存分配时,优先在TLAB分配;当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用CAS进行内存分配。由于很多对象过小或存活时间过短,适合被快速被GC回收,所以小对象通常JVM会优先分配在TLAB。此外,TLAB上的分配由于是线程私有所以没有加锁解锁的开销,也避免锁竞争的发生。
下面简单看一下TLAB的实现源码:
class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {
friend class VMStructs;
private:
HeapWord* _start; // address of TLAB
HeapWord* _top; // address after last allocation
HeapWord* _pf_top; // allocation prefetch watermark
HeapWord* _end; // allocation end (excluding alignment_reserve)
size_t _desired_size;
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
“挨踢”行业行情日益严峻,企业招聘的门槛也随之越来越高,大厂hc少之又少。 庞大的知识体系下,不知道学什么、怎么学? 面试高频考点是什么、怎么回答才能得到面试官的青睐? 作为后端求职者,在Java的道路上越走越宽。 本专刊则针对Java面试考点上,精讲JVM知识点,为大家的大厂求职路保驾护航! 针对如今校招痛点,深入详解JVM知识考点,列出高频真题并详细解答!探索JVM精髓!
查看18道真题和解析
安克创新 Anker公司福利 881人发布