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

问题描述 投票:1回答:2

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

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

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

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

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

java classloader instantiation
2个回答
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供应商因此可能有不同的定义)

Reference: Configuration and Setup of SAP JVM

调用someMethod()时可能发生两种情况:

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

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

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

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应该由peer或peer的子类加载器(CL2)加载,而b是由姐妹类加载器(CL1)定义的(所以没有父实际上已经定义了类),对等类 - 加载器CL2将加载(并定义)自己的B版本,它似乎与姐妹的加载器CL1的版本相同,但它们实际上是Java的不同类,因为加载该类的类加载器实际上是类。这意味着CL1-B!= CL2-B虽然两个版本共享相同的方法和字段。因此,将c投射到bB会导致ClassCastException

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

Reference: Understanding Stack and Heap-Tutorial


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.