近期,在项目中遇到一个问题,项目中与其他系统通信的代码,在其他系统已经停掉的情况下,程序中的返回值仍然有信息。经过验证,产生该现象的实际操作为:应用与其他系统通信——其他系统关闭(即其他系统不返回信息)——应用再次通信——返回值存在信息。由于本身我们的项目中,发送通信信息的代码是一个封装好的架包,而该架包的源码已经丢失,不得以,只好反编译源码以后进行调试。最终确认问题根源:返回值是使用架包中某个类的私有静态变量存储的,故每次发送信息成功后,该变量中均会缓存上次成功返回的信息。而当程序收不到返回信息时,并没有将原有缓存信息全部处理掉。
后来虽然通过外部判断条件优化的形式处理了上述问题,但实际上仍然未从根源上处理掉这个问题。当然,后来我修改了架包中的代码,将该问题处理掉了。但是处理过程中发现,如果想顺利解决该问题,需要对jvm中的内存模型有一定了解才行。下面我就简单介绍一下处理这个问题时遇到的问题,随后补充总结jvm中的内存分配。
首先将通讯程序中信息发送公共类中私有静态变量的代码贴出:

public class TcpSvr
{
  private static String key = "****";
  private static String str_rpl = " ";
  private static String str_top = "01001";
  private static int iFileFlag = 0;
  private static String strFilename = "";
  private static ConcurrentHashMap<String, Object> getField_value = new ConcurrentHashMap();
  private static String code = "gbk";
}
public static ConcurrentHashMap<String, Object> Tcp_ip8583(ConcurrentHashMap<String, Object> inputField_value, String tlrno)
    throws IOException, SocketTimeoutException
  {
    PublicParm initParm = new PublicParm();

    return Tcp_ip8583(inputField_value, tlrno, initParm);
  }

  public static ConcurrentHashMap<String, Object> Tcp_ip8583(ConcurrentHashMap<String, Object> inputField_value, String tlrno, PublicParm initParm)
    throws IOException, SocketTimeoutException
  {
    ConcurrentHashMap<String, Object> hexmap = new ConcurrentHashMap();
    ConcurrentHashMap<String, Object> hexmap_8583 = new ConcurrentHashMap();
    ConcurrentHashMap<String, Object> hexmap_8583_type = new ConcurrentHashMap();
    ConcurrentHashMap<String, Object> hexmap_8583_dec = new ConcurrentHashMap();
    String byte_rpt_flag = "";
    String strSysDate = "";



    Pub_sys_log.write_trad_log("初始化*****************************  开始");
      /*其他处理*/
      //以下内容为数据交互相关处理
      byte[] recv = (byte[])null;

      Pub_sys_log.write_trad_log("发送并接收文件 send");
      recv = TcpSend.send(s_last, tlrno, initParm);
      Pub_sys_log.write_trad_log("发送并接收文件 end");
      ConcurrentHashMap localConcurrentHashMap;
      if (recv == null)
      {
        getField_value.put("12", "X00B");
        localConcurrentHashMap = getField_value;
    return localConcurrentHashMap;
      }
      String timeout = new String(recv);
      if ("timeout".equals(timeout))
      {
        Pub_sys_log.write_trad_log("socket timeout");
        getField_value.put("timeout", timeout);
        localConcurrentHashMap = getField_value;
    return localConcurrentHashMap;
      }
      timeout = null;
}

上面的代码,其中最recv的字节数组是最终返回应用的从其他系统接收到的信息,而这里的信息是TcpSend.send()方法返回的。这个方法的具体内容是这样的:

public class TcpSend
{
  public static byte[] send(byte[] s_last, String tlrno, PublicParm initParm)
    throws IOException, SocketTimeoutException
  {
    byte[] result = (byte[])null;
      result = send_tcpip(s_last, tlrno, initParm);
    return result;
  }
  public static byte[] send_tcpip(byte[] s_last, String tlrno, PublicParm initParm)
    throws IOException, SocketTimeoutException
  {
    byte[] result = (byte[])null;

    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    HttpSession session1 = SessionThreadLocal.getSession();
    Future future = threadPool.submit(new TcpSendImpl(s_last, tlrno, session1, initParm));
    Pub_sys_log.write_trad_log("通讯开始");
    try
    {
      result = (byte[])future.get();

      Pub_sys_log.write_trad_log("通讯返回的值" + new String(result).trim());
    }
    catch (SocketTimeoutException e)
    {
      Pub_sys_log.write_trad_log("通讯出现SocketTimeoutException异常:", e);
      throw e;
    }
    catch (Exception ex)
    {
      Pub_sys_log.write_trad_log("通讯出现异常:", ex);
    }
    threadPool.shutdown();
    Pub_sys_log.write_trad_log("通讯结束");
    return result;
  }
}

我们可以看到,除了getField_value这个map类型的变量外,其他变量均是固定值;而getField_value这个变量所存放的正是每次其他应用系统正常通讯返回的信息。
下面我们看一下实际通讯的代码(由于通讯方法代码很长,这里我只截取部分代码):

class TcpSendImpl
  implements Callable
{
  DataInputStream getMessageStream = null;
  private ClientSocket JavaSocket = null;
  private PublicParm initParm = null;
  private String SocketServerName = "";
  private int SocketPORT;
  private int soTimeOut = 0;
  private HttpSession session = null;
  String tlrno;
  byte[] s_last;
  int k = 0;

  public TcpSendImpl(byte[] s_last, String tlrno)
  {
    this.tlrno = tlrno;
    this.s_last = s_last;
  }

  public TcpSendImpl(byte[] s_last, String tlrno, HttpSession session, PublicParm initParm)
  {
    this.tlrno = tlrno;
    this.s_last = s_last;
    this.session = session;
    this.initParm = initParm;
  }

  public byte[] call()
    throws Exception
  {
    SessionThreadLocal.setSessionThreadLocal(this.session);
    byte[] result = (byte[])null;
    this.SocketServerName = Pub_system_iniread.getValue(this.initParm.IP);
    String PORT = Pub_system_iniread.getValue(this.initParm.PORT);
    if ((PORT == null) || ("".equals(PORT))) {
      Pub_sys_log.write_trad_log("Error port,please view the trad_log.日期");
    } else {
      this.SocketPORT = Integer.parseInt(PORT);
    }
    String timeout = Pub_system_iniread.getValue(this.initParm.SOTIMEOUT);
    Pub_sys_log.write_trad_log("SocketServerName:PORT:timeout" + this.SocketServerName + ":" + this.SocketPORT + ":" + timeout);
    if ((timeout != null) && (timeout.trim().length() > 0)) {
      this.soTimeOut = Integer.parseInt(timeout);
    }
    Pub_sys_log.write_trad_log("通讯ip和端口:" + this.SocketServerName + "/" + PORT + ",通讯开始" + this.s_last.length);
    try
    {
      if (createConnection())
      {
        Pub_sys_log.write_trad_log("start sendMessage:" + this.s_last);
        sendMessage(this.s_last);

        Pub_sys_log.write_trad_log("start getMessage:");
        result = getMessage_gz();
        Pub_sys_log.write_trad_log("end getMessage:" + new String(result));

        this.JavaSocket.closeOutAndInStream();
      }
      if ("timeout".equals(new String(result))) {
        return result;
      }
      Pub_sys_log.write_trad_log("关闭服务器连接开始!\n");
      try
      {
        this.JavaSocket.ShutdownConnection();
        Pub_sys_log.write_trad_log("关闭服务器连接成功!\n");
      }
      catch (Exception e)
      {
        Pub_sys_log.write_trad_log("关闭服务器连接失败!", e);
      }
      Pub_sys_log.write_trad_log("通讯ip和端口:" + this.SocketServerName + "/" + PORT + ",通讯结束");
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
      Pub_sys_log.write_trad_log("Couldn't   get   I/O   for   the   connection   to", ex);
    }
    return result;
  }
}

我们仔细分析这两段代码,第一段代码中,异常情况判断中,如果recv为空(即收到的返回值是空),getField_value这个map型变量赋值,然后返回;而超时未收到返回值会在map中赋入一个timeout的键值对。而实际上,我们分析接收返回报文的第二段代码可以看出,返回到第一段代码中的recv变量是null;此时,程序会对最终返回应用的getField_value这个map中赋一个值为X00B名为12的键值对,而上一次报文发送成功后,返回的其他键值对信息,由于getField_value变量无法释放,也一直没有清空,所以上一次发送信息中,除了名为12的键值对被覆盖,其他的键值对仍然存在。故应用中如果取其他的键值对信息,最终就会造成误判。
相应的解决方案很容易,只要我们在第一段代码的Tcp_8583()方法起始位置将getField_value变量清空即可,即新增该行代码getField_value.clear();
如果想要迅速解决这个问题,需要理解私有静态变量及其类在jvm中释放的实际及相应的存储位置,否则在面对这个问题时,会十分迷茫,感到无从下手;只有正确分析出,私有静态变量在jvm的静态域中存储以供随时调用,且私有静态变量可以在类相应的对象未创建时使用。

附:
java内存模型:

1、 寄存器。这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接的控制权,也不可能在自己的程序里找到寄存器存在的任何踪迹。即我们在程序中无法控制。
2、 栈,又称堆栈。用于存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中。驻留于常规RAM(随机访问存储器)区域。但可通过它的“堆栈指针”获得处理的直接支持。堆栈指针若向下移,会创建新的内存;若向上移,则会释放那些内存。这是一种特别快、特别有效的数据保存方式,仅次于寄存器。创建程序时,Java编译器必须准确地知道堆栈内保存的所有数据的“长度”以及“存在时间”。这是由于它必须生成相应的代码,以便向上和向下移动指针。这一限制无疑影响了程序的灵活性,所以尽管有些java数据要保存在堆栈里——特别是对象句柄,但java对象并不放到其中。
3、 堆。一种常规用途的内存池(也在RAM区域),其中保存了java对象,即用new产生的数据。和堆栈不同:“内存堆”或“堆”最吸引人的地方在于编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间。因此,用堆保存数据时会得到更大的灵活性。要求创建一个对象时,只需用new命令编制相碰的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间这里写代码片时会花掉更长的时间
4、 静态域。用于存放在对象中用static定义的静态成员。这儿的“静态”是指“位于固定位置”。程序运行期间,静态存储的数据将随时等候调用。可用static关键字指出一个对象的特定元素是静态的。但java对象本身永远都不会置入静态存储空间。
5、常量池。用于存放常量。常数值通常直接置于程序代码内部。这样做是安全的。因为它们永远都不会改变,有的常数需要严格地保护,所以可考虑将它们置入只读存储器(ROM)。
6、 非RAM存储。指硬盘等永久存储空间。若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。其中两个最主要的例子便是“流式对象”和“固定对象”。对于流式对象,对象会变成字节流,通常会发给另一台机器,而对于固定对象,对象保存在磁盘中。即使程序中止运行,它们仍可保持自己的状态不变。对于这些类型的数据存储,一个特别有用的技艺就是它们能存在于其他媒体中,一旦需要,甚至能将它们恢复成普通的、基于RAM的对象。
另外需要说明的是字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同定(常量池和堆).有的是编译期就已经创建好,存放在字符串常 量池中,而有的是运行时才被创建.使用new关键字,存放在堆中。