17

Java 程序员知道 JVM 运行垃圾收集器,而 System.gc() 只是对 JVM 运行垃圾收集器的建议。如果我们使用 System.gc() 不一定会立即运行 GC。

如果我误解了 Java 的垃圾收集器,请纠正我。

除了依赖 Java 的垃圾收集器之外,还有其他方式进行内存管理吗?
如果您打算通过某种有助于管理内存的编程实践来回答这个问题,请这样做。

4

10 回答 10

16

下面是我以前写的小总结(我从某个博客上偷来的,但我不记得从哪里来的——所以没有参考,抱歉)

  1. 在 Java 中没有手动进行垃圾收集的方法。
  2. Java Heap为了垃圾回收分为三代。这些是年轻一代,终身或老一代,以及彼尔姆地区。
  3. 新对象在年轻代中创建,然后移动到老年代。
  4. 字符串池是在 Heap 的 Perm 区域中创建的,垃圾收集可以在 Perm 空间中进行,但依赖于 JVM 到 JVM。
  5. Minor 垃圾收集用于将对象从 Eden 空间移动到 Survivor 1 和 Survivor 2 空间,Major 收集用于将对象从年轻代移动到 Tenured 代。
  6. 每当应用程序发生重大垃圾收集时,线程在此期间停止,这将降低应用程序的性能和吞吐量。
  7. Java 6 中的垃圾收集几乎没有应用性能改进,我们通常使用 JRE 1.6.20 来运行我们的应用程序。
  8. JVM 命令行选项-Xms-Xmx用于设置 Java Heap 的起始大小和最大大小。根据我的经验,此参数的理想比例是 1:1 或 1:1.5,例如,您可以同时拥有–Xmx1GB–Xms或 –Xms 1.2 GB 和 1.8 GB。

命令行选项:-Xms:<min size> -Xmx:<max size>

于 2012-12-13T07:36:26.783 回答
16

关于 Java 内存管理要记住的最重要的事情是“取消”您的引用。

只有没有被引用的对象才会被垃圾回收。

例如,以下代码中的对象永远不会被收集,您的内存将被填满,只是什么都不做。

List objs = new ArrayList();
for (int i = 0; i  < Integer.MAX_VALUE; i++) objs.add(new Object());

但是如果你不引用那些对象......你可以尽可能多地循环而不会出现内存问题。

List objs = new ArrayList();
for (int i = 0; i  < Integer.MAX_VALUE; i++) new Object();

所以无论你做什么,确保你删除对不再使用的对象的引用(设置引用null或清除集合)。

垃圾收集器何时运行最好留给 JVM 来决定。好吧,除非您的程序即将开始执行使用大量内存且速度至关重要的事情,否则您可能会建议 JVM 在进入之前运行 GC,因为您可能会收集垃圾并继续使用额外的内存。否则,我个人认为没有理由跑步System.gc()

希望这可以帮助。

于 2012-12-13T07:38:54.187 回答
7

只是为了增加讨论:垃圾收集不是 Java 中内存管理的唯一形式

过去,在实现内存管理时,Java 中一直在努力避免 GC(请参阅Java 实时规范 (RTSJ))。这些努力主要致力于 Java 中的实时和嵌入式编程,GC 不适合 - 由于性能开销或 GC 引入的延迟。

RTSJ 特性

  • 不朽和范围内存管理 - 请参阅下面的示例。
  • GC 和 Immortal/Scoped Memory 可以与一个应用程序共存
  • RTSJ 需要经过特殊修改的 JVM。

RTSJ 优势:

  • 低延迟,没有 GC 暂停
  • 提供能够满足实时系统要求的可预测性能

为什么 RTSJ 失败/没有产生重大影响:

  • Scoped Memory 概念难以编程、容易出错且难以学习。
  • Real-time GC 算法的进步减少了 GC 暂停时间,使得 Real-time GC 在大多数实时应用程序中取代了 RTSJ。但是,Scoped Memory 仍然在不能容忍延迟的地方使用。

范围内存代码示例(取自范围内存使用示例):

import javax.realtime.*;
public class ScopedMemoryExample{

    private LTMemory myMem;

    public ScopedMemoryExample(int Size) {

       // initialize memory
       myMem = new LTMemory(1000, 5000); 
    }

public void periodicTask() {

  while (true)) {
    myMem.enter(new Runnable() {
        public void run() {
          // do some work in the SCOPED MEMORY
          new Object();
          ...
          // end of the enter() method, the scoped Memory is emptied.  
        }
    });
  }


}
}

在这里,预先分配了一个称为 ScopedMemory 的实现LTMemory。然后一个线程进入作用域内存,分配仅在计算期间需要的临时数据。计算结束后,线程离开作用域内存,立即清空特定作用域内存的全部内容。没有引入延迟,在恒定时间内完成,例如可预测的时间,没有触发 GC。

于 2012-12-13T21:41:40.333 回答
4

根据我的经验,在 java 中你应该依赖 JVM 本身提供的内存管理。

我在本主题中关注的重点是以您的用例可接受的方式对其进行配置。也许检查/理解 JVM 调整选项会很有用: http: //docs.oracle.com/cd/E15523_01/web.1111/e13814/jvm_tuning.htm

于 2012-12-13T07:35:56.903 回答
4

如果您使用 Java,则无法避免垃圾收集。也许有一些晦涩的JVM实现可以做到,但我不知道。

经过适当调整的 JVM 不需要任何 System.gc() 提示即可顺利运行。您需要的确切调整很大程度上取决于您的应用程序的功能,但根据我的经验,我总是使用以下标志打开并发标记和扫描选项-XX:+UseConcMarkSweepGC:此标志允许 JVM 利用 CPU 中的额外内核来清理后台线程上的死内存。它有助于大大减少执行垃圾收集时程序被强制暂停的时间。

于 2012-12-13T07:50:47.340 回答
3

好吧,GC 总是在那里——你不能创建超出它掌握范围的对象(除非你使用本机调用或分配一个直接字节缓冲区,但在后一种情况下,你并没有真正的对象,只是一堆字节)。也就是说,绝对有可能通过重用对象来规避 GC。例如,如果您需要一堆ArrayList对象,您可以根据需要创建每个对象,并让 GC 处理内存管理;或者您可以list.clear()在完成后调用每个人,然后将其放入其他人可以使用的队列中。

标准的最佳实践是不要进行这种重用,除非您有充分的理由(即,您已经分析并看到分配 + GC 是一个问题,并且重用对象可以解决该问题)。它会导致更复杂的代码,如果你弄错了,它实际上会使 GC 的工作变得更加困难(因为 GC 跟踪对象的方式)。

于 2012-12-13T07:35:30.103 回答
3

基本上,Java 中的想法是,您不应该处理内存,除非使用“new”来分配新对象并确保在使用完对象后没有对对象的引用。

其余的一切都故意留给 Java 运行时,并且——也是故意的——尽可能模糊地定义,以允许 JVM 设计人员在有效地这样做时拥有最大的自由。

打个比方:您的操作系统为您管理命名的硬盘空间区域(称为“文件”)。包括删除和重用您不想再使用的区域。您不会绕过该机制,而是将其留给操作系统

您应该专注于编写清晰、简单的代码,并确保您的对象得到正确处理。这将为 JVM 提供可能的最佳工作条件。

于 2012-12-13T07:35:51.260 回答
3

您说这System.gc()是对编译器的请求而不是命令是正确的。但是使用下面的程序,您可以确保它发生。

import java.lang.ref.WeakReference;

public class GCRun {

    public static void main(String[] args) {

        String str = new String("TEMP");
        WeakReference<String> wr = new WeakReference<String>(str);
        str = null;
        String temp = wr.get();
        System.out.println("temp -- " + temp);
        while(wr.get() != null) {
            System.gc();
        }
    }
}
于 2017-09-23T18:56:07.797 回答
2

我建议看看以下教程及其内容

这是一个由四部分组成的教程系列,用于了解垃圾收集的基础知识Java

  1. Java垃圾回收介绍

  2. Java 垃圾收集是如何工作的?

  3. Java 垃圾收集器的类型

  4. 监控和分析 Java 垃圾收集

我发现本教程非常有帮助。

于 2016-12-08T07:15:48.620 回答
2

在不需要时“取消”引用是使对象符合垃圾收集条件的最佳方法。

有 4 种方法可以对对象进行垃圾收集。

  1. 一旦不再需要,将引用指向 null。

    字符串 s = new String("Java"); 一旦不需要此字符串,您可以将其指向 null。

    s = 空;因此,s 将有资格进行垃圾收集。

  2. 将一个对象指向另一个对象,以便两个引用都指向同一个对象,并且其中一个对象符合 GC 条件。

    字符串 s1 = new String("Java");

    字符串 s2 = new String("C++");

将来如果 s2 也需要指向 s1 则;

s1 = s2;

然后具有“Java”的对象将有资格进行 GC。

  1. 一旦方法完成,在方法中创建的所有对象都有资格进行 GC。因此,一旦方法从线程的堆栈中被销毁,那么该方法中的相应对象将被销毁。

  2. Island of Isolation 是另一个概念,其中具有内部链接且没有外部引用链接的对象有资格进行垃圾收集。 垃圾回收的“孤岛”

示例:下面是android中Camera类的一个方法。了解开发人员如何在不需要时将 mCameraSource 指向 null。这是专家级代码。

public void release() {
        if (mCameraSource != null) {
            mCameraSource.release();
            mCameraSource = null;
        }
    }

垃圾收集器如何工作?
垃圾收集由称为垃圾收集器的守护线程执行。当有足够的内存可用时,这个恶魔线程的优先级很低,它在后台运行。但是当 JVM 发现堆已满并且 JVM 想要回收一些内存时,它会增加垃圾收集器线程的优先级并调用 Runtime.getRuntime.gc() 方法来搜索所有没有引用或空引用的对象和销毁那些对象。

于 2017-02-22T10:51:08.350 回答