Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Wednesday, April 29, 2009

解决字符编码问题

字符编码集的问题

这两天和一个第三方平台进行联调,碰到一个字符编码的问题,经过自己的分析并与对方进行沟通,最终问题得到妥善 解决。字符编码让人非常恼火,只有真正理解了其中的机制之后才能快速的找出问题的根源。 幸亏去年读过好些关于字符编码的文章,让我对这个问题有很清晰的认识,这也是这次能快速解决问题的根本。
记录一下解决这个问题时我的分析过程,也反省一下还有些什么可以提高的。
问题背景: 与对方平台的接口采用HTTP POST方式传递XML报文方式交换数据,其中报文体BODY是以DES加密的字符串。由于项目需求,在加密前进行了一次字符串转换,将字符串的byte[]转成Hex字符再进行DES。

问题现象:在公司测试环境上,发现对方传递过来的报文能正确解析出XML结构并正常处理,但是查看最终生成的数据中发现其中的中文为乱码。但是另外有个发现是我在生产测试机上在终端看到了正确的中文字符。

问 题分析解决过程:由于报文解析正常,业务处理也正常,存在中文出现乱码问题,因此我认定是自己内部处理出现错误。由于内部处理过程中也存在两个不同服务的 交互,于是先是假定问题出在两个服务传递数据时的问题(当时对于这个猜测,我并没找到充足的理论依据,事实证明这种猜想是错误的),于是debug接收数 据的服务。Debug中发现接收到的数据已经是乱码(值全部为 65533)。 问过做内部两个服务数据交互接口的开发人员,他说是通过Socket传递数据的,不可能存在编码问题,因此把我问题定位在解析这个过程上。于是针对解析部 分进行Debug,发现DES解密出现的串中,中文已经是乱码 (值全部为65533)。 于是查看解密的代码,在查看解码代码时发现了问题,正是解决该字符集问题的关键!在这里要提一下,幸亏对方给了我们解密与加密的实现代码,否则这问题更难 定位。代码中在将一个String转换成byte[]时采用str.getBytes()方法,该方法采用JVM默认的编码将一个String转换成 byte[]。这时让我想起之前在HP-UX终端打印的看到正确的中文,而在公司测试环境下的Linux下面却是乱码的。我断定是乱码的问题是由于对方加 密时JVM的编码与我们测试系统中解密时采用的编码方式不一致造成的。由于对方工作人员已经下班,没办法直接问他们的字符编码方式。但是想到HP-UX能 正常解析,只要找到HP-UX上JVM启动时设置的编码就能知道问题原因了。但是该死的HP-UX并没有像Linux那样的/porc文件系统来记录进程 信息,而且ps 命令只能显示很少的命令行字符,折腾了半天也没能看清楚cmdline的全貌。这时想起在Linux下面启动时应该和HP-UX下JVM启动参数设置是一 致的, 于是查看Linux下的cmdline。 但是发现JVM启动时并没有指定file.encoding参数,这表明JVM采用操作系统默认字符集了。 查看后发现Linux下是C,HP-UX不熟悉,折腾半天也没搞明白,自己找到编码方式的尝试失败。于是决定等第二天直接问对方采用的字符编码方式。
第二天早上和对方开发人员沟通后,对方告诉我他们是采用GBK编码。 我马上在JVM启动时加上一个启动参数-Dfile.encoding=GBK,再次发送报文测试,新生成的数据中中文不再是乱码,问题终于解决了。

问题的反思:
问题解决了,但是对于问题解决过程的反思更重要。
解决方案中可能引入的新问题是JVM改成GBK编码会不会对该服务中其它部分产生影响?
再 回顾这个问题,罪魁祸首就是用了String的getBytes()方法,或许在使用该方法时应该显示指明其编码类型?所有代码都在单个JVM中运行或许 不需要关心这 个问题,因为编码总是统一的。但是在多个JVM中,并且JVM之间要交换数据,使用这个方法时或许就需要注意一下了。
再回顾问题解 决过程,发现在分析问题过程中还有许多可以改进的地方。 比如开始就就忽略的HP-UX上面编码是正确的这个关键点,而是在折腾了半天之后,定位到是JVM编码问题时才想起这个关键点。从HP-UX与Linux 上出现不同的解密不同时就可以判定在JVM启动时没有指定默认字符集,而不需要花费很多时间去找到进程启动的cmdline. 这些都是在经过清晰,严谨的思考,只要再往前一步就能推断出答案的,而在解决问题中却没有发现。 遇到问题保持头脑清醒,周密,严谨的思考比想到一半就开始实践更有效率。这些都是今后工作中需要提高的。

Wednesday, March 18, 2009

Java程序运行时,各变量在JVM中存放的位置

Java程序运行时,各变量在JVM中存放的位置

JVM对于内存是通过分成不同的部分来管理的,主要包括方法区(Method Area),堆(Heap),Java栈(Stack)。其中方法区存放装载到JVM中的类的信息,堆存放类的实例对象,Java棧以帧为单位保存线程运行的状态,存放每一个线程在程序执行过程中需要的数据信息。
对应于Java文件中的各种类变量,实例变量,类本身等内容,下面阐述一下它们在JVM中的存放位置。
public static final int i =1;
这个i在编译时就可以确认它的值,因此它作为一个常量被直接写在.class文件中,其它类用到这个i的时候,在编译过程中会拷贝i的值到该类的.class文件中。
public static final int i = (int)(Math.random() * 4);
如果是以上面这种形式的定义变量,因为其在编译时无法确认其值,在运行过程中才会确定值,因此它会当作一个类变量在类初始化是存放在方法区中。

static int i = 9;
以上这种形式定义的变量在类初始化时被初始化正确的值,并将其与类信息一起存放在方法区。

int i = 9;
这种形式定义的变量是实例变量,在类被实例化时赋于正确的初始值,和类实例对象一起存放在堆区。

void getXX(int a){
int i = 1;
}
上面这种形式定义的变量a是一个参数,i是一个局部变量,只在运算过程在有效,因此会存放在Java棧中棧帧中,在方法返回后其值也会无效,等待GC回收。


通过上面的论述可以推断出有哪些情况可能会导致OutOfMemory的错误了。
1、过多的类信息被装载到方法区,导致该区域OutOfMemory错误,在一些框架中通过反射机制生成大量的类信息可能导致该问题。
2、使用过多的static类型的类变量,特别是将大的数组,字符串等,导致方法区占用内存过多,造成OutOfMemory错误。由于JVM中并没有提供设置方法区大小的参数,因此只能通过加大JVM的内存来解决。
3、程序创建了过多的线程,导致Java棧使用内存过多,导致OutOfMemory。可通过-Xss来设置每一个Java thread stack 的大小来解决该问题。
4、程序生成了过多的实例对象,使得堆占用大量内存,最终出现OutOfMemory错误。 该问题可通过设置下面两个JVM启动参数来避免:
-Xms set initial Java heap size
-Xmx set maximum Java heap size