您的位置:首页 > 编程语言 > Java开发

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

2017-01-22 11:02 162 查看
近期,在项目中遇到一个问题,项目中与其他系统通信的代码,在其他系统已经停掉的情况下,程序中的返回值仍然有信息。经过验证,产生该现象的实际操作为:应用与其他系统通信——其他系统关闭(即其他系统不返回信息)——应用再次通信——返回值存在信息。由于本身我们的项目中,发送通信信息的代码是一个封装好的架包,而该架包的源码已经丢失,不得以,只好反编译源码以后进行调试。最终确认问题根源:返回值是使用架包中某个类的私有静态变量存储的,故每次发送信息成功后,该变量中均会缓存上次成功返回的信息。而当程序收不到返回信息时,并没有将原有缓存信息全部处理掉。

后来虽然通过外部判断条件优化的形式处理了上述问题,但实际上仍然未从根源上处理掉这个问题。当然,后来我修改了架包中的代码,将该问题处理掉了。但是处理过程中发现,如果想顺利解决该问题,需要对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 通信