Java类实例化 - 内存中有什么?

时间:2014-01-27 00:14:13

标签: java classloader instantiation

我有一个基本问题。考虑一下这个简单的代码:

class A{
   void someMethod(){
      B b = new B(); // Line 3
      B c = new B(); // Line 4
   }
}

当执行第3行时,将B类加载到内存中(即:我们为类型为'Class'的类型的对象分配物理空间(假设有一个id - classLaoder1.B)类型包含代码B类)。

问题1#接下来会发生什么? - 基于classLoader.B实际包含B信息的事实,创建了B类(代表b的状态)的实例(分配的物理内存)?

问题2#此外,在第-4行,内存中存在classLoader.B,因此在内存中创建了一个包含c状态的对象?

2 个答案:

答案 0 :(得分:7)

嗯,你的例子和描述有点模糊,无法以简短的方式回答你的问题。

您指的是不同的类加载器,但是在加载哪个类时您没有包含任何示例代码。在目前的形式中,代码甚至无法编译,因为缺少返回值 - 但让我们继续您的问题。

堆是JVM在启动时创建的内存区域,可能会在运行时动态增加和减少。它分为不同的部分。 YoungGen will hold short lived objects, OldGen will hold object states of objects that survived the YoungGen space and finally the PermGen space which as it names suggest should contain permanent class metadata and descriptors。因此,PermGen空间保留用于与类(如静态成员)绑定的类和东西,如果您处理提供某种热部署功能的应用程序服务器或插件机制,则必须处理它。 (更准确一点,在Sun的JVM中PermGen space is actually a separate part of memory并不真正属于堆,但不同的JVM供应商可能因此有不同的定义。

enter image description here Reference: Configuration and Setup of SAP JVM

调用someMethod()后可能会出现两种情况:

  • B已在应用程序启动时由应用程序类加载器加载
  • B包含在由子类加载器加载的类中

在第一种情况下,类定义的内存在堆的PermGen空间内在启动时分配,并且仅在应用程序关闭时释放。在后一种情况下,该类的内存也存储在堆的PermGen空间中,但是在调用应该加载类的类加载器的loadClass(...)时。这里,如果没有强引用指向该类加载器加载的任何类,则可以释放内存。通常,枚举或单例类(它们对自己有强引用)会阻止正确卸载那些加载的字节并因此产生内存泄漏。

如果你实现了其中一个应用程序框架并对其进行调试,那么你将会看到究竟发生了什么。要通过类加载器加载类,调用loadClass(...)方法,首先检查它是否已经加载了该类,然后询问他的父母是否知道这个类(这也检查她是否已加载该类或她父母,......)。只有在之前未加载类(通过此类加载器或任何父类)时,当前(子)类加载器才会执行findClass(...),进一步调用defineClass()实际上转换字节从一些输入文件或流到Class表示。该Class对象包含蓝图(方法的签名,包括参数的数量和类型,返回值和抛出的异常)。在尝试加载类时,通常扩展类以及定义的接口也会被加载(如果在类加载器树中尚未知道) - 但是包含成员的类型尚未加载!当要实例化类时,它们将被加载。

enter image description here Reference: How ClassLoader Works in Java

在创建新实例时,new运算符会在内部调用newInstance(...)方法并reserves memory for all members of that instance。因此,如果当前类加载器或其父类尚未知该成员的类型,则在分配任何值之前将加载它。然后,执行类的构造函数(根据使用new操作调用的构造函数),并将值分配给堆上变量占用的内存(通常在Eden空间中)。在内存中构造对象后,new运算符将返回对象的引用,并且该对象已准备好在您的代码中使用。

示例中的

c实例化方式与b相同 - 首先,类加载器必须检查是否已加载类B。由于之前已加载B,因此它只从其本地缓存中获取B并返回该类。接下来,在类上执行newInstance(...)方法以实例化新对象。因此,再次在堆上分配成员变量的内存 - 在初始检查之后是否已经加载了所需的类 - 执行构造函数并返回对新创建和初始化对象的引用。

如果您的班级有static methods or static members,他们将被分配到PermGen空间,因为它们属于该类并且在所有实例中共享。

有一点需要注意:如果c应由对等或对等的子类加载器(CL2)加载,而b由姐妹类加载器(CL1)定义(所以没有父实际上已经定义了这个类),对等类加载器CL2将加载(并定义)它自己的B版本,它似乎与姐妹的加载器CL1的版本相同,但它们实际上是Java的不同类,因为加载该类的类加载器实际上是类的一部分。这意味着CL1 - B!= CL2 - B,尽管两个版本共享相同的方法和字段。将c投射到b' B会导致ClassCastException

为了完整起见,尽管你没有要求这样做,但在调用方法时会发生不同类型的内存分配。传递的变量被压入堆栈,每个线程都有自己的实例,如果方法返回,则从堆栈中弹出(包括返回值)。此外,每个块({}之间的部分)创建一个新的堆栈帧(这就是为什么块内声明的变量对块外的区域不可见),其中该块的局部变量存储在。更多信息here

enter image description here Reference: Understanding Stack and Heap-Tutorial

答案 1 :(得分:0)

b和c是B类的实例,将它们视为变量,因此它们将分别存储在内存中。 b包含B类的信息(该类相当于一个结构),因此您可能有一个具有变量名称的Person,并且您有2个实例:p1和p2。每个人都有不同的名字和不同的记忆位置