创建对象的方式
对象创建的步骤
判断类是否加载
为对象分配内存
-
内存规整
-
内存不规整
-
如何选择
处理并发安全问题
比较与交换(CAS)
TLAB
初始化分配到的空间
设置对象的对象头
执行init方法进行初始化
静态变量的初始化
- 默认初始化
在类的加载时进行的。 - 然后在准备阶段如果有静态代码块,则类变量还会进行正式的初始化
属性的初始化
- 默认初始化
默认初始化,是赋“零”值,因为有默认初始化,所以属性不需要赋初值也能使用。但是方法内部的局部变量就没有默认初始化,所以必须赋初值,否则会报错。 - 显式/代码块内 初始化
显式初始化是在声明变量的同时赋初始值;
代码块内初始化是在代码块内赋初始值,属性是非静态的,因此代码块也不能是静态的代码块。 - 构造方法中初始化
每个类都必须有一个构造方法,即使自己不写,也会有一个默认的构造方法。
关于变量初始化的代码测试
public class InitializationTest {
//声明同时赋初始值
public int num = 1001;
public double weight;
public String name;
public static long initial = 100L;
//在静态代码块中赋初值
static {
initial = 10086L;//如果将这句话注释掉,那么类变量的值就是0,说明非静态代码块
// 在类的加载过程中不会执行。因此不能使用非静态代码块来初始化静态变量
//静态代码块中不能使用非静态变量,因此不能给weight赋初值
// weight = 86.7;
}
//在代码块中赋初值
{
initial = 10097L;//这句话是没用的。或者说在类的加载过程中不会被执行到,
// 只有new一个对象的时候,才会执行。
weight = 88.88;
}
//在构造方法中初始化
public InitializationTest() {
name = "tom";
}
public static void main(String[] args) {
System.out.println(InitializationTest.initial);//结果为10086
InitializationTest init = new InitializationTest();
System.out.println(init.weight);//可以看到在new一个对象之后,结果变了,为10097
System.out.println(InitializationTest.initial);
System.out.println(init.name);
System.out.println(init.num);
}
}
通过代码测试发现:
-
类变量只能在静态代码块中初始化(这个只能是相对于非静态代码块来说的,当然也可以在声明的时候直接赋初值),或者确切的说是在类的加载过程中,只会执行静态代码块中的代码。这个具体是在准备阶段执行的赋初始值操作。
下面是类构造器clinit的指令,可以看到确实只有声明阶段的赋值和静态代码块中的赋值在这里面出现了。0 ldc2_w #18 <100> 3 putstatic #5 <com/wenchu/alpha/base/InitializationTest.initial> 6 ldc2_w #20 <10086> 9 putstatic #5 <com/wenchu/alpha/base/InitializationTest.initial> 12 return
-
属性(域)则因为不能在静态代码块内使用,则可以在声明时,在普通代码块中,在构造方法中赋初值。这些代码只有在new的时候才会被执行。
下面是对应的对象构造方法中的指令,可以看到在声明阶段、非静态代码块中、还有构造方法中的赋初值语句最终还是都以构造方法内代码实现的:0 aload_0 1 invokespecial #1 <java/lang/Object.<init>> 4 aload_0 5 sipush 1001 8 putfield #2 <com/wenchu/alpha/base/InitializationTest.num> 11 ldc2_w #3 <10097> 14 putstatic #5 <com/wenchu/alpha/base/InitializationTest.initial> 17 aload_0 18 ldc2_w #6 <88.88> 21 putfield #8 <com/wenchu/alpha/base/InitializationTest.weight> 24 aload_0 25 ldc #9 <tom> 27 putfield #10 <com/wenchu/alpha/base/InitializationTest.name> 30 return
-
也可以在普通(非静态)代码块中引用静态变量并赋值,但是这个赋值只能在执行了一次new之后才会生效,因此不能算是对类变量的初始化,可以看成普通的赋值操作。见上面的代码,可以看到10097这个初值是被放到了对象的构造方法内了。
对象的内存布局
对象头
- 运行时元数据
- 类型指针
实例数据
- 相同宽度放一起
- 父类放在子类前
对齐充填
不是必须的,没有特殊含义
对象内存布局图
对象访问定位
-
图示
-
目的
为了使用这个对象 -
用法
如何通过引用访问到对象的呢?通过引用的定位 -
实现
实现有两种方式:-
句柄池
缺点:两次访问才能找到
优点:当对象移动位置(例如发生了GC)时,不需要改变局部变量表中引用的值 -
直接指针
上面在讲内存布局时用的那个图就是这种架构。
优缺点正好和上面相反。
hotspot使用的这种方式。
-