一、Java虚拟机栈不存储运行时的数据,
比如声明一个String a="abc";,这个变量是在方法区还是堆呢?
如果存放在堆的话,这时就在堆里面分配一块内存,如果在声明一个String b="abc"
如果存放堆的话,也会在堆里面分配一块内存。
a==b;//true
如果a,b变量都在堆里面分配内存的话,就为false,所以常量的分配内存不是在堆里面分配的
而是在方法区里面分配的。方法区里面采用StringTable(HashSet)维护的。
如果存放了abc的话,b就存放布进去了。这时a与b就相等了。
String c=new String("abc");
c.intern()==a;//true
intern():方法的作用将内存地址移动到方法区的常量池里面
二、你的对象是怎么什么来的?
简介:java程序员不可不知的对象创建地测才能够步骤细节
1、对象创建的流程步骤包括哪些:比如 A a=new A();
①、虚拟机遇到一条new指令时,首先检查这个对应的类能否在常量池中定位到一个类的符号引用。如果能定位到的话,这个类就是在常量池里面存在的。
init方法:static修饰的方法/代码块。
②、判断这个类是否已被加载 ,解析和初始化(加载的步骤))
③、为这个新生对象在java堆中分配内存空间,其中java堆分配内存空间的方式主要有以下两种:几乎所有的对象和数组的创建都是在Java堆里面创建的。
A、指针碰撞
在上面的图代表堆内存。FREE代表的是空闲内存,USE代表已经使用内存。
当为对象分配一块内存的时候,会将使用的内存扩大,将空闲的内存缩小
中间的部分就是对象所分配的内存。将这种分配内存的方式称之为指针碰撞。
总结:Ⅰ、分配内存空间包括开辟一块内存和移动指针两个步骤
Ⅱ、两个步骤可能会非原子步骤可能出现并发问题,java虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。java虚拟机采用的是乐观锁的方式,会进行cas的判断,比如这边有两个步骤,在维护空闲列表的时候,就可以进行cas的判断预期值和插入的值是否进行匹配,如果匹配的话,就可以操作,如果不匹配,就进行重试,这就是cas的乐观锁的原理。
B、空闲列表
java堆内存对象的内存分布,有可能是不均匀的。比如说内存分布非常的乱的时候,就无法通过指针碰撞的时候给对象分配内存。称这种方式为空闲列表:存储堆内存的分配的空闲的地址
在Free区可能内存是一块一块的,空闲列表所维护的就是不规则,不连续的内存。这几块内存就可以通过空闲列表进行维护,维护的就是空闲内存的地址。如果当某个地址被用的时候,就可以从空闲列表里面剔除。
总结: Ⅰ、分配内存空间包括开辟一块内存和修改空闲列表的两个步骤
Ⅱ、两个步骤可能会非原子步骤可能出现并发问题,java虚拟机采用CAS配上失败重试的方式来保证更新操作的原子性。java虚拟机采用的是乐观锁的方式,会进行cas的判断,比如这边有两个步骤,在维护空闲列表的时候,就可以进行cas的判断预期值和插入的值是否进行匹配,如果匹配的话,就可以操作,如果不匹配,就进行重试,这就是cas的乐观锁的原理。
2、将分配的内存空间都初始化为零值:java虚拟机自己帮做的
3、设置对象相关的数据
GC分代年龄
对象的哈希码hashcode
元数据信息
4、执行对象的<init>方法,比如static方法/代码块是在创建对象之前创建的。
三、java对象内存布局:
A a1=new A();在创建对象的时候,会在堆中给这个对象A开辟一块内存。
那么这个A对象里面包含那几个部分呢?
数据类型,对象的属性,属性的值称之为实例数据
而且所有的类都继承Object类,hashCode的值也是属于对象里面的一部分。
hashcode肯定是存储在内存里面。
锁状态标识:代表当前对象是否持有锁
GC分代年龄:java虚拟机要对垃圾进行回收,将所回收的垃圾分成了新生代和老年代
java虚拟机在进行回收的时候就会分不同的年代,采用不同的回收的策略,回收算法进行回收
基本的回收算法有三种:标记清除,标记整理,复制算法。那么在不同的GC年代里面采用的回收算法就不一样。GC分代年龄就是在垃圾回收的时候所设置的概念。
锁状态:就是多线程在操作这个对象的时候,为了保证线程安全,所设置的概念。
比如说当前的线程占用了这把锁标识了这把锁,就是锁占用的状态。
线程持有的锁:就是这个线程所占有锁的指针。
对象头还有一个名词是 Mark Word
类型指针:为了定位在方法区里面是哪个类,指向方法区
对象头是在64位的系统里面,占的内存就是64位,如果在32位系统里面占用的内存是32位。
64位和32位都是8的倍数,对象在存储的时候会保持一种原则:保持8的倍数。
对其填充:存储的是对象的数据是占位符,因为对象头Header已经保证了8的倍数,实例数据无法保证8的倍数,那么对齐填充,为了实例数据保持8的倍数。
四、你是怎么访问你的对象的
简介:java程序员不可不知的对象访问定位方式
1、当我们在堆上创建一个对象实例后,就要通过虚拟机栈中的reference类型数据操作堆上的对象。现在主流大的访问方式有两种(Hotspot虚拟机中采用的是第二种)
①、使用句柄访问对象,即reference中存储的是对象 句柄的地址,而句柄中包含了对象实例数据与类型数据的具体的地址信息,相当于二级指针
对应另外一种访问是对象句柄访问
句柄访问是在堆里面开辟一块内存,用来存储句柄池:就是存储访问句柄的池子
也就是存储访问的指针。它会存储对象类型是数据的指针,和对象实例数据的指针。
②、直接指针访问对象,即reference中 存储的就是对象 地址,相当于一级指针。
写个main方法,main方法是对应java虚拟机中的栈
在方法栈的本地变量表里面,做一个对象的访问:User user=new User();
user.getId();
对User对象的访问主要访问的是,刚才写到在对象里面存储的是User对象的实例数据
除了实例数据还有对象的类型数据,下面的是实例数据
对象类型数据:Class文件有哪些类型,实例,实现的方法,接口,这些数据称之为对象的类型数据。
对象的访问就是访问它的对象的实例数据和对象的类型数据。
直接指针访问:局部变量表是在java栈里面的,里面存储对对象的访问的指针reference
通过reference就可以对应到堆上面的对象的实例数据,那么在堆里面也会存储对象的类型数据的指针,通过这个指针指向方法区的对象类型数据(Class文件肯定是放到方法区里面的)。
堆会存放对象类型数据的指针,和对象的实例数据。
直接访问就是对对象实例数据和对对象类型数据的指针的访问。
对比:
垃圾回收分析:方式1当垃圾回收移动对象时,reference中存储的地址是稳定的地址,不需要修改,仅仅需要修改对象句柄的地址;方式2垃圾回收需要修改reference中的存储地址
访问效率分析:方式二优于方式一,因为方式二只进行了一次指针定位,节省了时间开销,而这也是Hotspot采用的实现方式。