由一个私有静态变量的处理问题谈java内存模型

来源:互联网 发布:mac如何查看qq群文件 编辑:程序博客网 时间:2024/06/05 15:46

近期,在项目中遇到一个问题,项目中与其他系统通信的代码,在其他系统已经停掉的情况下,程序中的返回值仍然有信息。经过验证,产生该现象的实际操作为:应用与其他系统通信——其他系统关闭(即其他系统不返回信息)——应用再次通信——返回值存在信息。由于本身我们的项目中,发送通信信息的代码是一个封装好的架包,而该架包的源码已经丢失,不得以,只好反编译源码以后进行调试。最终确认问题根源:返回值是使用架包中某个类的私有静态变量存储的,故每次发送信息成功后,该变量中均会缓存上次成功返回的信息。而当程序收不到返回信息时,并没有将原有缓存信息全部处理掉。 
后来虽然通过外部判断条件优化的形式处理了上述问题,但实际上仍然未从根源上处理掉这个问题。当然,后来我修改了架包中的代码,将该问题处理掉了。但是处理过程中发现,如果想顺利解决该问题,需要对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";}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

上面的代码,其中最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;  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

我们可以看到,除了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;  }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82

我们仔细分析这两段代码,第一段代码中,异常情况判断中,如果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的静态域中存储以供随时调用,且私有静态变量可以在类相应的对象未创建时使用。

附: 
内存模型:

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

0 0