【问题标题】:Cap for Object instances in JavaJava中对象实例的上限
【发布时间】:2019-01-06 19:53:51
【问题描述】:

我正在试验对象实例,因为我想知道对于单个 Java 进程是否定义了最大数量的对象实例。所以我简单地创建了一个对象,它从构造函数中递归调用自己,如下所示:

public class A{

    private A a;

    public A(){
        a = new A();
    }
}

还有一个像这样的主类:

public class random {
    public static void main(String[] args) {
        A a = new A();
    }
}

这显然导致了 Stack Overflow 异常。谷歌搜索后,我发现每个 Java 线程都有自己的堆栈。那么为什么不在我原来的线程溢出之前产生一个新线程呢?所以我写了这个:

public class A{

    private A a;
    private B b;

    public A(int i, int lim){
        if (i < lim)
            a = new A(i + 1, lim, bufferedWriter);
        else{
            t = new Thread(b = new B(lim));
            t.start();
        }
    }
}

使用这样的可运行对象:

public class B implements Runnable {

    private int lim;
    private A a;

    public B(int lim) {
        this.lim = lim;
    }

    @Override
    public void run() {
        a = new A(lim, lim + 2500);
    }
}

我怀疑它会再次抛出异常或由于 RAM 不足而崩溃(这就是为什么我保持我的任务管理器打开,准备终止进程的原因)。几分钟后,我惊讶地发现 RAM 使用情况没有明显变化,我也没有遇到任何异常。所以我在eclipse中检查了我的运行配置,它也没有改变并设置为默认值。

为了进一步了解这个主题,我决定开始计算实例。第一次尝试使用简单的“System.out.println”是行不通的,因为 system.out-stream 运行的线程只会溢出。所以我决定将计数写入文件,每个线程一个文件。

我的课程现在看起来像这样:

主要:

public class random {

    public static void main(String[] args) {
        try {
            A a = new A(0, 2500, new BufferedWriter(new FileWriter(new File(0 + " - " + 2500))));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

答:

public class A {

    private A a;
    private Thread t;
    private B b;

    public A(int i, int lim, BufferedWriter bufferedWriter) {
        try {
            bufferedWriter.write(i + "\n");
            bufferedWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (i < lim)
            a = new A(i + 1, lim, bufferedWriter);
        else{
            t = new Thread(b = new B(lim));
            t.start();
            try {
                bufferedWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

乙:

public class B implements Runnable {

    private int lim;
    private A a

    public B(int lim) {
        this.lim = lim;
    }

    @Override
    public void run() {
        try {
            a = new A(lim, lim + 2500, new BufferedWriter(new FileWriter(new File(lim + " - " + (lim + 2500)))));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

同样,令人惊讶的是,我的 RAM 使用率相当稳定。我更担心我的磁盘存储,因为大约 2 分钟后,我的小程序创建了超过 6500 个文件(所以每秒约 33 个文件),每个文件中正好有 2500 个数字(我想......我没有费心计算)总数为 16.260.000。 Ofc 这还不足以填满我的磁盘,但我当时停止了它。

好的,如果你读到这里,你要么对我的小实验非常感兴趣,要么真的很想回答我的问题......所以这里是:

我从未设置对null 的任何引用,并且我创建的任何实例都被我的主类间接引用。所以我认为垃圾收集器不会触及我珍贵的实例(对吧?)。我的 RAM 怎么没有完全填满?或者至少 JVM 是如何达到分配的 RAM 限制的?如果我相信我的计数器有超过 1600 万个对象实例,甚至不计算 B 的实例,如果我让它继续下去,它会每秒创建 83.000 多个对象引用。 p>

另外,当我们讨论这个主题时:一个简单的对象会占用多少 RAM?我知道它显然与在对象中引用和使用了哪些变量和使用了多少变量有关,但我们只说一个完全空的对象。它会使用多少内存?

PS:我知道我可以通过在我的主类中同时启动线程来加速我的程序,但我的(主要)意图不是尽快用完我的所有磁盘空间,而是计算对象实例:)

【问题讨论】:

  • 我知道我写了很多,也许有点太多了。而且我知道我给他写了很多代码,有些人可能认为是不必要的。我明白那个。但我并不希望解决问题,但我实际上对某些事情是如何工作的有疑问。如果我对这个主题有任何误解,我希望人们在正确的地方纠正我,这只有在我展示我完整的思考过程时才有可能。此外,我想营造一种友好的氛围,并通过稍微修饰我的故事来邀请人们加入讨论,这样会产生更多的文字,但恕我直言,这也更有吸引力。
  • 您的 A 对象基本上需要大约 16 个字节或 RAM。 16.260.000 个对象乘以 16 个字节只有 260 MB,这对于当今的计算机来说是很小的数量。如果您只是用对象填充一个非常大的数组,您的测试会简单得多。或者,如果您只是在 Google 上搜索“Java 中一个对象使用了多少内存”。
  • 对象实例没有上限。内存有一个上限。如果你创建了太多的实例并且不允许 GC 收集它们,你最终会遇到 OutOfMemoryError,因为内存是有限的。
  • 另一个问题是你的对象被垃圾回收了。即使它们在线程中,因为没有保留对它们的引用并且不再使用它们(写入文件后),它们也会被销毁
  • 垃圾收集器看到你没有在其他地方使用它并删除它。可能会检查之前的 A 没有被引用等。否则它永远不会垃圾收集复杂的对象。

标签: java multithreading performance instance ram


【解决方案1】:

我认为您的测试的问题是您的对象仍然是垃圾收集。如果您创建一个线程并且在它完成工作(写入文件)后不继续引用它,则不再引用它并且 java 足够聪明来处理内存。

因此,为了使您的示例按预期工作,您需要保持对线程的引用。例如:

class MainClass {
    public static void main(String[] args) {
        List<B> objects=new ArrayList<>();
            while(true){
             objects.add(new B());
            }
        }
}       

class B implements Runnable {           
    @Override
    public void run() {         
        //do nothing
    }
}

理论上,对象创建没有限制。实际上,根据堆大小存在限制。堆是JVM分配的内存区域,用来存放对象。这就是为什么当你的程序用完内存(堆)时,你会遇到 OutOfMemoryError。

【讨论】:

  • 啊,我仍然在努力寻找打破我的参考链的点:) 因为我要去“Main->A->A->(2500x)->A->B- >A->(2500x)->(...)" 等等。并将每个 a 保存在之前的类中,并将 Bs 保存在调用它的 A 中。我知道链条必须在某处断开,否则就没有意义了:P非常感谢您的回答,这对我和我对Java的理解非常有帮助:)
  • 例如,如果您的 B 中有 A,但在执行后 B 的代码中的任何地方都没有引用它,并且它自己没有做任何事情,那么这个 A 和 B 一样被垃圾收集被垃圾收集。
  • 如果你有一个有房子的狗类和一个有狗的狗屋,那么这两个是耦合的,但如果它们都没有在该关系之外被引用并且它们没有在任何地方使用它们将是垃圾集。否则将无法使用引用来处理复杂对象。在我的示例中,它们存储在创建线程的调用的本地列表中,因此它们不会被垃圾收集并填充内存。我希望我能正确地解释 ;) GC 有点神秘,即使你认为你理解它
  • 是的,有点像 :D 感谢您的详细解释,我想我明白了^^
猜你喜欢
  • 1970-01-01
  • 2013-03-29
  • 2012-07-16
  • 2016-05-23
  • 1970-01-01
  • 2020-02-11
  • 1970-01-01
  • 2011-03-07
  • 1970-01-01
相关资源
最近更新 更多