1、对象访问:
在java语言中,对象访问如何进行的?
(1)最简单的访问,也会涉及java栈、java堆和方法区这三个最重要的内存区域之间的关联关系。
Object obj = new Object();
<1> “Object obj”:反应到java栈的本地变量表中,作为一个reference类型数据出现。
<2> “new Object()”:反应到java堆中,形成一块存储了Object类型所有实例数据值的结构化内存。这块内存的长度不是固定的。
<3> java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。
(2)由于reference类型在java虚拟机规范里只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到java堆中的对象的具体位置。主流的访问方式有两种:
<1> 使用句柄;
Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,此句柄中包含了:“对象实例数据”和“类型数据”各自具体的地址信息。具体如下图:
<2> 直接指针。
Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址。如下图:
<3> 两种方式的优缺点:
(1) 句柄优点:reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要被改变。
(2) 直接指针优点:速度很快,它节省一次指针定位的时间开销。Sun HotSpot而言,就是使用此种方式。
2、Java对象的大小:
基本数据的类型的大小是固定的。非基本类型的java对象,其大小就值得商榷了。
在java中,一个空object对象的大小是8byte。这个大小只是保存堆中一个没有任何属性的对象的大小。如下语句:
Object obj = new Object();
这样在程序中完成了一个java对象的生命,但是它所占的空间为:4byte+8byte。期中4byte就是上面所说的java栈中保存引用的所需要的空间,而8byte则是java堆中对象的信息。因为java中非基本类型的对象都需要继承Object对象,所有不论什么样的java对象,其大小都必须大于8byte。
有了Object对象的大小,我们就可以计算其他对象的大小了。
Class NewObject {
int count;
boolean flag;
Object ob;
}
其大小为:空对象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小
(4byte)=17byte。但是因为Java在对对象内存分配时都是以8的整数倍来分,因此大于17byte的最接
近8的整数倍的是24,因此此对象的大小为24byte。
程序计数器、虚拟机栈、本地方栈3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的。
以下内容待定:
2、内存分配
java对象所占用的内存主要从堆上进行分配,堆是所有线程共享的,因此堆上分配内存时需要进行加锁,导致了创建对象开销比较大。当堆上空间不足时,会触发GC,如果GC后空间仍然不足,则出OutOfMemory错误信息。
JDK提升内存分配的效率,会为每个新创建的线程在新生代的Eden Space上分配一块独立的空间。这个空间称为TLAB(Thread Local Allocation Buffer),其大小有JVM根据运行情况计算而得。
可通过:-XX:TLABwasteTargetPercent来设置TLAB可占用的Eden Space的百分比,默认值为1%。
JVM将根据这个比率,线程数量及线程是否频繁分配对象来给每个线程分配合适大小的TLAB空间。在TLAB上分配内存时不需要加锁,因此JVM在给线程中的对象分配内存时会尽量在TLAB上分配,如果对象过大或TLAB空间已用完,则仍然在堆上进行分配。
因此在编写java程序时,通常多个小的对象比大的对象分配起来更加高效。可通过在启动参数上增加-XX:+PrintTLAB来查看TLAB空间的使用情况。
除了从堆上分配及从TLAB上分配外,还有一种基于逃逸分析直接在栈上进行分配的方式。