Java OutOfMemoryError:无法创建新的本机线程

Java OutOfMemoryError:无法创建新的本机线程,java,url,tomcat,tomcat5.5,Java,Url,Tomcat,Tomcat5.5,我在tomcat上托管了两个不同的A和B web应用程序。B应用程序托管在4个物理系统上,使用单独的tomcat。应用程序A通过单独的tomcat托管在第五个物理系统上。每当应用程序A上发生登录/注销事件时,它都需要将此通知发送给所有4个B应用程序。应用程序A为每个事件创建一个线程,该线程使用以下代码向所有4个B应用程序发出请求: public class SendNotification implements Runnable { private String action; priv

我在tomcat上托管了两个不同的A和B web应用程序。B应用程序托管在4个物理系统上,使用单独的tomcat。应用程序A通过单独的tomcat托管在第五个物理系统上。每当应用程序A上发生登录/注销事件时,它都需要将此通知发送给所有4个B应用程序。应用程序A为每个事件创建一个线程,该线程使用以下代码向所有4个B应用程序发出请求:

public class SendNotification implements Runnable {
  private String action;
  private String loginid;
  private Thread thread;
  StringBuilder sb  =     new StringBuilder();

  public static SendNotification getInstance() {
        return new SendNotification();
  }
  public void sendNotification(String action, String loginid){
        this.action = action;
        this.loginid = loginid;
        sb.append(loginid)
        .append("_")
        .append(action)
        .append(new Date().getTime());
        thread = new Thread(this, sb.toString());
        thread.start();
  }

  public void run(){
        String methodName =     "run";
        int   len = 4;
        for(int i=0;i<len;i++){
              synchronized(this){
                    StringBuilder url = new StringBuilder()
                    .append(<Server_B_i_IP:PORT>)
                    .append("&action=").append(action)
                    .append("&loginid=").append(loginid);
                    URL urlObj                    =     null;
                    InputStream openStream  =     null;
                    try {
                          urlObj = new URL(url.toString());
                          openStream = urlObj.openStream();
                    } catch (Exception ex) {
                          ex.printStackTrace();
                    } finally {
                          try {
                                if(openStream != null){
                                      openStream.close();
                                }
                                openStream  =     null;
                                urlObj            =     null;
                                url               =     null;
                          } catch (IOException e) {
                                e.printStackTrace();
                          }
                    }
              }           
        }
        destroyObject();
  }

  private void destroyObject() {
        thread      =     null;
  }
如何解决上述问题

这些应用程序在带有jdk1.5.0_13的Tomcat 5.5.17上运行HTTPS

如何解决上述问题

问题的直接原因是内存不足,无法创建新线程。(此内存不是来自正常堆,因此更改
-Xmx
不会有帮助。)

实际原因是创建线程的速度比退出线程的速度快。我怀疑这句话是问题的根源:

synchronized (this) {
    // talk to network ...
}
问题在于
将是您用来发送通知的
发送通知
的实例。如果重用该对象,您会发现存在严重的瓶颈,导致网络任务一次只能执行一个

除上述问题外,您的方法还有缺陷,因为每次调用
sendNotification
,它都会覆盖
sendNotification
实例的状态,而不进行任何同步。简单地说,您不应该使用单例来保存多个线程的状态


另一种可能是您没有重用
SendNotification
实例,因此同步问题不是问题的原因。在这种情况下,下一个可能的原因是创建新线程的速度比服务器处理线程的速度快

直接的解决方案是使用一个有边界的线程池,这样一次活动的线程就不会超过(比如)20个。做这类事情有标准的类

但是,应用程序/平台执行这些任务的速度将受到限制。如果您在一段较长的时间内超过该限制,您的应用程序的队列将增加,您将遇到麻烦。如果这是可能的,那么您的应用程序需要某种减载策略


SSL堆栈似乎正在创建一个线程,以便在后台执行SSL协商。您似乎正在打开与远程服务器的连接,然后立即将其关闭。。。没有读任何东西。这种不寻常的使用模式可能会导致SSL协商线程泄漏。在关闭流之前,尝试从流中读取一个字节。这很可能会导致当前线程
加入到SSL协商线程中,而不会处于不确定状态

如果发生了这种情况,那么可能是您正在使用的Java SSLSocketImpl代码中的错误。您应该升级JVM和Tomcat,以获得最新的(不是最新的!)安全性、性能和bug修复。它可能无法解决此问题。。。但无论如何你都应该这么做


更新-用谷歌搜索“SSLSocketImpl漏洞泄漏”会带来很多点击率。我没有发现完全匹配的,但是升级是明智的。

感谢Stephen的及时回复。如果查看getInstance()方法,它将创建SendNotification类的新对象。因此,它不是一个单例类。如果我删除同步块怎么办?这能解决问题吗?SendNotification类的每个实例都会创建一个新线程,该线程使用URL类的openStream()方法向所有4b应用程序发出请求。所以我的问题是,openStream()方法是否创建了与应用程序B通信的新线程?如果我们查看异常StackTrace,它显示无法在java.lang.thread.start(thread.java:574)的java.lang.thread.start0(本机方法)处创建新的本机线程,那么每个请求都有5个线程被创建?(一个是SendNotification类,四个是openStream()方法)?如果是,那么如果应用程序A同时获得100个登录请求,那么它将一次创建500个线程。以下是tomcat连接器端口配置:当应用程序A当时崩溃时,任务管理器为tomcat5.exe进程显示了1029个线程。在这里使用绑定队列的ThreadPoolExecutor比较合适。通过这个小小的改变,您将从线程管理中抽象出您的业务逻辑。第一眼看到它的原因似乎是线程的创建比它的销毁要快。
synchronized (this) {
    // talk to network ...
}