搜索

首页  >  问答  >  正文

java在类的内部创建本类的对象是怎么做到的?不理解啊?

ringa_leeringa_lee2807 天前539

全部回复(8)我来回复

  • 黄舟

    黄舟2017-04-18 10:53:14

    先明确几个概念,java代码是跑在jvm中的,而jvm的内存区域划分为这么几个模块:

    • 程序计数器(Program Counter Register):程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。

    • 虚拟机栈(JVM Stack):一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。

    • 本地方法栈(Native Method Statck):本地方法栈在作用,运行机制,异常类型等方面都与虚拟机栈相同,唯一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一起使用。

    • 堆区(Heap):堆区是理解Java GC机制最重要的区域,没有之一。在JVM所管理的内存中,堆区是最大的一块,堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上讲,所有的对象都在堆区上分配内存(不过现代技术里,也不是这么绝对的,也有栈上直接分配的)。

    • 方法区(Method Area):(也被称为永久代),方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。

    • 直接内存(Direct Memory):直接内存并不是JVM管理的内存,可以这样理解,直接内存,就是JVM以外的机器内存,比如,你有4G的内存,JVM占用了1G,则其余的3G就是直接内存,JDK中有一种基于通道(Channel)和缓冲区(Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用。由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。

    明白这几个基本概念以后再来看看题主疑惑的地方。其实题主疑惑的是在java中,对象的引用是如何实现的。为什么可以在定义一个类的同时,定义自己的引用,同时如果再实例化了这个引用以后,难道不会导致无线循环引用下去吗?

    别急我们先来分析下java中一个引用是怎么实现的:

    一个Java的引用访问涉及到3个内存区域:JVM栈,堆,方法区。

      以最简单的本地变量引用:Object obj = new Object()为例:

    • Object obj表示一个本地引用,存储在JVM栈的本地变量表中,表示一个reference类型数据;

    • new Object()作为实例对象数据存储在堆中;

    • 堆中还记录了Object类的类型信息(接口、方法、field、对象类型等)的地址,这些地址所执行的数据存储在方法区中;

    具体的实现方式有很多种,句柄是其中一种,关系如图所示。

    看到这里应该就明白了。类本身的信息,类实例数据,以及指向对象的引用信息分别放在 java 的方法区和栈区以及堆区。

    在题主的例子中,java加载顺序是这样的:

    1. jvm先加载了方法区的类定义(但此时并没有实例化这个类)

    2. 因为 public static final Direction FRONT = new Direction(); 是个静态变量,所以这个变量也会在 jvm 第一次读取方法区定义时被装载进方法区中。

    3. 同时,这也意味着,在装载这个变量的同时,也在堆区实例化了这个类的实例。

    注意这里面的关键点,因为 FRONT 变量是静态变量,而加载类定义只会加载一次,所以这个静态变量也只可能加载一次。并不会像非静态变量一样因为循环引用重复实例化而导致栈溢出。

    回复
    0
  • 大家讲道理

    大家讲道理2017-04-18 10:53:14

    推荐你看看R大的回答

    先有Class还是先有Object?
    https://www.zhihu.com/questio...

    回复
    0
  • 天蓬老师

    天蓬老师2017-04-18 10:53:14

    说说你的理解,为什么类里面不能创建自己的对象?
    这几个变量加上了static后就变成了类的属性了,只会创建一次。

    回复
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-18 10:53:14

    如果自己都不能创建自己,那其他类就更不能了。这样的话这个类怎么实例化……

    回复
    0
  • 大家讲道理

    大家讲道理2017-04-18 10:53:14

    设计模式:单例模式

    回复
    0
  • PHPz

    PHPz2017-04-18 10:53:14

    本质是对java的面向对象编程的不理解。看看23种设计模式你可能就会理解

    回复
    0
  • 伊谢尔伦

    伊谢尔伦2017-04-18 10:53:14

    1. 构造函数也是一个方法。

    2. 具有 private 访问权限的方法表示私有的,只有本类可见。

    所以,本类可以调用具有 private 访问权限的构造函数实例化一个对象。

    回复
    0
  • 巴扎黑

    巴扎黑2017-04-18 10:53:14

    使用内部类的原因:每个内部类都能独立的继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)的实现,对内部类都没有影响。实际上内部类有效的实现了“多重继承”,就是说,内部类允许继承多个非接口类型。

    我们知道内部类自动拥有对外部类所有成员的访问权,那么这是如何做到的吗?当某个外部类对象创建了一个内部类对象时,此内部类对象必定会秘密的捕获一个指向那个外部类对象的引用。然后,在你访问外部类的成员时,就是用那个引用来选择外部类的成员。当然这些细节是编译器处理,并且这里的内部类是非static的。
    如果一个类都不能创建自己的类对象,那我要你这个类何用?啊,哈哈哈哈,开玩笑咯

    回复
    0
  • 取消回复