jvm【三】jvm中对象的创建与访问


[toc]

对象创建

1.new关键字

虚拟机遇到new 关键字的时候, 首先去常量池中寻找有没有这个类的符号引用,并且检查该引用的类是否已经被 加载,解析,和初始化(执行类构造器)过,如果没有则会先执行该类的加载过程, 在通过检查后, 虚拟机为该新生对象分配内存。

2.分配内存

为对象分配内存有俩种方式:

一种分配方式是“指针碰撞",在内存规整的时候, 已使用的内存在一侧,未使用的内存在一侧时,中间为指示器指针,这个时候的内存分配就是 把指示器指针向未使用的区域移动至创建的对象大小相等的距离。

另一种分配方式是“空闲列表”,当内存不规整时,虚拟机必须 在不连续的内存空间寻找一块适合对象大小的内存区域并使用一个列表去维护创建的每一个区域,并更新列表上的记录。

选择那种分配方式是由堆内存是否规整决定,又由所采用的gc是否带有压缩整理功能决定。

面临并发时时,有可能存在,虚拟机给对象A分配内存时指针还未来得及改变,这个时候同时又有B对象使用指针来分配内存解决这个问题的两种式:

一种是对分配内存空间的操作进行同步处理 ,虚拟机采用的CAS和失败重试的方式保证更新操作的原子性,另一种是把内存分配的动作按照线程划分在不同的空间进行,即每一个线程都在java堆中预先分配一小块内存。又称本地线程分配缓冲(Thread Local Allocation Buffer,简称TLAB)。 TLAB用完时分配新的TLAB 时需要同步锁定操作。虚拟机设置使用TLAB,可以通过-XX:+/UseTLAB参数设定。

3.初始化对象内存空间

内存分配完成之后,虚拟机对该对象分到的内存空间初始化为零值(除了对象头),如果使用了TLAB ,这一工作也可以提前至TLAB分配时进行。 初始化零值这一步也是为什么对象刚创建就可以使用的原因。

4.对象设置

虚拟机对对象进行设置,比如对象是那个类的实例,对象的哈希值,gc分带年龄等,这些信息都存在对象的头之中。之后就是执行方法,到此类创建结束。

内存布局

对象在内存中分三块区域, 对象头,实例数据,对齐填充。

java对象头部分俩个部分:一部分是用来存对象本身的运行时数据,比如:哈希code, gc分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等

另一部分是类型的指针,指向类元数据,虚拟机通过这个指针确定它属于那个类的实例,查找对象的元数据信息,并不一定需要经过对象本身。

实例数据部分是对象真正存储的有效信息,也是代码中所定义的类型的字段内容,无论是父类还是子类的都需要记录。

对齐填充不是必然存在的,它只是起占位符的作用,HotSpot VM的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,如果不是则需要通过对齐填充来补全。

对象访问

对象访问会涉及到Java栈、Java堆、方法区这三个内存区域。 如下面这句代码:

Object objectRef = new Object();  

假设这句代码出现在方法体中,"Object objectRef” 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。而“new Object()”这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。

reference类型在java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。

句柄访问方式

句柄访问方式:java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。

指针访问方式

指针访问方式:reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。

这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)。

转自1:http://my.oschina.net/u/272065/blog/506903?p={"output"=>nil, "content"=>"[toc]\n\n## 对象创建\n### 1.new关键字\n虚拟机遇到new 关键字的时候, 首先去常量池中寻找有没有这个类的符号引用,并且检查该引用的类是否已经被 加载,解析,和初始化(执行类构造器)过,如果没有则会先执行该类的加载过程, 在通过检查后, 虚拟机为该新生对象分配内存。\n### 2.分配内存\n为对象分配内存有俩种方式: \n\n一种分配方式是“指针碰撞\",在内存规整的时候, 已使用的内存在一侧,未使用的内存在一侧时,中间为指示器指针,这个时候的内存分配就是 把指示器指针向未使用的区域移动至创建的对象大小相等的距离。\n\n另一种分配方式是“空闲列表”,当内存不规整时,虚拟机必须 在不连续的内存空间寻找一块适合对象大小的内存区域并使用一个列表去维护创建的每一个区域,并更新列表上的记录。\n\n>选择那种分配方式是由堆内存是否规整决定,又由所采用的gc是否带有压缩整理功能决定。\n\n当 面临并发时时,有可能存在,虚拟机给对象A分配内存时指针还未来得及改变,这个时候同时又有B对象使用指针来分配内存解决这个问题的两种式:\n\n一种是对分配内存空间的操作进行同步处理 ,虚拟机采用的[CAS](http://www.blogjava.net/xylz/archive/2010/07/04/325206.html)和失败重试的方式保证更新操作的原子性,另一种是把内存分配的动作按照线程划分在不同的空间进行,即每一个线程都在java堆中预先分配一小块内存。又称本地线程分配缓冲(Thread Local Allocation Buffer,简称TLAB)。 TLAB用完时分配新的TLAB 时需要同步锁定操作。虚拟机设置使用TLAB,可以通过-XX:+/UseTLAB参数设定。\n\n### 3.初始化对象内存空间\n内存分配完成之后,虚拟机对该对象分到的内存空间初始化为零值(除了对象头),如果使用了TLAB ,这一工作也可以提前至TLAB分配时进行。 初始化零值这一步也是为什么对象刚创建就可以使用的原因。\n\n### 4.对象设置\n\n虚拟机对对象进行设置,比如对象是那个类的实例,对象的哈希值,gc分带年龄等,这些信息都存在对象的头之中。之后就是执行方法,到此类创建结束。\n\n## 内存布局\n对象在内存中分三块区域, 对象头,实例数据,对齐填充。\n\njava对象头部分俩个部分:一部分是用来存对象本身的运行时数据,比如:哈希code, gc分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等\n\n另一部分是类型的指针,指向类元数据,虚拟机通过这个指针确定它属于那个类的实例,查找对象的元数据信息,并不一定需要经过对象本身。\n\n实例数据部分是对象真正存储的有效信息,也是代码中所定义的类型的字段内容,无论是父类还是子类的都需要记录。\n\n对齐填充不是必然存在的,它只是起占位符的作用,HotSpot VM的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,如果不是则需要通过对齐填充来补全。\n## 对象访问\n对象访问会涉及到Java栈、Java堆、方法区这三个内存区域。\n如下面这句代码:\n\n{% highlight java %}\nObject objectRef = new Object(); \n{% endhighlight %}\n假设这句代码出现在方法体中,\"Object objectRef” 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。而“new Object()”这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。\n \nreference类型在java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。\n\n### 句柄访问方式\n句柄访问方式:java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。\n\n\n### 指针访问方式\n指针访问方式:reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。\n\n\n这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)。\n\n转自1:[http://my.oschina.net/u/272065/blog/506903?p={{page}}](http://my.oschina.net/u/272065/blog/506903?p={{page}})\n转自2:[http://blog.csdn.net/java2000_wl/article/details/8015105](http://blog.csdn.net/java2000_wl/article/details/8015105)\n\n", "relative_path"=>"posts/2015/2015-12-15-jvm【三】jvm中对象的创建与访问.md", "path"=>"posts/2015/2015-12-15-jvm【三】jvm中对象的创建与访问.md", "url"=>"/2015/12/jvm-%E4%B8%89-jvm%E4%B8%AD%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%88%9B%E5%BB%BA%E4%B8%8E%E8%AE%BF%E9%97%AE/", "collection"=>"posts", "next"=>#<Jekyll::Document posts/2015/2015-12-15-jvm【二】jvm内存模型&jvm常用参数.md collection=posts>, "previous"=>#<Jekyll::Document posts/2015/2015-12-14-java中io包源码详解.md collection=posts>, "id"=>"/2015/12/jvm【三】jvm中对象的创建与访问", "draft"=>false, "categories"=>["jvm"], "date"=>2015-12-15 00:00:00 +0000, "layout"=>"post", "title"=>"jvm【三】jvm中对象的创建与访问", "tags"=>["jvm"], "slug"=>"jvm【三】jvm中对象的创建与访问", "ext"=>".md", "excerpt"=>"

[toc]

\n"}过,如果没有则会先执行该类的加载过程, 在通过检查后, 虚拟机为该新生对象分配内存。\n### 2.分配内存\n为对象分配内存有俩种方式: \n\n一种分配方式是“指针碰撞\",在内存规整的时候, 已使用的内存在一侧,未使用的内存在一侧时,中间为指示器指针,这个时候的内存分配就是 把指示器指针向未使用的区域移动至创建的对象大小相等的距离。\n\n另一种分配方式是“空闲列表”,当内存不规整时,虚拟机必须 在不连续的内存空间寻找一块适合对象大小的内存区域并使用一个列表去维护创建的每一个区域,并更新列表上的记录。\n\n>选择那种分配方式是由堆内存是否规整决定,又由所采用的gc是否带有压缩整理功能决定。\n\n当 面临并发时时,有可能存在,虚拟机给对象A分配内存时指针还未来得及改变,这个时候同时又有B对象使用指针来分配内存解决这个问题的两种式:\n\n一种是对分配内存空间的操作进行同步处理 ,虚拟机采用的CAS和失败重试的方式保证更新操作的原子性,另一种是把内存分配的动作按照线程划分在不同的空间进行,即每一个线程都在java堆中预先分配一小块内存。又称本地线程分配缓冲(Thread Local Allocation Buffer,简称TLAB)。 TLAB用完时分配新的TLAB 时需要同步锁定操作。虚拟机设置使用TLAB,可以通过-XX:+/UseTLAB参数设定。\n\n### 3.初始化对象内存空间\n内存分配完成之后,虚拟机对该对象分到的内存空间初始化为零值(除了对象头),如果使用了TLAB ,这一工作也可以提前至TLAB分配时进行。 初始化零值这一步也是为什么对象刚创建就可以使用的原因。\n\n### 4.对象设置\n\n虚拟机对对象进行设置,比如对象是那个类的实例,对象的哈希值,gc分带年龄等,这些信息都存在对象的头之中。之后就是执行方法,到此类创建结束。\n\n## 内存布局\n对象在内存中分三块区域, 对象头,实例数据,对齐填充。\n\njava对象头部分俩个部分:一部分是用来存对象本身的运行时数据,比如:哈希code, gc分带年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等\n\n另一部分是类型的指针,指向类元数据,虚拟机通过这个指针确定它属于那个类的实例,查找对象的元数据信息,并不一定需要经过对象本身。\n\n实例数据部分是对象真正存储的有效信息,也是代码中所定义的类型的字段内容,无论是父类还是子类的都需要记录。\n\n对齐填充不是必然存在的,它只是起占位符的作用,HotSpot VM的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,如果不是则需要通过对齐填充来补全。\n## 对象访问\n对象访问会涉及到Java栈、Java堆、方法区这三个内存区域。\n如下面这句代码:\n\n{% highlight java %}\nObject objectRef = new Object(); \n{% endhighlight %}\n假设这句代码出现在方法体中,\"Object objectRef” 这部分将会反映到Java栈的本地变量中,作为一个reference类型数据出现。而“new Object()”这部分将会反映到Java堆中,形成一块存储Object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定。另外,在java堆中还必须包括能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些数据类型存储在方法区中。\n \nreference类型在java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。\n\n### 句柄访问方式\n句柄访问方式:java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。\n\n\n### 指针访问方式\n指针访问方式:**reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据。\n\n\n这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)。\n\n转自1:http://my.oschina.net/u/272065/blog/506903?p={{page}}\n转自2:http://blog.csdn.net/java2000_wl/article/details/8015105\n\n", "relative_path"=>"posts/2015/2015-12-15-jvm【三】jvm中对象的创建与访问.md", "path"=>"posts/2015/2015-12-15-jvm【三】jvm中对象的创建与访问.md", "url"=>"/2015/12/jvm-%E4%B8%89-jvm%E4%B8%AD%E5%AF%B9%E8%B1%A1%E7%9A%84%E5%88%9B%E5%BB%BA%E4%B8%8E%E8%AE%BF%E9%97%AE/", "collection"=>"posts", "next"=>#<Jekyll::Document posts/2015/2015-12-15-jvm【二】jvm内存模型&jvm常用参数.md collection=posts>, "previous"=>#<Jekyll::Document posts/2015/2015-12-14-java中io包源码详解.md collection=posts>, "id"=>"/2015/12/jvm【三】jvm中对象的创建与访问", "draft"=>false, "categories"=>["jvm"], "date"=>2015-12-15 00:00:00 +0000, "layout"=>"post", "title"=>"jvm【三】jvm中对象的创建与访问", "tags"=>["jvm"], "slug"=>"jvm【三】jvm中对象的创建与访问", "ext"=>".md", "excerpt"=>"

[toc]

\n"}) 转自2:http://blog.csdn.net/java2000_wl/article/details/8015105

nemotan /
Published under (CC) BY-NC-SA in categories jvm  tagged with jvm