【问题标题】:How do you properly set up a thread to be GC'd in Java?您如何正确设置要在 Java 中进行 GC 处理的线程?
【发布时间】:2010-12-28 19:57:59
【问题描述】:

我想知道如何设置一个已创建的线程进行 GC。我做了一个简单的多线程服务器来接受来自客户端的连接,但是每次客户端连接时,内存使用量都会增加大约 80k ......可以理解。问题是客户端和服务器断开连接后,我不知道如何从快速增长中恢复过来。

这是设置线程的主类:

public static void main(String[] args) {

   try {
      serverSock= new ServerSocket(9999);
   } catch (IOException e1) {
      e1.printStackTrace();
   }

   while (true){
      try {
         Thread t = new server(serverSock.accept());
         t.start();
         t = null;
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

这是基本的服务器线程:

public class server extends Thread {
     private Socket serverSocket = null;
     public static String command = null;

     public server(Socket serverSocket2) {
      super("server");
      serverSocket = serverSocket2;
     }

     @Override
     public void run(){
      BufferedReader fromClient = null;
      PrintStream toClient = null;
      String line = null;
      String[] user = new String[2];

      try {
       serverSocket.setSoTimeout(10000);
       fromClient = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
       toClient = new PrintStream(serverSocket.getOutputStream());
       System.out.println("Got connection");
                System.out.println("Terminating server");
       fromClient.close();
       toClient.close();
       serverSocket.close()
            } catch (NumberFormatException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
      } catch (IOException e) {
       System.out.println("Connection established, but timeout on" +
         " message recieve may have occured");
       e.printStackTrace();
      }
}

现在,我究竟如何让这个线程……死掉然后消失,而不是导致当前发生的内存泄漏?

另外,我是 Java 新手 - 没关系使用线程,所以如果很明显,请尽量保持友好。感谢您提供的任何帮助!

【问题讨论】:

  • t 设置为null 完全没有效果,因为在该语句之后块立即结束并且t 超出范围。 @Pete:那么如果在线程运行时将t 设置为null 会怎样?线程不会发生任何不好的事情。
  • 我的印象是,除非我们保留指向线程子类的指针,否则它可能会在超出范围后进行 GC(与任何其他对象一样)。我猜……不是吗?

标签: java multithreading garbage-collection


【解决方案1】:

GC 只在需要时运行,它似乎不需要,因此可以清理的对象/线程不是。这并不意味着存在内存泄漏。这意味着不需要内存。

顺便说一句:您不应该直接扩展 Thread。相反,我建议您使用 Executors.newCachedThreadPool()。这将更有效地处理短期线程。

同样当 ServerSocket 抛出异常时,这是不可恢复的。这确实意味着一旦失败,它将无休止地失败。如果抛出异常,通常将其打印并继续,就好像它没有发生一样。

一个不会无休止地失败的主要版本是

    public static void main(String... args) throws IOException {
    ServerSocket serverSock = new ServerSocket(9999);
    ExecutorService es = Executors.newCachedThreadPool();
    while (true)
      es.submit(new ClientHandler(serverSock.accept()));
  }
}
class ClientHandler implements Runnable {
  private final Socket socket;
  public ClientHandler(Socket socket) { this.socket = socket; }

  public void run()  {
    while(true) {
       // do something with the socket. 
    }
  }
}

【讨论】:

  • 由于D4N14L是Java新手,需要指出的是,与其直接扩展Thread,不如实现Runnable
  • @R. Bemrose,我本来打算推荐 Callable,但 Runnable 是更好的选择,谢谢。
  • @R. Bemrose:是的,我确实在早期版本中实现了 Runnable,所以我只是在试验……只是碰巧发布了扩展线程的代码。不过还是谢谢。
  • Executors.newCachedThreadPool() 这个想法很有帮助。我已经喜欢这个社区了,谢谢!
【解决方案2】:
  1. 您如何知道存在内存泄漏?对我来说似乎没有泄漏

  2. 您无需执行任何操作。垃圾收集器始终在运行,它不会[区分]从线程引用的对象。

  3. 我唯一看到的是,serverSock.accept() 会在那里阻塞吗?如果不是,那么您将创建线程的速度与 while 循环运行的速度一样快。我不是这里的专家,所以如果在启动线程之前接受调用阻塞,那么这可能没问题。

【讨论】:

  • ServerSocketaccept 块,是的。
  • 我调整了您的答案以更清楚@Falmarri。 GC 确实关心对象是否被引用,是否来自线程。坦率地说,无论如何,一切都是线程——甚至是主线程。
【解决方案3】:

在这两个答案之间,我没有看到此信息。 GC 将清理不活动的线程,因此如果您让线程退出 run() 方法,它将被 GC 清理。如果没有其他活动线程从它的堆栈中引用该内存或没有静态根引用它(Falmarri 的回答点 2 对此错误或至少措辞错误),则从该线程的堆栈引用的任何内存都将被清除。 GC 大部分是免提和自动的,在正常情况下,您几乎不需要做任何工作来实际释放内存。

现在你看到创建线程时增加的内存是不同的。你如何测量这个 80K 的跳跃?您是否使用任务管理器、顶部或某些操作系统工具来查看该凹凸?操作系统工具对于真正理解 JVM 内部发生的事情并没有多大帮助。它可能正在释放操作系统工具看不到的 JVM 内部内存。原因是 Java 在内存分配方面很吝啬。一旦它分配了更多的内存,即使它不使用它,它也会保留它。原因是操作系统向进程分配内存的速度很慢。如果以后再次需要它,并且统计数据表明它会保留它,那么它会更快地抓住它而不是释放它。如果操作系统内存不足或使用的内存非常少,Java 将返还内存。不过这种情况很少见。 Java 的内存分配非常高效。

如果您想查看内存或查看更多详细信息,我建议您使用 JVM 附带的 jconsole。它将向您展示 JVM 的内部结构,因此您可以看到构成堆的总内存、堆和 4 个空间(eden、survivor、tenured、Perm Generation)。您还可以在 Code Gen 空间中查看您的代码占用了多少内存。你甚至可以看到你的活动线程在进程中运行,所以如果你有卡住的线程,你也可以在那里诊断。您可以选择请求 GC,以便查看您的应用如何使用内存。

关键在于,如果您认为自己有泄漏,您可以使用此工具开始理解它。它超级简单,内容丰富且免费。如果您有泄漏,您可能需要像 jprofiler 这样的内存分析器来告诉您更多详细信息。

【讨论】:

  • 谢谢,这对像我这样的新手来说非常有用。是的,我正在使用任务管理器...我会记住 jConsole 以备日后的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多