在这里我们将通过几个有趣的例子,来演示Java对象序列化缓存问题。下面这个程序非常神奇,用了不到4秒的时间就向我的硬盘上输出了1000TB的数据。不要怀疑你看错了,确实是不到4秒时间就输出1000TB的数据,不相信你也可以在你的电脑上运行一下这个程序。如果你的硬盘不够大也不用担心,Java完全可以自己解决硬盘容量问题。这个例子对你的电脑唯一的要求就是必须有256M以上的内存,并且要设置执行参数为-Xmx256m。相信现在没有谁的电脑内存是不够256M的。
import java.io.*; public class SuperFastWriter { private static final long TERA_BYTE = 1024L * 1024 * 1024 * 1024; public static void main(String[] args) throws IOException { long bytesWritten = 0; byte[] data = new byte[100 * 1024 * 1024]; ObjectOutputStream out = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream(“bigdata.bin”) ) ); long time = System.currentTimeMillis(); for (int i = 0; i < 10 * 1024 * 1024; i++) { out.writeObject(data); bytesWritten += data.length; } out.writeObject(null); out.close(); time = System.currentTimeMillis() – time; System.out.printf(“Wrote %d TB%n”, bytesWritten / TERA_BYTE); System.out.println(“time = ” + time); } } |
编译之后,我们就可以执行这个程序了。
java -Xmx256m SuperFastWriter
可以看到类似以下的输出
Wrote 1000 TB
time = 3710
你一定会非常奇怪,我用的到底是什么电脑。不仅输出的速度那么快,并且输出的内容完全超出了硬盘容量。每秒钟250 TB,简直是不可思议的事情。
如果到硬盘上看一下输出的文件,会发现文件只有大概150M。这是因为当我们通过ObjectOutputStream输出一个对象的时候,ObjectOutputStream会将该对象保存到一个哈希表中,以后在输出相同的对象,都会只输出指针,不输出内容。同样的事情也发生在读取对象的时候。Java通过该机制达到最小化数据输入和输出的目的。下面的例子就演示了读取的过程。
import java.io.*; public class SuperFastReader { private static final long TERA_BYTE = 1024L * 1024 * 1024 * 1024; public static void main(String[] args) throws Exception { long bytesRead = 0; ObjectInputStream in = new ObjectInputStream( new BufferedInputStream( new FileInputStream(“bigdata.bin”) ) ); long time = System.currentTimeMillis(); byte[] data; while ((data = (byte[]) in.readObject()) != null) { bytesRead += data.length; } in.close(); time = System.currentTimeMillis() – time; System.out.printf(“Read %d TB%n”, bytesRead / TERA_BYTE); System.out.println(“time = ” + time); } } |
在这个例子中,我们去读取刚才输出的文件。虽然文件只有150M左右,但是实际读取的时候,数据量应该是和写出的一样。程序执行时间只需要几秒时间。类似执行结果是:
Read 1000 TB
time = 2033
前面的例子我们反复的将同一个数组写出到文件中,但是并没有修改数组的内容。下面的例子我们将每次写出内容不同的数组。因为Arrays.fill()的执行效率比较低。所以我们只写出256个大数组。
import java.io.*; import java.util.Arrays; public class ModifiedObjectWriter { public static void main(String[] args) throws IOException { byte[] data = new byte[10 * 1024 * 1024]; ObjectOutputStream out = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream(“smalldata.bin”) ) ); for (int i = -128; i < 128; i++) { Arrays.fill(data, (byte) i); out.writeObject(data); } out.writeObject(null); out.close(); } } |
接下来,我们把写出的内容在从文件中读出看看。
import java.io.*; public class ModifiedObjectReader { public static void main(String[] args) throws Exception { ObjectInputStream in = new ObjectInputStream( new BufferedInputStream( new FileInputStream(“smalldata.bin”) ) ); byte[] data; while ((data = (byte[]) in.readObject()) != null) { System.out.println(data[0]); } in.close(); } } |
观察会发现,读出的内容并没有-128, -127, -126等数字,只有-128。这是因为虽然每次我们写出之前都修改了数据的内容,但是依然是原来的数组。Java序列化机制除了第一次写出数组内容以外,以后每次只写出一个指针。在读的时候,也就只第一次读取到内容为-128的数组,以后每次都根据读取到的指针反复在本地哈希表中读取了。也就是说序列化机制只关心对象是否变化,而不关心内容是否变化。
通过这些提点,我们可以看出序列化的原则是:如果需要重复序列化一个对象,并且两次序列化之间对象的内容会发生改变,那么就要复位输出流。或者每次输出前都重新创建一个对象。
下面我们看一下每次都创建新对象的结果:
public class ModifiedObjectWriter2 { |
当程序运行一会之后,将会提示OutOfMemoryError。这是因为每次对象写出的时候,都会在哈希表中保留一个指针,所以虽然对象已经不再使用了,Java的垃圾回收机制也不会对对象进行回收,要一直等到输出流复位为止。当循环多次执行的时候,创建的对象越来越多,并且没有被及时回收,就会出现OutOfMemoryError问题了。通过观察可以发现,在出现错误之前所产生的文件基本接近于为JVM所分配的内存大小。如果每次输出之后,都复位输出,就可以避免这个问题了。
import java.io.*; import java.util.Arrays; public class ModifiedObjectWriter3 { public static void main(String[] args) throws IOException { ObjectOutputStream out = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream(“verylargedata.bin”) ) ); byte[] data = new byte[10 * 1024 * 1024]; for (int i = -128; i < 128; i++) { Arrays.fill(data, (byte) i); out.writeObject(data); out.reset(); } out.writeObject(null); out.close(); } } |
不幸的是,复位输出为导致所有的对象都被清理,即使是需要重复输出的对象。
对ObjectOutputStream和ObjectInputStream进行优化设计很大程度上降低了重复数据的输入输出工作,比如字符串。不幸的是,如果不恰当的使用会经常导致OutOfMemoryError错误或者输出数据不完整。
我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。
我原创,你原创,我们的内容世界才会更加精彩!
【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】
微信公众号
TechTarget
官方微博
TechTarget中国
作者
相关推荐
-
内存数据网格提供商一头扎进Java
10年的时间里,应用性能解决方案提供商Alachisoft一直在用NCache(针对N-Tier和网格计算.NET应用的内存计算和数据网格产品)为.NET社区服务。
-
遇到这样一个问题:通过java service wrapper部署应用,wrapper进程占用的内存会一直升高, 直到把内存吃完应用崩溃,但是这个wrapper
遇到这样一个问题:通过java service wrapper部署应用,wrapper进程占用的内存会一直升高 […]
-
Google App Engine for Java 对于目前中国需要学习吗?
-
前无古人后无来者的Java平台
开发人员一直在致力于保持Java的活力,经过20年后,我们感觉从来没有更好的、更令人激动的时刻如同Java社区一样。