1.对象创建(初始化)的过程:
以下代码是使用JClassLib插件可查看的字节码信息
0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init> : ()V>
7 astore_1
8 return
步骤0 new来申请内存,属性会有默认值
步骤4 调用特殊的方法构造方法
步骤7 建立关联,引用与对象之间
步骤8 执行结束
对象创建的过程会涉及对象半初始化状态的问题:cpu有时为了执行效率的提升会把一些指令提前执行,也就是所谓的指令重排序,关于指令重排序的条件,我在这里不赘述了。在发生指令重排序时可能把步骤7提前执行:将对象与引用关联,那么这时候可能构造方法还没有完成,也就造成了对象的版初始化状态。
这个现象其实在DCL中也有涉及,就是实例对象要不要加volatile的问题。DCL已经保证了多线程情况单例的安全性,但是在并发量及其大的时候获取单例,可能会出现一个半初始化的对象,所以要加volatile,其实这种情况极少发生,排查起来也无从下手。
volatile修饰变量的作用:
1.禁止指令重排序
2.线程间可见
2.对象在内存中的存储布局:就是new出来占据的内存空间
第一部分markword 8字节
作为同步监视器时会在mark work标记锁的类型
mark work中记录的三大信息:hash值、锁信息、GC信息(对象回收的状态,年龄),
这几个信息拎出来会涉及到java的锁机制,垃圾回收算法(三色标记算法...)
第二部分class pointer(类指针) 4字节
指向.class的指针
第三部分instance data(实例数据) 引用指针4字节
属性
第四部分padding(对齐)
jvm是8字节对齐,不够就加,满足求余8整除即可
64位的虚拟机8字节。
使用-XX:-UseCompressedClassPointer来取消类指针压缩。
使用-XX:-UseCompressedOops取消普通类型指针压缩
3.对象怎么分配
a.先尝试在栈上分配,只要弹栈,操作系统就可以帮你回收,栈的空间小Xss来调整栈的大小
再栈空间分配的好处:自动弹出,不需要GC介入。
在栈上分配的原则:
1.可以进行标量替换的对象
2.逃逸分析:再栈中分配的对象,当栈帧执行完毕,弹栈,对象消失,那么那个外部的
引用就成了空指针了
b.不满足栈分配的原则看对象大小,如果过大,直接往Old扔,如果不大往TLAB(Thread Local Allocation Buffer)
线程本地分配缓冲区,每个线程在new出来时,线程都有一个线程的私有空间,也是在Eden区。
有限往自己的兜里装。但始终在Eden区
----------------------------------------------------------------------------
64位和32位系统指的是指针的寻址空间的位数,那为什么jvm里的引用指针为什么是4字节
因为进行了指针压缩。32位指针的寻址空间有4G个,即4G个地址,如果一个空间是8bit那么
就可以控制32G内存
Class载入内存
Class文件如何放入内存:
class———— loading--->linking--->initializing ————gc
linking分为三步:
verification--->preparation--->resolution
在preparation阶段为静态变量赋默认值
initializing指的是类的初始化
以下代码:交换T001中两个静态属性的位置,输出有何变化?
public class T {
public static void main(String[] args) {
System.out.println(T001.count);
}
}
class T001{
public static T001 t = new T001();
public static int count = 2;
private T001(){
count ++;
}
}
在类装入内存的过程中count经历了从 0 ---> 1 ---> 2的变化,所以这个结果输出为 2
public class T {
public static void main(String[] args) {
System.out.println(T001.count);
}
}
class T001{
public static int count = 2;
public static T001 t = new T001();
private T001(){
count ++;
}
}
在类载入内存中count的值经历了 0 ---> 2 ---> 3,所以最后输出结果为 3