java 对象动态堆
2013-11-10 16:47:55 zhengqiqiqinqin 阅读数 727

原文地址:http://blog.csdn.net/renfufei/article/details/14669513

翻译人员: 铁锚
翻译日期: 2013年11月8日
原文链接:  What do Java objects look like in memory during run-time?

我们知道,函数每次被调用时,在内存中都有自己的活动记录(activation record),称为栈空间(stack). Java 的方法在调用时在 JVM 栈中为其分配一个栈帧(Java栈空间的一个片段),可以称之为方法栈. 原则上,所有对象都在堆空间(Heap)中分配.

java对象在内存中是怎样分配的呢? 一旦对象在堆中分配了空间,那本质上就是一系列的字节. 那么如何找到对象中某个特定的属性域呢? 编译器通过一个内部表来保存每个域的偏移量.

下图是 Base 类的一个对象内存分布图,Base(基类)没有定义任何方法,关于方法在内存中的分布请看接下来的内容.


图1
如果还有另一个派生类 "Derived" 继承了基类"Base".那么内存分布将如下图所示:

图2
子类对象和父类对象拥有同样的内存分布,当然,子类对象需要更多的空间来存放新的属性域.
这种分配方式的好处在于 Base类型的指针 如果指向了子类Derived的对象, 依然在开头的地方"看见"Base对象.
因此, 子类对象(Derived)采用 父类引用(Base) 来进行的操作 保证是安全的,因此在运行时不需要动态地检查 Base 引用的实际类型.
用样的道理,方法也可以放到object空间的开始处,如下图所示.

图3

然而这种实现方式是没有效率的.假若一个类有很多方法(例如20个),那么每个对象就要持有20个指针,相应的,每个对象都需要20个指针的内存空间,这会导致创建对象变慢,所占空间更大。
优化手段是创建一个 虚拟函数表(vtable,虚表),虚表是一个指向特定类的成员函数的指针数组. 如下图所示:

图4

* 以上是我对斯坦福大学编译器讲座所做的笔记,该讲座非常生动有趣。
参考文献:
1. Stanford Compilers Lectures
2.  JVM
相关文章:

  1. What does a Java array look like in memory?
  2. Top 5 Questions about C/C++ Pointers
  3. 简述Java内存泄露
  4. An example of C++ dot vs. arrow usageYarpp

2015-12-04 11:58:31 u011974987 阅读数 1547

最近在重构代码中出现了一个问题。导致功能不能使用,后来才发现,java基础没掌握好,栈和堆都还没区别开来,后来找到了问题的所在。

栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
Java的堆是一个运行时数据区,类的对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,因此,用堆保存数据时会得到更大的灵活性。变量所需的存储空间只有在运行时创建了对象之后才能确定。Java的垃圾收集器会自动收走这些不再使用的数
据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和引用对象,栈里存的却是堆的首地址名;就像引用变量。 

Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在栈中分配的内存只是一个指向这个堆对象的指针(引用变量)而已。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 

在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。

引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。

例子:
String s=new String("123");
这个s就在栈里面,而他的"123"这个对象在堆里面。 s 指向"123";
3.就对象本身而言,他的所有属性的值如果不是单例或者静态的,就是存储在堆里面的。一个类的所有对象的属性值都在堆里面并且占用不同的内存空间,而一个类的方法只在方法区里占一个地方

栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3; 

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。

这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响
到b的值。


a-------》3
b-------》3 ,这个3是共享存在的,a,b指向同一个地址,然后存进4,4也是共享存在的。

a,b先指向3个共享,然后a指向4,b仍然指向3.

3和4是存放在栈中,a和b是引用对象



要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一
个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。 


String是一个特殊的包装类数据。可以用:
String str = new String("abc");------------------开辟新的空间,创建新对象
String str = "abc"; -----------------------------使用共享的字符串

两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。
而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”,相当于指向“abc”的地址。

比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一个对象的。

String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的对象。每一次生成一个。



因此用第二种方式创建多个”abc”字符串,第一种方式在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度
,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相
等,是否有必要创建新对象,从而加重了程序的负担。

另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能
只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。


2017-11-16 11:42:31 u013970971 阅读数 318
翻译人员: 铁锚
翻译日期: 2013年11月8日
原文链接:  What do Java objects look like in memory during run-time?

我们知道,函数每次被调用时,在内存中都有自己的活动记录(activation record),称为栈空间(stack). Java 的方法在调用时在 JVM 栈中为其分配一个栈帧(Java栈空间的一个片段),可以称之为方法栈. 原则上,所有对象都在堆空间(Heap)中分配.

java对象在内存中是怎样分配的呢? 一旦对象在堆中分配了空间,那本质上就是一系列的字节. 那么如何找到对象中某个特定的属性域呢? 编译器通过一个内部表来保存每个域的偏移量.

下图是 Base 类的一个对象内存分布图,Base(基类)没有定义任何方法,关于方法在内存中的分布请看接下来的内容.


图1
如果还有另一个派生类 "Derived" 继承了基类"Base".那么内存分布将如下图所示:

图2
子类对象和父类对象拥有同样的内存分布,当然,子类对象需要更多的空间来存放新的属性域.
这种分配方式的好处在于 Base类型的指针 如果指向了子类Derived的对象, 依然在开头的地方"看见"Base对象.
因此, 子类对象(Derived)采用 父类引用(Base) 来进行的操作 保证是安全的,因此在运行时不需要动态地检查 Base 引用的实际类型.
用样的道理,方法也可以放到object空间的开始处,如下图所示.

图3

然而这种实现方式是没有效率的.假若一个类有很多方法(例如20个),那么每个对象就要持有20个指针,相应的,每个对象都需要20个指针的内存空间,这会导致创建对象变慢,所占空间更大。
优化手段是创建一个 虚拟函数表(vtable,虚表),虚表是一个指向特定类的成员函数的指针数组. 如下图所示:

图4

* 以上是我对斯坦福大学编译器讲座所做的笔记,该讲座非常生动有趣。
参考文献:
1. Stanford Compilers Lectures
2.  JVM
相关文章:
2017-05-06 10:06:22 sinat_24032823 阅读数 194

对象

堆中的对象

堆是垃圾GC的主要区域。那么堆中对象实例就有必要进行了解一下了。

一、对象的创建

Java是面向对象语言,所以在Java程序运行的时候每时每刻都会有对象创建。而编写代码的时候,创建对象仅仅是通过关键字new来实现的。

即虚拟机遇到一条new指令后,会先去检查相应的类是否被加载、解析和初始化过,没有的话就加载相应的类来创建出这个对象来。这个过程实际上会更复杂一些。首先虚拟机会先检查类加载,通过加载后就会为即将创建的对象分配内存。而如果可分配的内存不是完整的一段内存的话,还需要维护一个列表来记录哪些内存块是可用的,然后再取出一块足够大的空间划分给对象实例,并更新记录列表。

分配完内存后还需要对分配到的空间进行初始化,包括一些必要的设置。并把设置完的一些信息存放到对象头(Object Header)中。而到这里后,对于虚拟机来说,对象已经产生了,但是对于程序来说,对象的创建才仅仅只是开始而已。这个时候还会执行一个init方法,把对象按照我们的意愿进行初始化后,才是真正的一个对象完整的创建出来。

二、对象的内存布局

上面有提到了对象头是用来存放一个对象的必要信息的。其实,对象在内存中的存储区域可分为3块:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

  1. 对象头:对象头分为两部分,一部分是存储对象自身的运行时数据(HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等),另一部分则是类型指针,即对象指向它的类元数据的指针,虚拟机可以通过这个指针确定这个对象是哪个类的实例。当然查找对象的元数据信息也不一定是要经过对象本身的类型指针的。
  2. 实例数据:对象真正存储数据的有效部分。
  3. 对齐填充:仅仅起到了占位符的作用。因为对象的大小必须是8字节的整数倍。因为对象头正好是8字节的倍数,而对象实例数据部分就不一定了,所以需要通过对象填充来不全。

三、对象的访问定位

Java程序是通过栈上的reference数据来操作堆上的具体对象的。由于在Java虚拟机规范中红只规定了一个指向对象的引用,没有定义这个引用应该通过什么方式去定位和访问对象的具体位置,所以对象的访问方式就取决于虚拟机实现而定了。目前主流的访问方式有使用句柄和直接指针两种。

1、句柄访问

Java堆会划分一块内存来作为句柄池,reference存储的就是对象的句柄地址,句柄则包含了对象实例数据与类型数据各自的具体地址信息了。
句柄访问对象

2、直接指针访问

Java堆对象的布局就需要考虑如何防止访问类型数据的相关信息了,而reference中存储的就是对象地址。
指针访问对象

句柄范根的好处在于reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄的实例数据指针,reference本身不会修改。
直接指针访问的好处在于速度更快,节省一次指针定位的时间开销,对于需要频繁访问对象的Java来说,这个时间开销的节省累积起来还是很可观的。

2012-07-19 14:06:12 Jacular 阅读数 0
Java 类型, 引用变量与堆对象
1)Java 类, 是用来描述事物类型的. 如: 书
<图书馆管理系统>中的概念: 书Book
书的特征: 书号, 书名, 编号, 作者, 价格...
2) 类的声明:
public class Book{
long id;
String name;
String isbn; //书号
String[] authors; //作者
double price;
}
3) 引用变量与对象的创建过程:
A Java 的内存管理与分配:
栈是一块Java 使用内存区域, 使用的方式:LIFO(后来者居上)
堆也是一块Java 使用内存区域, 使用方式: 无需随机分配
B 局部变量与堆对象空间分配
Java 局部变量在栈中分配, 是按照变量的类型分配
对象在堆中分配, 按照类的属性(实例变量)分配空间
C 变量类型与分配
变量分为: 基本类型和引用类型
基本类型变量的空间大小: 就是基本类型的空间大小, 值是基本类型
的值
引用变量的值是一个对象的地址值, 引用变量通过地址引用了一个堆
对象.引用类型变量的占用空间大小和值管理是"透明的(不可看见)",
由Java 系统管理: 变量占用空间以及值的管理, 都是透明的.
4) 对象的属性默认自动初始化的. 自动初始化为"零"值, 引用为null
5) 在null 引用上调用属性或方法, 会发生运行时异常

Java 对象在堆中的内存结构

阅读数 22418

翻译人员:铁锚翻译日期:2013年11月8日原文链接: WhatdoJavaobjectslooklikeinmemoryduringrun-time?我们知道,函数每次被调用时,在内存中都有自己的活动记录(activationrecord),称为栈空间(stack). Java的方法在调用时在JVM栈中为其分配一个栈帧(Java栈空间的一个片段),

博文 来自: renfufei

java 对象在堆中的内存结构

阅读数 543

原文链接: WhatdoJavaobjectslooklikeinmemoryduringrun-time?我们知道,函数每次被调用时,在内存中都有自己的活动记录(activationrecord),称为栈空间(stack). Java的方法在调用时在JVM栈中为其分配一个栈帧(Java栈空间的一个片段),可以称之为方法栈. 原则上,所有对象都在堆空间(He

博文 来自: qilixiang012

java 模拟动态对象

阅读数 11

  packageorg.zero.base;importjava.util.HashMap;importjava.util.Map;publicclassDynamicObject{ privateMap&lt;String,Object&gt;atts=null; privateMap&lt;String,IDynamicMe...

博文 来自: hwp897457487

Java 对象在堆中的内存结构

阅读数 25

翻译人员:铁锚翻译日期:2013年11月8日原文链接: WhatdoJavaobjectslooklikeinmemoryduringrun-time?我们知道,函数每次被调用时,在内存中都有自己的活动记录(activationrecord),称为栈空间(stack). Java的方法在调用时在JVM栈中为其分配一个栈帧(Java栈空间的一个片段),可以称之...

博文 来自: lgshendy

Java 中的堆和对象的区别

阅读数 2

2019独角兽企业重金招聘Python工程师标准>>>...

博文 来自: weixin_34101229
没有更多推荐了,返回首页