一个Java的java对象的序列化大小等于序列化的大小吗?

优化Java堆大小的5个技巧
发表于 09:14|
来源CSDN编译|
作者Pierre
摘要:Java堆容量不足可以对性能造成很大影响,这样无疑就给程序带来不可必要的麻烦,本文总结了影响Java堆容量不足的五大原因以及巧妙地去优化?
本文作者Pierre是一名有10多年经验的高级系统架构师,他的主要专业领域是Java EE、中间件和JVM技术。根据他多年的工作实践经验,他发现许多性能问题都是由Java堆容量不足和调优引起的。下面他将和大家分享非常实用的5个Java堆优化技巧。
1.JVM:对难以理解的东西产生恐惧感
千万不要以为,通过配置,调优,就可以排除那些你所不明白的问题。有些人认为Java程序员不需要知道内部JVM内存管理。毫无疑问,这种观点明显是错误的,如果想拓宽知识面和提升排除故障能力,你就必须要了解和学习一下JVM内存管理。
对于Java或者是Java EE新手来说,Java Heap调优和故障排除是一项非常有挑战的工作。下面会提供一些典型的案例场景:
客户端环境面临着有规律的OutOfMemoryError错误并且对业务造成了很大的影响。
你的开发团队要在如此大的压力下去解决这个问题,通常会怎么做?
用谷歌搜索引擎找到类似的问题并且你会相信(或假设)你也面临同样的问题。
你会抓住JVM-Xms和存在OutOfMemoryError异常这几个关键字的例子,然后希望通过这样的案例来快速解决客户端问题。
最后你会在你环境中使用相同的调优方法。两天后,问题仍然发生(甚至更糟或者稍微好点)&&
到底是哪里错了呢?
首先,没有摸清问题根源所在?对开发环境没有正确地进行深层面(规格、负载情况等)理解。网络搜索是一个非常优秀的学习方法和知识分享工具,但是你必须结合自己的实际项目,从根本上进行分析解决。
可能缺乏基本的JVM和JVM内存管理技能,阻止你把所有的点给连接起来。
今天讲的第一条技巧是帮助你理解基本的JVM原则及其与众不同的内存空间。这些知识都是相当重要的,它可以帮助你做出有效的调优策略、更加正确合理的预测将来会产生的影响、提前知道未来需要做哪些调优工作。下面来看一下JVM参考指南:
JVM内存分为3个内存空间
Java Heap:适用于所有的JVM厂商,通常用来拆分YoungGen(幼苗)和OldGen(终身享用)空间。
PermGen(永久代):适用于Sun HotSpot VM((PermGen空间在Java7或者Java8更新中将会被删除)
Native Heap(C-Heap):适用于所有的JVM厂商。
建议把下面的文章都能看一遍,最好把Sun的Java内存管理白皮书和OpenJDKS实现下载下来并仔细阅读。
正如你所看到的,JVM内存管理比使用Xmx设置最大值更为复杂。你需要查看每个角度,包括本地和PermGen需求以及从主机上查看物理内存可用性(CPU core)。
在较大的Java Heap和较小的本地Heap比赛中,32位虚拟机可能会变得相当棘手。试图在一个32位VM如2.5GB+上设置一个大型堆,根据应用程序占用和线程数量等因素会增加OutOfMemoryError这个异常抛出。64位JVM可以解决这个问题,但物理资源可用性和垃圾回收成本仍然是有限制的(成本主要集中在GC大小收集上)。最大并不表示是最好的,所以请不要假设在一个16GB的64位虚拟机上可以运行20个Java EE应用程序。
2.数据和应用程序为王:回顾静态占用需求
应用程序以及相关数据将决定Java堆空间占用需求。通过静态内存,可&预测&下面的内存需求:
确定将会有多少不同的应用程序部署到预先计划的一个单独的JVM进程上,例如有多少个ear文件、war文件、jar文件等。在一个JVM上部署的应用程序越多,对本机堆的需求就越多。
确定有多少个类需要在运行时加载:包括第三方API。越多的类加载器和类在运行时被加载,在HotSpot VM PermGen空间和内部JIT相关优化对象上的需求就越高。
确定数据缓存占用,如应用程序加载内部缓存数据结构(和第三方API),例如数据库中的数据缓存,从文件中读取数据等。数据缓存使用越多,Java Heap OldGen空间需求就越高。
确定允许建立的中间件线程数量。这是非常重要的,因为Java线程需要足够的本机内存,否则会抛OutOfMemoryError异常。
在JVM进程上部署的应用程序越多,对本地内存和PermGen空间的要求就越高。数据缓存并不是序列化为一个磁盘或数据库,它将从OldGen空间里面需要额外的内存。
设法对静态内存占用进行合理的评估,在真正进行数据测试之前,设置一些JVM能力起点是非常有用的。对于32位JVM,通常不推荐一个Java堆大小超过2 GB(-Xms2048m,-Xmx2048m),对于Java EE应用程序和线程来说这样将需要足够的内存和本机堆PermGen。
这个评估是非常重要因为太多的应用程序部署在一个32位JVM进程上很容易导致本机堆耗尽;尤其是在多重线程环境。
对于64位JVM, 一个3GB或者4GB的Java堆/JVM进程是推荐的起点。
3.业务流量设置规则:审查动态内存占用需求
业务流量通常会决定动态内存占用。通过观察各种监控工具可以发现并发用户与请求生成的JVM GC&心跳&,这是由于频繁的创建和垃圾回收短期或者长期对象。
一个典型的32位JVM,Java堆大小设置在2 GB(使用分代&并发收集器)通常为500 MB YoungGen分配空间和1.5 GB的OldGen空间。
最大限度地减少重大GC收集的频率是获得最佳性能的关键因素,所以在高峰的时候理解和评估需要多少内存是非常重要的。
再次声明,应用程序类型和数据将决定内存需求。购物车的应用程序类型(长期居住的对象)涉及大型和非序列化会话数据,这个通常需要大型Java堆和很多OldGen空间。无状态和XML处理(很多短命的对象)繁重的应用程序需要适当YoungGen空间,以尽量减少频率主要集合。
你有5个ear应用程序(2000多个Java类)要部署(包含中间件代码)
本地堆需求估计为1GB(必须足够大以处理线程创建等等。)PermGen空间大约是512 MB。
内部静态缓存大约500MB
在高峰时间,总预测流量是5000个并发用户
每个用户的会话数据大约500K
在高峰期间,总流量会话要求是2.5GB。
正如你所看到的一样,在如此情况下,32位JVM进程就无法满足。一个典型的解决方案是进行流量拆分,在几个JVM进程或物理主机(假设有足够的硬件和CPU core可用)上。
大多数时候,业务流量将推动内存占用。除非你需要大量的数据缓存来实现适当的性能,典型的门户应用网站(媒体)繁重的应用程序需求。数据缓存太多的时候应该用一个黄色的标志标注一下,最好早点去重新审视一下一些设计元素。
4.量体裁衣
这一条,你应该做到:
理解基本的JVM原则和内存空间。
对所有应用程序有深入的了解及其它们的特点(大小、类型、动态流量、无状态对象VS有状态对象、内部内存缓存等)。
对预测业务流量(并发用户)给每一个应用程序能提出很好的观点&如果你需要一个64位的虚拟内存,那么将设置哪个作为开始。
如果需要多个JVM(中间件)过程。
等一下,这样做并不足够。虽然上面的信息是至关重要的,并且关于Java堆的设置进行了&最佳猜测&,对应用程序的行为进行模拟并且进行适当的分析、负载和性能测试来验证Java堆内存要求。
推荐Jprofiler工具给大家,学习如何使用一个分析器的最好方法是正确理解应用程序的内存占用。另一个方法是使用Eclipse MAT工具根据现有的环境进行堆转储分析。堆转储非常强大,它可以允许你查看和理解Java堆的整个内存占用,包含类加载器相关数据和在内存占用分析中必须要做的,特别是内存泄漏。
Java分析器和堆转储分析工具允许你理解和验证应用程序内存足迹,包含内存泄漏的检测和解决方案。负载测试和性能测试是必不可少的,通过模拟并发用户来验证早期评估是否正确,它也会把应用程序瓶颈暴露出来并且允许你进行微调。推荐一个非常容易上手的工具:Apache Jmeter。
最后将看一下这样的情况,应用程序在Java EE环境非常正常,直到有一天完全正常的设备启动失败,例如硬件问题。突然的环境运行能力下降和整体环境下降,到底发生了什么?
引起&多米诺效应&的原因有很多,但缺少JVM调优和处理故障转移的能力(短期额外负荷)是很常见的。如果JVM进程运行在80% + OldGen空间容量和频繁的垃圾收集,你如何预期故障转移场景?
前面模拟的负载和性能测试应该模拟这样的场景,调整你的调优设置使您的Java堆有足够的缓冲来处理额外的负载(额外的对象)在短期内。这主要适用于动态内存占用,由于故障转移意味着将重定向一些固定的并发用户给可利用的JVM进程(中间件实例)。
5.分而治之
这一条的前提是你已经完成了几十个负载测试。JVM已经不存在泄露,你的应用程序内存不能再进行任何减少。你已经尝试了几个调优策略,例如使用一个64位的Java堆空间在10GB以上。多个GC策略,尽管这样,仍然没有找到合适的可以接受的性能水平?
与当前的JVM规范相比,适当的垂直和水平伸缩,包括在每个物理主机和跨多个主机上建立JVM进程来满足整个吞吐量和容量。如果在几个逻辑仓、自身的JVM进程、线程和调优值里打破应用程序列表那么IT环境的容错能力将更强大。
&分而治之&策略包括拆分应用程序流量到多个JVM进程,下面提供一些拆分技巧:
减少每个JVM进程的Java堆大小(静态和动态的占用)
降低JVM调优复杂度。
减少GC流失和暂停每个JVM进程
增加冗余和故障切换功能
排列最新的Cloud和IT虚拟化战略
当你发现已经花费了大量的时间在64位JVM进程调优上,是时候该好好审视一下你的中间件和JVM部署策略并且利用垂直和水平缩放。这条策略的实现需要更多的硬件支持,但是从长远角度来看,是非常有效和有益的。(张红月/编译)
原文链接:
本文为CSDN编译整理,未经许可不得转载。如需转载请联系。&
推荐阅读相关主题:
网友评论有(0)
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
您的访问请求被拒绝 403 Forbidden - ITeye技术社区
您的访问请求被拒绝
亲爱的会员,您的IP地址所在网段被ITeye拒绝服务,这可能是以下两种情况导致:
一、您所在的网段内有网络爬虫大量抓取ITeye网页,为保证其他人流畅的访问ITeye,该网段被ITeye拒绝
二、您通过某个代理服务器访问ITeye网站,该代理服务器被网络爬虫利用,大量抓取ITeye网页
请您点击按钮解除封锁&我想知道一个Java对象在内存中到底有多大,用Runtime的测感觉不太准确。如果我把该对象转为一个JSON字符串或者序列化,那么这个等于真实的在内存中的Java对象的大小吗?
显然不是啊,从序列化的目的就可以看出来了:序列化的目的是为了在远程系统之间以较小代价进行对象二进制级传输而生的,我曾遇到一个开发情景以说明这个问题:把一个含有相当多字段的复杂对象通过MQ队列从一个系统传送到另一个队列,刚开始按照通行的做法我们使用了XML这一平台无关的格式,XML把对象按照字段名以DOM树的形式进行展开,因此在对象传输前要解析对象为DOM树,在XML的DOM树传输到另一端系统时需要再次解析把DOM树转换为原来的对象。但是由于我们需要传输的数据量巨大,所以频繁的解析使得时间不可接受。后来我们又尝试了JSON格式,但是效果仍然不好,因为也牵扯到DOM树的构造和解析。所以通常情况下的DOM格式的文件传输实际上对数量较大的java对象的传输效果都不很好。-----------------------------------由于MQ队列对数据传输格式其实并不要求太多,接下来我们使用了这个问题中提到的java对象的序列化串,正因如此,我去察看了java的序列化规范,序列化规范规定了对象的序列化后的二进制字节格式,精细到了每个字段的存储形式是怎么样的,占用几个字节等等。详情可以去读《java核心技术卷二》的IO部分,又非常细致的对于java序列化的解释,这里我举个小例子来说明一下序列化的规范:一个说明序列化最简单的例子,把结构最简单的Person类的对象序列化,为了说明方便我们把序列化串放到文件中来分析,当然在真实的环境里面我们直接就把这个串通过网络进行传输了,下面就是这个序列化串:从这里我们不难发现,这个序列化串中包含类名、类签名指纹、对数据域的描述等等,这个问题是要比较class和这个序列化串,当然我们就可以直接把class编译出来看看,当然编译出的class是由类的二进制字节码规范约束的,不同的jvm可能会有不同的实现,只是为了在jvm运行时指导解释动作的:从这里我们不难发现,这个序列化串中包含类名、类签名指纹、对数据域的描述等等,这个问题是要比较class和这个序列化串,当然我们就可以直接把class编译出来看看,当然编译出的class是由类的二进制字节码规范约束的,不同的jvm可能会有不同的实现,只是为了在jvm运行时指导解释动作的:可以看出,这个结构完整地描述了类的结构,如果想了解详情,可以阅读那本著名的《深入了解java虚拟机》。可以看出,这个结构完整地描述了类的结构,如果想了解详情,可以阅读那本著名的《深入了解java虚拟机》。事情说到这里,问题的答案就不难了,class和序列化串的目的不同,所以也自然不能相等了,因为class为了指导虚拟机的运行,而序列化串为了暂存活化的内存对象到IO中去。
重要的事情要说三遍:&br&Java对象在内存中的形态跟序列化后的形态没有关系。&br&Java对象在内存中的形态跟序列化后的形态没有关系。&br&Java对象在内存中的形态跟序列化后的形态没有关系。&br&&br&Java对象在内存里有多大,最靠谱的办法是:在完整支持JVMTI与Java Agent的JVM上,可以用&a href=&///?target=https%3A///javase/7/docs/api/java/lang/instrument/Instrumentation.html%23getObjectSize%28java.lang.Object%29& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Instrumentation.getObjectSize()&i class=&icon-external&&&/i&&/a&方法来获取。&br&其它靠谱的办法有:&br&&ul&&li&HotSpot VM上用Serviceability Agent的API来查看&/li&&li&IBM J9 VM上用jdmpview来查看&/li&&/ul&&br&相关的一些讨论请跳传送门:&br&&ul&&li&&a href=&///?target=http%3A//hllvm./group/topic/26051& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&java 类型所占用的字节数&i class=&icon-external&&&/i&&/a& &- 这里我有提到序列化后的形态跟在内存里的形态的关系——没有关系。&br&&/li&&li&&a href=&///?target=http%3A///blog/727938& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&如何dump出一个Java进程里的类对应的Class文件?&i class=&icon-external&&&/i&&/a& &- 这里举了java.lang.Class的例子来演示它序列化后的形态跟在内存里的形态没有关系。&br&&/li&&li&&a href=&///?target=http%3A//hllvm./group/topic/35621& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java如何获知对象大小?&i class=&icon-external&&&/i&&/a&&br&&/li&&li&&a href=&///?target=http%3A//hllvm./group/topic/38400& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java基本类型的物理存储大小&i class=&icon-external&&&/i&&/a&&/li&&/ul&
重要的事情要说三遍:Java对象在内存中的形态跟序列化后的形态没有关系。Java对象在内存中的形态跟序列化后的形态没有关系。Java对象在内存中的形态跟序列化后的形态没有关系。Java对象在内存里有多大,最靠谱的办法是:在完整支持JVMTI与Java Agent的JVM上…
已有帐号?
无法登录?
社交帐号登录
饥饿的扎瓦程序员java序列化与反序列化以及浅谈一下hadoop的序列化 - 推酷
java序列化与反序列化以及浅谈一下hadoop的序列化
1、什么是序列化和反序列化
神马是序列化呢,序列化就是把
内存中的对象的状态信息
以便于存储(持久化)和网络传输。(网络传输和硬盘持久化,你没有一定的手段来进行辨别这些字节序列是什么东西,有什么信息,这些字节序列就是垃圾)。
反序列化就是将收到
字节序列或者是硬盘的持久化数据
内存中的对象
2、JDK的序列化
JDK的序列化只有实现了serializable接口就能实现序列化与反序列化,但是记得一定要加上序列化版本ID&serialVersionUID&
这个是识别序列化的之前那个类的到底是哪一个?我们显示这个序列化版本ID的目的就是为了:
1) 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
2) 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
java的序列化算法要考虑到下面这些东西:
◆将对象实例相关的类元数据输出。
◆递归地输出类的超类描述直到不再有超类。
◆类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
◆从上至下递归输出实例的数据
所以java的序列化确实很强大,序列化后得到的信息也很详细,所以反序列化就so easy.
但是这样做也有它的坏处,序列化后很占内存,所以不一定详细就是好处,简单有时也是不错的。
在hadoop中,hadoop实现了一套自己的序列化框架,hadoop的序列化相对于JDK的序列化来说是比较简洁的。在集群中信息的传递主要就是靠这些序列化的字节序列来传递的所以更快速度更小的容量就变得非常地重要了。
说了太多的废话,还是扯回JDK的序列化吧。下面我们看一下在JDK中式如何实现序列化的。
首先我们有一个需要序列化的类如下(必须实现serializable接口)
import java.io.S
public class Block implements Serializable{
private static final long serialVersionUID = 1L;
public int getId() {
public void setId(int id) {
public String getName() {
public void setName(String name) {
this.name =
public Block(int id, String name) {
this.name =
} 下面我们来测试一下序列化的结果:
import java.io.ByteArrayInputS
import java.io.ByteArrayOutputS
import java.io.FileInputS
import java.io.FileOutputS
import java.io.IOE
import java.io.ObjectInputS
import java.io.ObjectOutputS
public class TestSerializable {
public static void main(String[] args) throws IOException,
ClassNotFoundException {
//将序列化化的数据写到文件out里面(持久化)
FileOutputStream fos = new FileOutputStream(&./out&);
ObjectOutputStream oos = new ObjectOutputStream(fos);
for (int i = 0; i & 100; i++) {
Block b = new Block(i, &B&+i);
oos.writeObject(b);
oos.flush();
oos.close();
//读出一个序列化的对象的字节序列(^..^)就是反序列化
FileInputStream fis = new FileInputStream(&./out&);
ObjectInputStream ois = new ObjectInputStream(fis);
Block b2 = (Block) ois.readObject();
ois.close();
System.out.println(b2.getName());
} 测试的结果:(取出第一个对象的name)
B0 生成一百个对象的持久化数据的大小是:1.60 KB (1,643 字节)一个对象平均16个字节,该类只有两个字段一个是int,一个字符串但是字符串的长度为2,所以我们可以感受到这冗余还是挺大的。
3、hadoop的序列化
hadoop的序列化的特点是:
1、紧凑:由于带宽是集群中信息传递的最宝贵的资源所以我们必须想法设法缩小传递信息的大小,hadoop的序列化就为了更好地坐到这一点而设计的。
2、对象可重用:JDK的反序列化会不断地创建对象,这肯定会造成一定的系统开销,但是在hadoop的反序列化中,能重复的利用一个对象的readField方法来重新产生不同的对象。
3、可扩展性:当前hadoop的序列化有多中选择
*可以利用实现hadoop的Writable接口。
*使用开源的序列化框架protocol Buffers,Avro等框架。
我们可以注意到的是hadoop2.X之后是实现一个叫YARN的云操作系统,所有应用(如mapreduce,或者其他spark实时或者离线的计算框架都可以运行在YARN上),YARN还负责对资源的调度等等。
YARN的序列化就是用Google开发的序列化框架protocol Buffers,proto目前支持支持三种语言C++,java,Python所以RPC这一层我们就可以利用其他语言来做文章,满足其他语言开发者的需求。
我屮艸芔茻,扯得有点远。
回到hadoop原生的序列化,hadoop原生的序列化类需要实现一个叫Writeable的接口,类似于serializable接口。
还有hadoop也为我们提供了几个序列化类,他们都直接或者间接地实现了Writable接口。如:IntWritable,LongWritable,Text等等。
实现Writable接口必须实现两个方法:write(DataOutputStream out);readField(DataInputStream in)方法。
下面是一个hadoop的序列化例子:
import java.io.ByteArrayOutputS
import java.io.DataI
import java.io.DataInputS
import java.io.DataO
import java.io.DataOutputS
import java.io.FileInputS
import java.io.FileOutputS
import java.io.IOE
import org.apache.hadoop.io.T
import org.apache.hadoop.io.W
import org.junit.T
public class Testhadoop_serializable_writable {
public void serializable() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
FileOutputStream fos = new FileOutputStream(&./hadoop_out&);
for (int i = 0; i & 10; i++) {
Text t1 = new Text(String.valueOf(i));
Text t2 = new Text(&mw&);
MyWritable mw = new MyWritable(t1,t2);
mw.write(dataOut);
dataOut.close();
fos.write(out.toByteArray());
fos.flush();
fos.close();
FileInputStream fis = new FileInputStream(&./hadoop_out&);
DataInputStream dis = new DataInputStream(fis);
for (int i = 0; i & 10; i++) {
MyWritable mw = new MyWritable(new Text(), new Text());
mw.readFields(dis);
System.out.println(mw.getId() + & & + mw.getName());
class MyWritable implements Writable {
public MyWritable(Text id, Text name) {
this.name =
public synchronized Text getId() {
public synchronized void setId(Text id) {
public synchronized Text getName() {
public synchronized void setName(Text name) {
this.name =
public void write(DataOutput out) throws IOException {
id.write(out);
name.write(out);
public void readFields(DataInput in) throws IOException {
id.readFields(in);
name.readFields(in);
我们可以看到我们实现的自己序列化类MyWritable。他有两个字段都是Text,Text是hadoop自带的序列化类,可以看做字符串(类似吧)吧?!
write()和readField()用到的是回调函数,将流(DataOutputStream DataInputStream)写出,或者读出,都是用到回调函数(hook(钩子))。
上面的运行结果如下:
生成的字节序列:
命令行结果:
已发表评论数()
已收藏到推刊!
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
没有分页内容
图片无法显示
视频无法显示
与原文不一致<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
您的访问请求被拒绝 403 Forbidden - ITeye技术社区
您的访问请求被拒绝
亲爱的会员,您的IP地址所在网段被ITeye拒绝服务,这可能是以下两种情况导致:
一、您所在的网段内有网络爬虫大量抓取ITeye网页,为保证其他人流畅的访问ITeye,该网段被ITeye拒绝
二、您通过某个代理服务器访问ITeye网站,该代理服务器被网络爬虫利用,大量抓取ITeye网页
请您点击按钮解除封锁&

我要回帖

更多关于 java对象序列化的作用 的文章

 

随机推荐