由一个私有静态变量的处理问题谈java内存模型
2017-01-22 11:02
162 查看
近期,在项目中遇到一个问题,项目中与其他系统通信的代码,在其他系统已经停掉的情况下,程序中的返回值仍然有信息。经过验证,产生该现象的实际操作为:应用与其他系统通信——其他系统关闭(即其他系统不返回信息)——应用再次通信——返回值存在信息。由于本身我们的项目中,发送通信信息的代码是一个封装好的架包,而该架包的源码已经丢失,不得以,只好反编译源码以后进行调试。最终确认问题根源:返回值是使用架包中某个类的私有静态变量存储的,故每次发送信息成功后,该变量中均会缓存上次成功返回的信息。而当程序收不到返回信息时,并没有将原有缓存信息全部处理掉。
后来虽然通过外部判断条件优化的形式处理了上述问题,但实际上仍然未从根源上处理掉这个问题。当然,后来我修改了架包中的代码,将该问题处理掉了。但是处理过程中发现,如果想顺利解决该问题,需要对jvm中的内存模型有一定了解才行。下面我就简单介绍一下处理这个问题时遇到的问题,随后补充总结jvm中的内存分配。
首先将通讯程序中信息发送公共类中私有静态变量的代码贴出:
上面的代码,其中最recv的字节数组是最终返回应用的从其他系统接收到的信息,而这里的信息是TcpSend.send()方法返回的。这个方法的具体内容是这样的:
我们可以看到,除了getField_value这个map类型的变量外,其他变量均是固定值;而getField_value这个变量所存放的正是每次其他应用系统正常通讯返回的信息。
下面我们看一下实际通讯的代码(由于通讯方法代码很长,这里我只截取部分代码):
我们仔细分析这两段代码,第一段代码中,异常情况判断中,如果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关键字,存放在堆中。
后来虽然通过外部判断条件优化的形式处理了上述问题,但实际上仍然未从根源上处理掉这个问题。当然,后来我修改了架包中的代码,将该问题处理掉了。但是处理过程中发现,如果想顺利解决该问题,需要对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关键字,存放在堆中。
相关文章推荐
- 由一个私有静态变量的处理问题谈java内存模型
- 触发器(当2个表中的相应值改变时同时改变一个表中的一个字段)(同时有处理“无法解决 equal to 操作的排序规则冲突”问题)
- 处理一个电脑启动问题
- 一个iBatis框架进行batch处理的问题
- 一个关于c++字符串处理和delete[]与delete差别的问题
- 今天在处理GridView分页问题时遇到了一个比较常见的分页出错问题。
- Asp.net中如何处理一个站点不同Web应用通用Session的问题
- 【求助】csapp书中关于信号处理的一个问题
- 用js写的一个形式为##.dd的动态小数掩码问题,可以处理负值 NumberMask
- 一个关于字符集处理的问题
- 处理ASP中checkbox 在 form enctype="multipart/form-data"中只能取一个的问题
- 提一个比较不知道如何处理的问题?
- Asp.net中处理一个站点不同Web应用共享Session的问题
- Asp.net中处理一个站点不同Web应用共享Session的问题
- 一个私有的PageLoad在Mono下引发的问题
- csdn一个有关影像处理问题帖子的解答
- 一个静态变量的问题
- 遇见的又一个新问题,关于显示文章条目的时候,显示宽度的处理
- Hi!现在处理spring的一个问题java.lang.NoClassDefFoundError: org/apache/commons/pool/impl/GenericObjectPool
- 如何解决 html 中多空格字符被当作一个空格字符处理的问题