借助HotSpot SA来一窥PermGen上的对象

Posted on

借助HotSpot SA来一窥PermGen上的对象

您还未登录 ! 我的应用 登录 注册

JavaEye-最棒的软件开发交流社区

论坛首页Java编程和Java企业应用版Java综合

借助HotSpot SA来一窥PermGen上的对象

全部 Hibernate Spring Struts iBATIS 企业应用 设计模式 DAO 领域模型 OO Tomcat SOA JBoss Swing Java综合

myApps快速开发平台,配置即开发、所见即所得,节约85%工作量

« 上一页 1 2 下一页 » 浏览 2354 次 主题:借助HotSpot SA来一窥PermGen上的对象

该帖已经被评为精华帖 作者 正文 * RednaxelaFX

  • 等级: 一钻会员
  • RednaxelaFX的博客
  • 文章: 385
  • 积分: 900
  • 来自: 杭州
  • 发表时间:2010-08-05 最后修改:前天

< > 猎头职位:

相关文章:

  • 问题的产生(解决)??
  • 分析java.lang.OutOfMemoryError: PermGen space
  • IKVM 编程武林之.NET派的北冥神功
  • 一次Java垃圾收集调优实战 推荐圈子: 高级语言虚拟机 更多相关推荐 (Disclaimer:如果需要转载请先与我联系; 作者:RednaxelaFX -> rednaxelafx.javaeye.com) 接着昨天的前天的帖,今天也来介绍一个HotSpotServiceability Agent(以下简称SA)的玩法例子。 昨天用SA把x86机器码反汇编到汇编代码,或许对多数Java程序员来说并不怎么有趣。那么今天就来点更接近Java,但又经常被误解的话题——HotSpot的GC堆的permanent generation。 要用SA里最底层的API来连接上一个Java进程并不困难,不过SA还提供了更方便的封装:只要继承 sun.jvm.hotspot.tools.Tool 并实现一个 run() 方法,在该方法内使用SA的API访问JVM即可。 这次我们就把一个跑在HotSpot上的Java进程的perm gen里所有对象的信息打到标准输出流上看看吧。 测试环境是32位Linux,x86,Sun JDK 6 update 2 (手边可用的JDK版本很多,随便拿了一个来用,呵呵 >_<) 代码如下: import sun.jvm.hotspot.gc_implementation.parallelScavenge.PSPermGen; import sun.jvm.hotspot.gc_implementation.parallelScavenge.ParallelScavengeHeap; import sun.jvm.hotspot.gc_implementation.shared.MutableSpace; import sun.jvm.hotspot.gc_interface.CollectedHeap; import sun.jvm.hotspot.memory.Universe; import sun.jvm.hotspot.oops.HeapPrinter; import sun.jvm.hotspot.oops.HeapVisitor; import sun.jvm.hotspot.oops.ObjectHeap; import sun.jvm.hotspot.runtime.VM; import sun.jvm.hotspot.tools.Tool; /// / @author sajia / // public class TestPrintPSPermGen extends Tool { public static void main(String[] args) { TestPrintPSPermGen test = new TestPrintPSPermGen(); test.start(args); test.stop(); } @Override public void run() { VM vm = VM.getVM(); Universe universe = vm.getUniverse(); CollectedHeap heap = universe.heap(); puts("GC heap name: " + heap.kind()); if (heap instanceof ParallelScavengeHeap) { ParallelScavengeHeap psHeap = (ParallelScavengeHeap) heap; PSPermGen perm = psHeap.permGen(); MutableSpace permObjSpace = perm.objectSpace(); puts("Perm gen: [" + permObjSpace.bottom() + ", " + permObjSpace.end() + ")"); long permSize = 0; for (VM.Flag f : VM.getVM().getCommandLineFlags()) { if ("PermSize".equals(f.getName())) { permSize = Long.parseLong(f.getValue()); break; } } puts("PermSize: " + permSize); } puts(); ObjectHeap objHeap = vm.getObjectHeap(); HeapVisitor heapVisitor = new HeapPrinter(System.out); objHeap.iteratePerm(heapVisitor); } private static void puts() { System.out.println(); } private static void puts(String s) { System.out.println(s); } } 很简单,假定目标Java进程用的是Parallel Scavenge(PS)算法的GC堆,输出GC堆的名字,当前perm gen的起始和结束地址,VM参数中设置的PermSize(perm gen的初始大小);然后是perm gen中所有对象的信息,包括对象摘要、地址、每个成员域的名字、偏移量和值等。 对HotSpot的VM参数不熟悉的同学可以留意一下几个参数在HotSpot源码中的定义: product(ccstrlist, OnOutOfMemoryError, "", "Run user-defined commands on first java.lang.OutOfMemoryError") product(bool, UseParallelGC, false, "Use the Parallel Scavenge garbage collector") product_pd(uintx, PermSize, "Initial size of permanent generation (in bytes)") product_pd(uintx, MaxPermSize, "Maximum size of permanent generation (in bytes)") 要让SA连接到一个正在运行的Java进程最重要是提供进程ID。获取pid的方法有很多,今天演示的是利用OnOutOfMemoryError参数指定让HotSpot在遇到内存不足而抛出OutOfMemoryError时执行一段用户指定的命令;在这个命令中可以使用%p占位符表示pid,HotSpot在执行命令时会把真实pid填充进去。 然后来造一个引发OOM的导火索: public class Foo { public static void main(String[] args) { Long[] array = new Long[256/1024/1024]; } } 对32位HotSpot来说,main()方法里的new Long[256/1024/1024]会试图创建一个大于1GB的数组对象,那么只要把-Xmx参数设到1GB或更小便足以引发OOM了。 如何知道这个数组对象会占用超过1GB的呢?Long[]是一个引用类型的数组,只要知道HotSpot中采用的对象布局: ----------------------- (+0) | _mark | ----------------------- (+4) | _metadata | ----------------------- (+8) | 数组长度 length | ----------------------- (+12+4/0) | 下标为0的元素 | ----------------------- (+12+4/1) | 下标为1的元素 | ----------------------- | ... | ----------------------- (+12+4/n) | 下标为n的元素 | ----------------------- | ... | ----------------------- 就知道一大半了~ 跑一下Foo程序。留意到依赖SA的代码要编译的话需要$JAVA_HOME/lib/sa-jdi.jar在classpath上,执行时同理。指定GC算法为Parallel Scavenge,并指定GC堆(不包括perm gen)的初始和最大值都为1GB: [sajia@sajia ~]$ java -server -version java version "1.6.0_02" Java(TM) SE Runtime Environment (build 1.6.0_02-b05) Java HotSpot(TM) Server VM (build 1.6.0_02-b05, mixed mode) [sajia@sajia ~]$ javac Foo.java [sajia@sajia ~]$ javac -classpath ".:$JAVA_HOME/lib/sa-jdi.jar" TestPrintPSPermGen.java [sajia@sajia ~]$ java -server -XX:+UseParallelGC -XX:OnOutOfMemoryError='java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen %p > foo.txt' -Xms1g -Xmx1g Foo /# /# java.lang.OutOfMemoryError: Java heap space /# -XX:OnOutOfMemoryError="java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen %p > foo.txt" /# Executing /bin/sh -c "java -cp $JAVA_HOME/lib/sa-jdi.jar:. TestPrintPSPermGen 23373 > foo.txt"... Attaching to process ID 23373, please wait... Debugger attached successfully. Server compiler detected. JVM version is 1.6.0_02-b05 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at Foo.main(Foo.java:5) [sajia@sajia ~]$ 得到的foo.txt就是要演示的输出结果。把它压缩了放在附件里,有兴趣但懒得自己实验的同学也可以观摩一下~ 在foo.txt的开头可以看到: GC heap name: ParallelScavengeHeap Perm gen: [0x70e60000, 0x71e60000) PermSize: 16777216 这里显示了GC堆确实是Parallel Scavenge的,其中perm gen当前的起始地址为0x70e60000,结束地址为0x71e60000,中间连续的虚拟内存空间都分配给perm gen使用。简单计算一下可知perm gen大小为16MB,与下面打出的PermSize参数的值完全吻合。 通过阅读该日志文件,可以得知HotSpot在perm gen里存放的对象主要有:
  • Klass系对象
  • java.lang.Class对象
  • 字符串常量
  • 符号(Symbol/symbolOop)常量
  • 常量池对象
  • 方法对象 等等,以及它们所直接依赖的一些对象。具体这些都是什么留待以后有空再写。 接下来挑几个例子来简单讲解一下如何阅读这个日志文件里的对象描述。 首先看一个String对象。先看看JDK里java.lang.String对象的声明是什么样的: package java.lang; // imports ... public final class String implements java.io.Serializable, Comparable, CharSequence { /// The value is used for character storage. // private final char value[]; /// The offset is the first index of the storage that is used. // private final int offset; /// The count is the number of characters in the String. // private final int count; /// Cache the hash code for the string // private int hash; // Default to 0 /// use serialVersionUID from JDK 1.0.2 for interoperability // private static final long serialVersionUID = -6849794470754667710L; private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; public static final Comparator CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator(); private static class CaseInsensitiveComparator implements Comparator, java.io.Serializable { // ... } } 留意到String对象有4个成员域,分别是: 名字 类型 引用类型还是值类型 value char[] 引用类型 offset int 值类型 count int 值类型 hash int 值类型 String类自身有三个静态变量,分别是: 名字 类型 引用类型还是值类型 备注 serialVersionUID long 值类型 常量 serialPersistentFields java.io.ObjectStreamField[] 引用类型 只读变量 CASE_INSENSITIVE_ORDER java.lang.String.CaseInsensitiveComparator 引用类型 只读变量 回到我们的foo.txt日志文件来看一个String的对象实例: "main" @ 0x7100b140 (object size = 24) - _mark: {0} :1 - _klass: {4} :InstanceKlass for java/lang/String @ 0x70e6c6a0 - value: {8} :[C @ 0x7100b158 - offset: {12} :0 - count: {16} :4 - hash: {20} :0 这是在HotSpot的字符串池里的一个字符串常量对象,"main"。 日志中的“"main"”是对象的摘要,String对象有特别处理显示为它的内容,其它多数类型的对象都是显示类型名之类的。 在@符号之后的就是对象的起始地址,十六进制表示。 紧接着后面是对象占用GC堆的大小。很明显这个String对象自身占用了24字节。这里强调是“占用”的大小是因为对象除了存储必要的数据需要空间外,为了满足数据对齐的要求可能会有一部分空间作为填充数据而空占着。 String在内存中的布局是: ----------------------- (+0) | _mark | ----------------------- (+4) | _metadata | ----------------------- (+8) | value | ----------------------- (+12)| offset | ----------------------- (+16)| count | ----------------------- (+20)| hash | ----------------------- 32位HotSpot上要求64位/8字节对齐,String占用的24字节正好全部都是有效数据,不需要填充空数据。 上面的String实例在内存中的实际数据如下: 偏移量(字节) 数值(二进制表示) 数值(十六进制表示) 宽度(位/字节) +0 00000000000000000000000000000001 00000001 32位/4字节 +4 01110000111001101100011010100000 70e6c6a0 32位/4字节 +8 01110001000000001011000101011000 7100b158 32位/4字节 +12 00000000000000000000000000000000 00000000 32位/4字节 +16 00000000000000000000000000000100 00000004 32位/4字节 +20 00000000000000000000000000000000 00000000 32位/4字节 OK,那我们来每个成员域都过一遍,看看有何玄机。 第一个是_mark。在HotSpot的C++代码里它的类型是markOop,在SA里以sun.jvm.hotspot.oops.Mark来表现。 它属于对象头(object header)的一部分,是个多用途标记,可用于记录GC的标记(mark)状态、锁状态、偏向锁(bias-locking)状态、身份哈希值(identity hash)缓存等等。它的可能组合包括: 比特域(名字或常量值:位数) 标识(tag) *状态 身份哈希值:25, 年龄:4, 0:1 01 未锁 锁记录地址:30 00 被轻量级锁住 monitor对象地址:30 10 被重量级锁住 转向地址:30 11 被GC标记 线程ID:23, 纪元:2, 年龄:4, 1:1 01 被偏向锁住/可被偏向锁 例子中的"main"字符串的_mark值为1,也就是说它:
  • 没有被锁住;
  • 现在未被GC标记;
  • 年龄为0(尚未经历过GC);
  • 身份哈希值尚未被计算。 HotSpot的GC堆中许多创建没多久的对象的mark值都会是1,属于正常现象。 接下来看SA输出的日志中写为_klass而在我的图示上写为_metadata的这个域。 在HotSpot的C++代码里,oopDesc是所有放在GC堆上的对象的顶层类,它的成员就构成了对象头。HotSpot在C++代码中用instanceOopDesc类来表示Java对象,而该类继承oopDesc,所以HotSpot中的Java对象也自然拥有oopDesc所声明的头部。 hotspot/src/share/vm/oops/oop.hpp: class oopDesc { private: volatile markOop _mark; union _metadata { wideKlassOop _klass; narrowOop _compressed_klass; } _metadata; }; _metadata与前面提过的_mark一同构成了对象头。 _metadata是个union,为了能兼容32位、64位与开了压缩指针(CompressedOops)等几种情况。无论是这个union中的_klass还是_compressed_klass域,它们都是用于指向一个描述该对象的klass对象的指针。SA的API屏蔽了普通指针与压缩指针之间的差异,所以就直接把_metadata._klass称为了_klass。 对象头的格式是固定的,而对象自身内容的布局则由HotSpot根据一定规则来决定。Java类在被HotSpot加载时,其对象实例的布局与类自身的布局都会被计算出来。这个计算规则有机会以后再详细写。 现在来看看"main"这个String对象实例自身的域都是些什么。 value:指向真正保存字符串内容的对象的引用。留意Java里String并不把真正的字符内容直接存在自己里面,而是引用一个char[]对象来承载真正的存储。 从Java一侧看value域的类型是char[],而从HotSpot的C++代码来看它就是个普通的指针而已。它当前值是0x7100b158,指向一个char[]对象的起始位置。 offset:字符串的内容从value指向的char[]中的第几个字符开始算(0-based)。int型,32位带符号整数,这从Java和C++来看都差不多。当前值为0。 count:该字符串的长度,或者说包含的UTF-16字符的个数。类型同上。当前值为4,说明该字符串有4个UTF-16字符。 hash:缓存该String对象的哈希值的成员域。类型同上。当前值为0,说明该实例的String.hashCode()方法尚未被调用过,因而尚未缓存住该字符串的哈希值。 String对象的成员域都走过一遍了,来看看value所指向的对象状况。 [C @ 0x7100b158 (object size = 24) - _mark: {0} :1 - _klass: {4} :TypeArrayKlass for [C @ 0x70e60440 - _length: {8} :4 - 0: {12} :m - 1: {14} :a - 2: {16} :i - 3: {18} :n 这就是"main"字符串的value所引用的char[]的日志。 [C 是char[]在JVM中的内部名称。 在@符号之后的0x7100b158是该对象的起始地址。 该对象占用GC堆的大小是24字节。留意了哦。 看看它的成员域。 _mark与_klass构成的对象头就不重复介绍了。可以留意的是元素类型为原始类型(boolean、char、short、int、long、float、double)的数组在HotSpot的C++代码里是用typeArrayOopDesc来表示的;这里的char[]也不例外。描述typeArrayOopDesc的klass对象是typeArrayKlass类型的,所以可以看到日志里_klass的值写着TypeArrayKlass for [C。 接下来是_length域。HotSpot中,数组对象比普通对象的头要多一个域,正是这个描述数组元素个数的_length。Java语言中数组的.length属性、JVM字节码中的arraylength要取的也正是这个值。 日志中的这个数组对象有4个字符,所以_length值为4。 再后面就是数组的内容了。于是该char[]在内存中的布局是: ----------------------- (+0) | _mark | ----------------------- (+4) | _metadata | ----------------------- (+8) | 数组长度 length | ----------------------- (+12) | char[0] | char[1] | ----------------------- (+16) | char[2] | char[3] | ----------------------- (+20) | 填充0 | ----------------------- Java的char是UTF-16字符,宽度是16位/2字节;4个字符需要8字节,加上对象头的4/3=12字节,总共需要20字节。但该char[]却占用了GC堆上的24字节,正是因为前面提到的数据对齐要求——HotSpot要求GC堆上的对象是8字节对齐的,20向上找最近的8的倍数就是24了。用于对齐的这部分会被填充为0。 "main"对象的value指向的char[]也介绍过了,回过头来看看它的_metadata._klass所指向的klass对象又是什么状况。 从HotSpot的角度来看,klass就是用于描述GC堆上的对象的对象;如果一个对象的大小、域的个数与类型等信息不固定的话,它就需要特定的klass对象来描述。 instanceOopDesc用于表示Java对象,instanceKlass用于描述它,但自身却又有些不固定的信息需要被描述,因而又有instanceKlassKlass;如此下去会没完没了,所以有个klassKlass作为这个描述链上的终结符。 klass的关系图: (图片来源) 回到foo.txt日志文件上来,找到"main"对象的_klass域所引用的instanceKlass对象: InstanceKlass for java/lang/String @ 0x70e6c6a0 (object size = 384) - _mark: {0} :1 - _klass: {4} :InstanceKlassKlass @ 0x70e60168 - _java_mirror: {60} :Oop for java/lang/Class @ 0x70e77760 - _super: {64} :InstanceKlass for java/lang/Object @ 0x70e65af8 - _size_helper: {12} :6 - _name: {68} :/#java/lang/String @ 0x70e613e8 - _access_flags: {84} :134217777 - _subklass: {72} :null - _next_sibling: {76} :InstanceKlass for java/lang/CharSequence @ 0x70e680e8 - _alloc_count: {88} :0 - _array_klasses: {112} :ObjArrayKlass for InstanceKlass for java/lang/String @ 0x70ef6298 - _methods: {116} :ObjArray @ 0x70e682a0 - _method_ordering: {120} :[I @ 0x70e61330 - _local_interfaces: {124} :ObjArray @ 0x70e67998 - _transitive_interfaces: {128} :ObjArray @ 0x70e67998 - _nof_implementors: {268} :0 - _implementors[0]: {164} :null - _implementors[0]: {168} :null - _fields: {132} :[S @ 0x70e68230 - _constants: {136} :ConstantPool for java/lang/String @ 0x70e65c38 - _class_loader: {140} :null - _protection_domain: {144} :null - _signers: {148} :null - _source_file_name: {152} :/#String.java @ 0x70e67980 - _inner_classes: {160} :[S @ 0x70e6c820 - _nonstatic_field_size: {196} :4 - _static_field_size: {200} :4 - _static_oop_field_size: {204} :2 - _nonstatic_oop_map_size: {208} :1 - _is_marked_dependent: {212} :0 - _init_state: {220} :5 - _vtable_len: {228} :5 - _itable_len: {232} :9 - serialVersionUID: {368} :-6849794470754667710 - serialPersistentFields: {360} :ObjArray @ 0x74e882c8 - CASE_INSENSITIVE_ORDER: {364} :Oop for java/lang/String$CaseInsensitiveComparator @ 0x74e882c0 还记得上文提到过的String类的3个静态变量么?有没有觉得有什么眼熟的地方? 没错,在HotSpot中,Java类的静态变量就是作为该类对应的instanceKlass的实例变量出现的。上面的日志里最后三行描述了String的静态变量所在。 这是件非常自然的事:类用于描述对象,类自身也是对象,有用于描述自身的类;某个类的所谓“静态变量”就是该类对象的实例变量。很多对象系统都是这么设计的。HotSpot的这套oop体系(指“普通对象指针”,不是指“面向对象编程”)继承自Strongtalk,实际上反而比暴露给Java的对象模型显得更加面向对象一些。 HotSpot并不把instanceKlass暴露给Java,而会另外创建对应的java.lang.Class对象,并将后者称为前者的“Java镜像”,两者之间互相持有引用。日志中的_java_mirror便是该instanceKlass对Class对象的引用。 镜像机制被认为是良好的面向对象的反射与元编程设计的重要机制。Gilad Bracha与David Ungar还专门写了篇论文来阐述此观点,参考Mirrors: Design Principles for Meta-level Facilities of Object-Oriented Programming Languages。 顺带把"main"对象的_klass链上余下的两个对象的日志也贴出来: InstanceKlassKlass @ 0x70e60168 (object size = 120) - _mark: {0} :1 - _klass: {4} :KlassKlass @ 0x70e60000 - _java_mirror: {60} :Oop for java/lang/Class @ 0x70e76f20 - _super: {64} :null - _size_helper: {12} :0 - _name: {68} :null - _access_flags: {84} :0 - _subklass: {72} :null - _next_sibling: {76} :null - _alloc_count: {88} :0 所有instanceKlass对象都是被这个instanceKlassKlass对象所描述的。 KlassKlass @ 0x70e60000 (object size = 120) - _mark: {0} :1 - _klass: {4} :KlassKlass @ 0x70e60000 - _java_mirror: {60} :Oop for java/lang/Class @ 0x70e76e00 - _super: {64} :null - _size_helper: {12} :0 - _name: {68} :null - _access_flags: {84} :0 - _subklass: {72} :null - _next_sibling: {76} :null - _alloc_count: {88} :0 而所有/KlassKlass对象都是被这个klassKlass对象所描述的。 klass对象的更详细的介绍也留待以后再写吧~至少得找时间写写instanceKlass与vtable、itable的故事。 嘛,今天的废话到此结束 ^^ 希望这帖能解答先前关于java里面的全局变量的内存分配一帖中的疑问。也希望大家能多多支持高级语言虚拟机圈子,有什么HLL VM相关的话题想讨论的欢迎来转转~

声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。 推荐链接

  • adobe 返回顶楼 * melin
  • 等级: 三星会员
  • melin的博客
  • 文章: 327
  • 积分: 383
  • 来自: 合肥
  • 发表时间:前天

兄弟有点入魔了。。。 返回顶楼 回帖地址

0 0 请登录后投票 * kimmking

  • 等级: 二钻会员
  • kimmking的博客
  • 文章: 2091
  • 积分: 1160
  • 来自: 中华大丈夫学院
  • 发表时间:前天

RednaxelaFX 的帖子,太深奥了。 我找本编译原理补补先。 返回顶楼 回帖地址

0 0 请登录后投票 * youjianbo_han_87

  • 等级: 初级会员
  • youjianbo_han_87的博客
  • 文章: 62
  • 积分: 0
  • 来自: 上海
  • 发表时间:前天

确实不错,基础基础。 返回顶楼 回帖地址

0 0 请登录后投票 * reilost

  • 等级: 初级会员
  • reilost的博客
  • 文章: 74
  • 积分: 20
  • 来自: 北京
  • 发表时间:前天

-。-fx同学威武。。每次看你的帖子都想回去再看一遍编译原理 返回顶楼 回帖地址

0 0 请登录后投票 * kaka2008

  • 等级: 二星会员
  • kaka2008的博客
  • 文章: 241
  • 积分: 200
  • 来自: 北京
  • 发表时间:前天

fx同学出手即精华 而且帖子太深奥。。。 返回顶楼 回帖地址

0 0 请登录后投票 * liujun999999

  • 等级: 初级会员
  • liujun999999的博客
  • 文章: 35
  • 积分: 30
  • 来自: 广州
  • 发表时间:前天

晕,说实话,我没有看懂,看出差距来了,唉 返回顶楼 回帖地址

0 0 请登录后投票 * hymanyung

  • 等级: 初级会员
  • hymanyung的博客
  • 文章: 5
  • 积分: 30
  • 来自: 东莞
  • 发表时间:前天

看得頭暈了. 返回顶楼 回帖地址

0 0 请登录后投票 * seraphim871211

  • 等级: 初级会员
  • seraphim871211的博客
  • 文章: 58
  • 积分: 50
  • 来自: 杭州
  • 发表时间:前天

“大叔”,你最近的帖子都看不下去了。。。 返回顶楼 回帖地址

0 0 请登录后投票 * AlwenS

  • 等级: 初级会员
  • AlwenS的博客
  • 文章: 20
  • 积分: 30
  • 来自: 深圳
  • 发表时间:前天 最后修改:前天

看完鸟,楼主的动手能力真强。 我也喜欢探究这些东西,只是功力还不如楼主深。 其实这东东和编译原理倒没多大关系了,主要看的是具体OO语言的Runtime Env模型。 比如c++就是没有penmgen的,所以他的动态能力和RTTI都非常弱。 返回顶楼 回帖地址

0 0 请登录后投票

« 上一页 1 2 下一页 » 论坛首页Java编程和Java企业应用版Java综合 跳转论坛:Java编程和Java企业应用 Web前端技术 移动编程和手机应用开发 C/C++编程 Ruby编程 Python编程 PHP编程 Flash编程和RIA Microsoft .Net 综合技术 软件开发和项目管理 行业应用 入门讨论 招聘求职 海阔天空

© 2003-2010 JavaEye.com. 上海炯耐计算机软件有限公司版权所有 [ 沪ICP备05023328号 ]

优化技巧分享:把内存消耗降低至原来的1

Posted on

优化技巧分享:把内存消耗降低至原来的1/20

伯乐在线注:昨天在微博中推荐了英文原文,感谢@NULL_文龙 和@战斗的那美克星人 的热情。以下是译文。

——————————–

这是最近发生的又一起内存相关的事件了。这个案例是从一个最近的客户报告中提取出来,一个异常运行的应用在其产品中反复报告内存耗尽。

这个症状是由我们的一个实验性功能发现,它主要用来监测某一类数据结构的使用情况。它提供了一个信号探针,结果会指向问题源代码的某一位置。为了保护客户的隐私,我们人为重建了该例子并保持它同原真实场景在技术层面的一致性。你可以免费在此处下载到源码

故事开始于一组从外界源加载进来的对象。同外部的信息交互是基于XML的接口,这本身并没什么大不了的,但事实上“基于XML的格式进行通讯”的实现细节被分散到了系统的每一个角落。 传入系统的文档是首先被转换成XMLBean实例,然后在整个系统范围内被使用,这中做法听起来有点傻。

整个问题中最核心的部分是一个延迟加载的缓冲方案。缓存的对象是“Person”的实例: 1

2 3

4 5

6 7 // Imports and methods removed to improve readability

public

class

Person {

private

String id;

private

Date dateOfBirth;

private

String forename;

private

String surname; }

你也许会说这才能消耗多少内存呢。但当我们揭开进一步的细节时,发现事情就变了味了。表面上根据设计,声称实现只用到的诸如上文提到的那样一些简单的类,但真实的情形是使用了基于模型生成的数据结构。使用的模型是诸如下面的这个简化的XSD片段。

1

2 3

4 5

6 7

8 9

10 11

12 13

14 <

xs:schema

targetNamespace

=

"http://plumbr.eu"

xmlns:xs

=

"http://www.w3.org/2001/XMLSchema"

elementFormDefault

=

"qualified"

>

<

xs:element

name

=

"person"

>

<

xs:complexType

>

<

xs:sequence

>

<

xs:element

name

=

"id"

type

=

"xs:string"

/>

<

xs:element

name

=

"dateOfBirth"

type

=

"xs:dateTime"

/>

<

xs:element

name

=

"forename"

type

=

"xs:string"

/>

<

xs:element

name

=

"surname"

type

=

"xs:string"

/>

</

xs:sequence

>

</

xs:complexType

>

</

xs:element

>

</

xs:schema

>

使用XMLBeans,开发者生成了该模型,并在真实的场景中使用。现在我们回到开始的这个缓存的方案上来,假设它设计初衷是为了支持最多1.3M Person类的实例,而我们实际却要塞进去同等数量的大家伙,这从根上就注定了失败。

跑一组测试用例后,发现1.3M个基于XMLBean的生成的实例需要消耗大概1.5GB的堆空间。我们当时想这肯定可以做的更好。

第一个改进是显而易见的,外部同系统内部集成的实现细节是不应该把影响传递给系统的每一个角落的。所以我们把缓存改成了使用简单的 java.util.HashMap。ID是键,Person是值。我们发现内存的消耗立即降低到了214MB。但这还不能令我们满意。

由于Map中的键是一个数,我们有十足的理由使用Trove Collections来进一步降低它的内存消耗。这在实现上的改动很快,我们只需把 HashMap 改成 TLongObjectHashMap ,堆的消耗进一步降低到了143MB。

活干到这个程度我们已经可以收工了,但是工程师的好奇心驱使我们要更进一步。不由自主的我们发现了系统的数据存在着大量的重复信息。例如Date Of Birth其实已经在ID中编码了,所以Date Of Birth可以直接从ID中得到,而不必使用额外的空间去它。

经过改良,Person类现在变成了这个样子: 1

2 3

4 5

6 // Imports and methods removed to improve readability

public

class

Person {

private

String id;

private

String forename;

private

String surname;

}

重新跑一边测试证实我们的改进的确有效,堆消耗降低到了93MB。但是我们还未满足。

该应用在64位的机器上使用老的JDK6。默认情况下,这么做不能压缩普通对象的指针的。通过参数”-XX:UseCompressedOops“切换到压缩模式使我们获得了额外的收获,现在我们的内存消耗降低到了73MB。

当然,我们还能走的更远。比如基于键值建立B-tree,但这已经开始影响到了代码的可读性,所以我们决定到此为止。降低21.5倍的堆内存应该已经是一个足够好的结果了。

让我们在重复一下学到了什么

  • 别把同外部模块的整合影响到系统的每一个角落
  • 冗余的数据可能带来开销。在可能的情况下尽量消除它
  • 基本数据类型是你最经常打交道的朋友,务必知道些关于它们的工具,如果还没玩过Trove请立刻开始吧
  • JVM自带的优化技术不可忽视

如果你对这个实验很好奇,请在此处下载相关的代码。使用到的的测量工具和其具体描述可以在这篇博文找到。

原文链接: Nikita Salnikov-Tarnovski ,编译:感谢@NULL_文龙 的热心翻译

译文链接:http://blog.jobbole.com/40666/

代码难道不是这么写的?讨论第18页

Posted on

代码难道不是这么写的?讨论第18页 - Java综合 - Java - JavaEye论坛

您还未登录 ! 我的应用 登录 注册

JavaEye-最棒的软件开发交流社区

论坛首页Java编程和Java企业应用版Java综合

代码难道不是这么写的?

全部 Hibernate Spring Struts iBATIS 企业应用 设计模式 DAO 领域模型 OO Tomcat SOA JBoss Swing Java综合

myApps快速开发平台,配置即开发、所见即所得,节约85%工作量

« 上一页 1 217 18 19 20 下一页 » 浏览 21808 次 主题:代码难道不是这么写的?

该帖已经被评为良好帖 作者 正文 * moonranger

  • 等级: 初级会员
  • moonranger的博客
  • 文章: 44
  • 积分: 40
  • 来自: 天津
  • 发表时间:2010-07-30

< > 猎头职位:

qiushily2030 写道 fengfeng925 写道

qiushily2030 写道

A a; 申明在内部,每次遍历都申明一个a的变量.
申明在外面 只申明一次 一直复用. 申明也是要资源的.. 一个变量就相当于一个指针,循环1亿次,LZ你说呢. 评审官说的还是有理的,不过这个得看需求. JVM回收是在内存不足的时候. 还是不要想当然了,我就那么干过一回. 程序是最真实的答案... 简直是TMD胡扯。 JVM回收是在内存不足的时候.这个已经修正. 不是内存不足才调用。。 你说我胡扯? 那for里定义的局部变量放哪里?不要也和上面一个的观点一样:只申明一次,后调用索引. 这个我不可理解。。 方法的参数连同方法里的变量所占用的空间,在编译时就已经确定了,它决定了运行时一个方法被调用的时侯要分配多大的栈帧。一个变量放在循环外还是循环里,只是在编译期限定了其作用域而已,到了运行时是没有区别的(也许只是在栈帧的local variables区里的位置不同)。 看一下方法的字节码,一切都非常明了: private final int[] numbers = {1, 2, 3, 4, 5}; public void varOutsideLoop() { int n; for (int i = 0; i < numbers.length; i++) { n = numbers[i]; System.out.println(n); } } public void varInsideLoop() { for (int i = 0; i < numbers.length; i++) { int n = numbers[i]; System.out.println(n); } } 对应的字节码: public void varOutsideLoop(); Signature: ()V Code: Stack=2, Locals=3, Args_size=1 0: iconst_0 1: istore_2 2: goto 22 5: aload_0 6: getfield /#12; //Field numbers:[I 9: iload_2 10: iaload 11: istore_1 12: getstatic /#19; //Field java/lang/System.out:Ljava/io/PrintStream; 15: iload_1 16: invokevirtual /#25; //Method java/io/PrintStream.println:(I)V 19: iinc 2, 1 22: iload_2 23: aload_0 24: getfield /#12; //Field numbers:[I 27: arraylength 28: if_icmplt 5 31: return LineNumberTable: line 43: 0 line 44: 5 line 45: 12 line 43: 19 line 47: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lcom/jstudio/learnjvm/classloader/VarInLoop; 12 10 1 n I 2 29 2 i I public void varInsideLoop(); Signature: ()V Code: Stack=2, Locals=3, Args_size=1 0: iconst_0 1: istore_1 2: goto 22 5: aload_0 6: getfield /#12; //Field numbers:[I 9: iload_1 10: iaload 11: istore_2 12: getstatic /#19; //Field java/lang/System.out:Ljava/io/PrintStream; 15: iload_2 16: invokevirtual /#25; //Method java/io/PrintStream.println:(I)V 19: iinc 1, 1 22: iload_1 23: aload_0 24: getfield /#12; //Field numbers:[I 27: arraylength 28: if_icmplt 5 31: return LineNumberTable: line 50: 0 line 51: 5 line 52: 12 line 50: 19 line 54: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lcom/jstudio/learnjvm/classloader/VarInLoop; 2 29 1 i I 12 7 2 n I 仔细看字节码就能发现,除了n和循环变量i在local variable table中的位置不同,其他的所有的都没有区别。 varOutsideLoop那个方法里我故意没有初始化n,如果加上初始化,就会多一条iconst和一条istore指令。 至于位置不一样的原因,个人猜测与变量出现的位置有关,n在循环外的版本,它出现在前,所以占据了第一个slot(第0个slot是对象本身,即this);n在循环里的版本,循环变量i出现在前,所以i占据了第一个slot,而n占用的是第二个slot。 除此之外,两个方法没有任何的区别。 mercyblitz 写道

主要你误解了a变量在遍历中,有入栈和出栈的操作。并没有重复开辟局部变量,再说JVM优化之后,不会重复创建。 个人认为这种说法也不太对,在一个方法内部不会有所谓的“入栈”和“出栈”操作。方法里,一个变量占用一个local variable table的slot,一个萝卜一个坑。所有这些变量都是在方法结束,堆栈帧被销毁的时侯才真正被“回收”,即使方法结束的时侯它早以超出了自己的作用域…… 实践中,变量的作用域当然是尽可能小才好,即有利于垃圾收集、也消除了一些潜在的导致bug的隐患。 楼主别理那个“代码评审官”就好……话说如果我遇到这种人这种事,我八成不愿继续干下去…… 返回顶楼 回帖地址

5 0 请登录后投票 * mercyblitz

  • 等级: 初级会员
  • mercyblitz的博客
  • 文章: 465
  • 积分: 30
  • 来自: 长沙市
  • 发表时间:2010-07-30

moonranger 写道

qiushily2030 写道

fengfeng925 写道

qiushily2030 写道

A a; 申明在内部,每次遍历都申明一个a的变量.
申明在外面 只申明一次 一直复用. 申明也是要资源的.. 一个变量就相当于一个指针,循环1亿次,LZ你说呢. 评审官说的还是有理的,不过这个得看需求. JVM回收是在内存不足的时候. 还是不要想当然了,我就那么干过一回. 程序是最真实的答案... 简直是TMD胡扯。 JVM回收是在内存不足的时候.这个已经修正. 不是内存不足才调用。。 你说我胡扯? 那for里定义的局部变量放哪里?不要也和上面一个的观点一样:只申明一次,后调用索引. 这个我不可理解。。 方法的参数连同方法里的变量所占用的空间,在编译时就已经确定了,它决定了运行时一个方法被调用的时侯要分配多大的栈帧。一个变量放在循环外还是循环里,只是在编译期限定了其作用域而已,到了运行时是没有区别的(也许只是在栈帧的local variables区里的位置不同)。 看一下方法的字节码,一切都非常明了: private final int[] numbers = {1, 2, 3, 4, 5}; public void varOutsideLoop() { int n; for (int i = 0; i < numbers.length; i++) { n = numbers[i]; System.out.println(n); } } public void varInsideLoop() { for (int i = 0; i < numbers.length; i++) { int n = numbers[i]; System.out.println(n); } } 对应的字节码: public void varOutsideLoop(); Signature: ()V Code: Stack=2, Locals=3, Args_size=1 0: iconst_0 1: istore_2 2: goto 22 5: aload_0 6: getfield /#12; //Field numbers:[I 9: iload_2 10: iaload 11: istore_1 12: getstatic /#19; //Field java/lang/System.out:Ljava/io/PrintStream; 15: iload_1 16: invokevirtual /#25; //Method java/io/PrintStream.println:(I)V 19: iinc 2, 1 22: iload_2 23: aload_0 24: getfield /#12; //Field numbers:[I 27: arraylength 28: if_icmplt 5 31: return LineNumberTable: line 43: 0 line 44: 5 line 45: 12 line 43: 19 line 47: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lcom/jstudio/learnjvm/classloader/VarInLoop; 12 10 1 n I 2 29 2 i I public void varInsideLoop(); Signature: ()V Code: Stack=2, Locals=3, Args_size=1 0: iconst_0 1: istore_1 2: goto 22 5: aload_0 6: getfield /#12; //Field numbers:[I 9: iload_1 10: iaload 11: istore_2 12: getstatic /#19; //Field java/lang/System.out:Ljava/io/PrintStream; 15: iload_2 16: invokevirtual /#25; //Method java/io/PrintStream.println:(I)V 19: iinc 1, 1 22: iload_1 23: aload_0 24: getfield /#12; //Field numbers:[I 27: arraylength 28: if_icmplt 5 31: return LineNumberTable: line 50: 0 line 51: 5 line 52: 12 line 50: 19 line 54: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lcom/jstudio/learnjvm/classloader/VarInLoop; 2 29 1 i I 12 7 2 n I 仔细看字节码就能发现,除了n和循环变量i在local variable table中的位置不同,其他的所有的都没有区别。 varOutsideLoop那个方法里我故意没有初始化n,如果加上初始化,就会多一条iconst和一条istore指令。 至于位置不一样的原因,个人猜测与变量出现的位置有关,n在循环外的版本,它出现在前,所以占据了第一个slot(第0个slot是对象本身,即this);n在循环里的版本,循环变量i出现在前,所以i占据了第一个slot,而n占用的是第二个slot。 除此之外,两个方法没有任何的区别。 mercyblitz 写道

主要你误解了a变量在遍历中,有入栈和出栈的操作。并没有重复开辟局部变量,再说JVM优化之后,不会重复创建。 个人认为这种说法也不太对,在一个方法内部不会有所谓的“入栈”和“出栈”操作。方法里,一个变量占用一个local variable table的slot,一个萝卜一个坑。所有这些变量都是在方法结束,堆栈帧被销毁的时侯才真正被“回收”,即使方法结束的时侯它早以超出了自己的作用域…… 实践中,变量的作用域当然是尽可能小才好,即有利于垃圾收集、也消除了一些潜在的导致bug的隐患。 楼主别理那个“代码评审官”就好……话说如果我遇到这种人这种事,我八成不愿继续干下去…… 恩,谢谢指正,我也仔细想了一下。在楼主的例子中,字节码说明两个局部变量指向不同的地方。不会重复开辟。要想开辟更多,哪么需要不同名称的变量申明。 返回顶楼 回帖地址

0 0 请登录后投票 * cisumer

  • 等级: 初级会员
  • cisumer的博客
  • 文章: 4
  • 积分: 30
  • 来自: 上海
  • 发表时间:2010-07-30

ei0 写道

我只对s感兴趣!!! 返回顶楼 回帖地址

0 2 请登录后投票 * beneo

  • 等级: 初级会员
  • beneo的博客
  • 文章: 115
  • 积分: 50
  • 来自: 希伯來
  • 发表时间:2010-07-30

mercyblitz 写道

moonranger 写道

qiushily2030 写道

fengfeng925 写道

qiushily2030 写道

A a; 申明在内部,每次遍历都申明一个a的变量.
申明在外面 只申明一次 一直复用. 申明也是要资源的.. 一个变量就相当于一个指针,循环1亿次,LZ你说呢. 评审官说的还是有理的,不过这个得看需求. JVM回收是在内存不足的时候. 还是不要想当然了,我就那么干过一回. 程序是最真实的答案... 简直是TMD胡扯。 JVM回收是在内存不足的时候.这个已经修正. 不是内存不足才调用。。 你说我胡扯? 那for里定义的局部变量放哪里?不要也和上面一个的观点一样:只申明一次,后调用索引. 这个我不可理解。。 方法的参数连同方法里的变量所占用的空间,在编译时就已经确定了,它决定了运行时一个方法被调用的时侯要分配多大的栈帧。一个变量放在循环外还是循环里,只是在编译期限定了其作用域而已,到了运行时是没有区别的(也许只是在栈帧的local variables区里的位置不同)。 看一下方法的字节码,一切都非常明了: private final int[] numbers = {1, 2, 3, 4, 5}; public void varOutsideLoop() { int n; for (int i = 0; i < numbers.length; i++) { n = numbers[i]; System.out.println(n); } } public void varInsideLoop() { for (int i = 0; i < numbers.length; i++) { int n = numbers[i]; System.out.println(n); } } 对应的字节码: public void varOutsideLoop(); Signature: ()V Code: Stack=2, Locals=3, Args_size=1 0: iconst_0 1: istore_2 2: goto 22 5: aload_0 6: getfield /#12; //Field numbers:[I 9: iload_2 10: iaload 11: istore_1 12: getstatic /#19; //Field java/lang/System.out:Ljava/io/PrintStream; 15: iload_1 16: invokevirtual /#25; //Method java/io/PrintStream.println:(I)V 19: iinc 2, 1 22: iload_2 23: aload_0 24: getfield /#12; //Field numbers:[I 27: arraylength 28: if_icmplt 5 31: return LineNumberTable: line 43: 0 line 44: 5 line 45: 12 line 43: 19 line 47: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lcom/jstudio/learnjvm/classloader/VarInLoop; 12 10 1 n I 2 29 2 i I public void varInsideLoop(); Signature: ()V Code: Stack=2, Locals=3, Args_size=1 0: iconst_0 1: istore_1 2: goto 22 5: aload_0 6: getfield /#12; //Field numbers:[I 9: iload_1 10: iaload 11: istore_2 12: getstatic /#19; //Field java/lang/System.out:Ljava/io/PrintStream; 15: iload_2 16: invokevirtual /#25; //Method java/io/PrintStream.println:(I)V 19: iinc 1, 1 22: iload_1 23: aload_0 24: getfield /#12; //Field numbers:[I 27: arraylength 28: if_icmplt 5 31: return LineNumberTable: line 50: 0 line 51: 5 line 52: 12 line 50: 19 line 54: 31 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lcom/jstudio/learnjvm/classloader/VarInLoop; 2 29 1 i I 12 7 2 n I 仔细看字节码就能发现,除了n和循环变量i在local variable table中的位置不同,其他的所有的都没有区别。 varOutsideLoop那个方法里我故意没有初始化n,如果加上初始化,就会多一条iconst和一条istore指令。 至于位置不一样的原因,个人猜测与变量出现的位置有关,n在循环外的版本,它出现在前,所以占据了第一个slot(第0个slot是对象本身,即this);n在循环里的版本,循环变量i出现在前,所以i占据了第一个slot,而n占用的是第二个slot。 除此之外,两个方法没有任何的区别。 mercyblitz 写道

主要你误解了a变量在遍历中,有入栈和出栈的操作。并没有重复开辟局部变量,再说JVM优化之后,不会重复创建。 个人认为这种说法也不太对,在一个方法内部不会有所谓的“入栈”和“出栈”操作。方法里,一个变量占用一个local variable table的slot,一个萝卜一个坑。所有这些变量都是在方法结束,堆栈帧被销毁的时侯才真正被“回收”,即使方法结束的时侯它早以超出了自己的作用域…… 实践中,变量的作用域当然是尽可能小才好,即有利于垃圾收集、也消除了一些潜在的导致bug的隐患。 楼主别理那个“代码评审官”就好……话说如果我遇到这种人这种事,我八成不愿继续干下去…… 恩,谢谢指正,我也仔细想了一下。在楼主的例子中,字节码说明两个局部变量指向不同的地方。不会重复开辟。要想开辟更多,哪么需要不同名称的变量申明。 第一个是frame append 第二个是frame same 所以还是有些许区别的吧 返回顶楼 回帖地址

0 0 请登录后投票 * RednaxelaFX

  • 等级: 五星会员
  • RednaxelaFX的博客
  • 文章: 385
  • 积分: 790
  • 来自: 杭州
  • 发表时间:2010-07-30 最后修改:2010-07-30

moonranger 写道

mercyblitz 写道

主要你误解了a变量在遍历中,有入栈和出栈的操作。并没有重复开辟局部变量,再说JVM优化之后,不会重复创建。 个人认为这种说法也不太对,在一个方法内部不会有所谓的“入栈”和“出栈”操作。方法里,一个变量占用一个local variable table的slot,一个萝卜一个坑。所有这些变量都是在方法结束,堆栈帧被销毁的时侯才真正被“回收”,即使方法结束的时侯它早以超出了自己的作用域…… ECMA-335 CLI是规定方法中每个局部变量占用局部变量区的一个坑,不在方法中复用局部变量区。但它在设计之初就是倾向于使用JIT编译器来实现执行引擎的;虽然中间代码(CIL)中局部变量不复用局部变量区的坑,但实际JIT编译后的代码不关心这个,局部变量的存储分配(栈/寄存器)还是有复用。可以参考.NET CLR的实现。 (以下说明针对概念中的Java虚拟机) Java虚拟机在设计之初就同时考虑到要能方便的用解释器或JIT编译器来实现。解释器一般做的优化较少,执行过程与字节码中指定的方式基本是直接对应的,所以字节码上就已经需要考虑到实际执行的情况了。Java虚拟机的局部变量区是可复用的,在同一方法里作用域不交叠的局部变量可以分配在同一个局部变量区的slot上。有兴趣的话可以自己做实验验证,也可以参考之前我做的分享的演示稿,20100621的版本。 例子的话,可以看这么一段代码: public class LocalsDemo { // locals = 5 public static void main(String[] args) { // args in slot 0 int a = 1; // a in slot 1 int b = 2; // b in slot 2 { long c = 3; // c in slot 3 (to slot 4) } { int d = 4; // d in slot 3 int e = 5; // e in slot 4 } for (int i = 0; i < 3; i++) { // i in slot 3 int j = i + 1; // j in slot 4 } } } main()方法编译后对应的字节码和元信息是: public static void main(java.lang.String[]); Code: Stack=2, Locals=5, Args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: ldc2_w /#2; //long 3l 7: lstore_3 8: iconst_4 9: istore_3 10: iconst_5 11: istore 4 13: iconst_0 14: istore_3 15: iload_3 16: iconst_3 17: if_icmpge 31 20: iload_3 21: iconst_1 22: iadd 23: istore 4 25: iinc 3, 1 28: goto 15 31: return LineNumberTable: line 4: 0 line 5: 2 line 7: 4 line 10: 8 line 11: 10 line 13: 13 line 14: 20 line 13: 25 line 16: 31 LocalVariableTable: Start Length Slot Name Signature 8 0 3 c J 10 3 3 d I 13 0 4 e I 25 0 4 j I 15 16 3 i I 0 32 0 args [Ljava/lang/String; 2 30 1 a I 4 28 2 b I 要在Class文件里看到LocalVariableTable属性表的话,编译Java源码时请加上-g参数。 Java虚拟机中, 方法调用涉及的栈帧push/pop是对Java栈(Java stack),或者有些文档里叫“Java控制栈”(Java control stack),或者叫“Java方法调用栈”; 在方法中局部变量的写/读操作涉及的push/pop则是对操作数栈(operand stack),或者有些文档叫“表达式栈”(expression stack),或者叫求值栈(evaluation stack)。 两个栈的区别可以参考Java虚拟机规范第二版3.5.23.6.2两个小节,我之前的介绍基于栈/寄存器的虚拟机的帖里也有提到,也可以参考前面说的JVM分享的演示稿。 最后上一老图……逃 返回顶楼 回帖地址

7 0 请登录后投票 * moonranger

  • 等级: 初级会员
  • moonranger的博客
  • 文章: 44
  • 积分: 40
  • 来自: 天津
  • 发表时间:2010-07-30

RednaxelaFX 写道

moonranger 写道

mercyblitz 写道

主要你误解了a变量在遍历中,有入栈和出栈的操作。并没有重复开辟局部变量,再说JVM优化之后,不会重复创建。 个人认为这种说法也不太对,在一个方法内部不会有所谓的“入栈”和“出栈”操作。方法里,一个变量占用一个local variable table的slot,一个萝卜一个坑。所有这些变量都是在方法结束,堆栈帧被销毁的时侯才真正被“回收”,即使方法结束的时侯它早以超出了自己的作用域…… ECMA-335 CLI是规定方法中每个局部变量占用局部变量区的一个坑,不在方法中复用局部变量区。但它在设计之初就是倾向于使用JIT编译器来实现执行引擎的;虽然中间代码(CIL)中局部变量不复用局部变量区的坑,但实际JIT编译后的代码不关心这个,局部变量的存储分配(栈/寄存器)还是有复用。可以参考.NET CLR的实现。 (以下说明针对概念中的Java虚拟机) Java虚拟机在设计之初就同时考虑到要能方便的用解释器或JIT编译器来实现。解释器一般做的优化较少,执行过程与字节码中指定的方式基本是直接对应的,所以字节码上就已经需要考虑到实际执行的情况了。Java虚拟机的局部变量区是可复用的,在同一方法里作用域不交叠的局部变量可以分配在同一个局部变量区的slot上。有兴趣的话可以自己做实验验证,也可以参考之前我做的分享的演示稿,20100621的版本。 例子的话,可以看这么一段代码: public class LocalsDemo { // locals = 5 public static void main(String[] args) { // args in slot 0 int a = 1; // a in slot 1 int b = 2; // b in slot 2 { long c = 3; // c in slot 3 (to slot 4) } { int d = 4; // d in slot 3 int e = 5; // e in slot 4 } for (int i = 0; i < 3; i++) { // i in slot 3 int j = i + 1; // j in slot 4 } } } main()方法编译后对应的字节码和元信息是: public static void main(java.lang.String[]); Code: Stack=2, Locals=5, Args_size=1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: ldc2_w /#2; //long 3l 7: lstore_3 8: iconst_4 9: istore_3 10: iconst_5 11: istore 4 13: iconst_0 14: istore_3 15: iload_3 16: iconst_3 17: if_icmpge 31 20: iload_3 21: iconst_1 22: iadd 23: istore 4 25: iinc 3, 1 28: goto 15 31: return LineNumberTable: line 4: 0 line 5: 2 line 7: 4 line 10: 8 line 11: 10 line 13: 13 line 14: 20 line 13: 25 line 16: 31 LocalVariableTable: Start Length Slot Name Signature 8 0 3 c J 10 3 3 d I 13 0 4 e I 25 0 4 j I 15 16 3 i I 0 32 0 args [Ljava/lang/String; 2 30 1 a I 4 28 2 b I 要在Class文件里看到LocalVariableTable属性表的话,编译Java源码时请加上-g参数。 Java虚拟机中, 方法调用涉及的栈帧push/pop是对Java栈(Java stack),或者有些文档里叫“Java控制栈”(Java control stack),或者叫“Java方法调用栈”; 在方法中局部变量的读/写操作涉及的push/pop则是对操作数栈(operand stack),或者有些文档叫“表达式栈”(expression stack),或者叫求值栈(evaluation stack)。 两个栈的区别可以参考Java虚拟机规范第二版3.5.23.6.2两个小节,我之前的介绍基于栈/寄存器的虚拟机的帖里也有提到,也可以参考前面说的JVM分享的演示稿。 最后上一老图……逃 还真没有研究到这种程度。学习了! 返回顶楼 回帖地址

2 0 请登录后投票 * linliangyi2007

  • 等级: 一钻会员
  • linliangyi2007的博客
  • 文章: 803
  • 积分: 996
  • 来自: 福州
  • 发表时间:2010-07-30

高人出现了,哈哈哈!! 哪些只懂得代码文本面上搞所谓性能优化的家伙,可以羞愧的匿了~~~ 返回顶楼 回帖地址

0 0 请登录后投票 * murainwood

  • 等级: 初级会员
  • murainwood的博客
  • 文章: 1077
  • 积分: 30
  • 来自: ...
  • 发表时间:2010-07-30

精彩,秒杀! 返回顶楼 回帖地址

0 0 请登录后投票 * 分离的北极熊

  • 等级: 初级会员
  • 分离的北极熊的博客
  • 文章: 19
  • 积分: 30
  • 来自: 北京
  • 发表时间:2010-07-30

太狠了……………… 返回顶楼 回帖地址

0 0 请登录后投票 * lijihuai

  • 等级: 初级会员
  • lijihuai的博客
  • 文章: 8
  • 积分: 30
  • 发表时间:2010-07-30

没事,实际上,编译以后的字节码是一样的 返回顶楼 回帖地址

0 0 请登录后投票

« 上一页 1 217 18 19 20 下一页 » 论坛首页Java编程和Java企业应用版Java综合 跳转论坛:Java编程和Java企业应用 Web前端技术 移动编程和手机应用开发 C/C++编程 Ruby编程 Python编程 PHP编程 Flash编程和RIA Microsoft .Net 综合技术 软件开发和项目管理 行业应用 入门讨论 招聘求职 海阔天空

© 2003-2010 JavaEye.com. 上海炯耐计算机软件有限公司版权所有 [ 沪ICP备05023328号 ]

JAVA HOTSPOT VM参数大全

Posted on

JAVA HOTSPOT VM参数大全

首页 新闻 论坛 问答 博客 招聘 更多 ▼

专栏 圈子 搜索

您还未登录 ! 我的应用 登录 注册

幸福的懦夫

永久域名 http://longdick.javaeye.com/

2顶 0踩

Freaker与妖人 | 图解JVM在内存中申请对象及垃圾回收流程

2009-09-20

JAVA HOTSPOT VM参数大全

关键字: hotspot vm 参数 ///

/* 转载请注明作者longdick http://longdick.javaeye.com

/*

/*/

(本文JDK版本6.0)

SUN的JDK版本从1.3.1开始使用HotSpot虚拟机技术。 HotSpot是较新的Java虚拟机技术,用来代替JIT(Just in Time)技术,可以大大提高Java运行的性能。 Java原先是把源代码编译为字节码在虚拟机执行,这样执行速度较慢。而该技术将常用的部分代码编译为本地(原生,native)代码,这样 显著提高了性能。 用于服务器版和标准版的HotSpot有所不同。 其他的Java虚拟机也有类似的技术。 HotSpot JVM 参数可以分为标准参数(standard options)和非标准参数(non-standard options)。 标准参数相对稳定,在JDK未来的版本里不会有太大的改动。 非标准参数则有因升级JDK而改变的可能。 标准参数: -client 使用Java HotSpot 客户端版VM。 -server 使用Java HotSpot 服务器版VM。如果是64位的JDK,默认只有server版,所以以上两个参数对64位版本JDK无效。 -agentlib: libname [=options] 加载本地代理函数库, e.g. -agentlib:jdwp=help -agentpath :pathname [=options] 使用给定的路径加载本地代理库。 -classpath classpath -cp classpath 不用说了。 -Dproperty =value 设置一个系统属性。 -d32 -d64 要求程序在32位或64位下跑,未来这个参数可能有变。 -enableassertions [:"..." | : ] -ea [:"..." | : ] 开启断言。 -disableassertions [:"..." | : ] -da [:"..." | : ] 关闭断言。 -enablesystemassertions - esa 启动所有系统类的断言。

-disablesystemassertions -dsa 关闭所有系统类的断言。 -jar 这个也没什么说的。 -javaagent :jarpath [=options] 加载Java 程序语言代理 -verbose:class 输出每个加载的类详细信息。 -verbose:gc 输出GC的详细信息。 -verbose:jni 输出本地方法接口的调用信息。 -version -help -? 不用说了。 -X 显示可用的非标准参数 非标准参数: -Xint 以解释模式运行。JVM不会使用HotSpot的新特性,不会将部分常用代码编译为本地代码,所有代码都以字节码的方式解释运行。你可以理解为使用JDK1.3.1以前的JIT方式运行程序。 -Xbatch 不使用后台编译。 -Xbootclasspath:bootclasspath 使用bootstrap classloader 加载指定路径的class或jar,这是种完全覆盖默认系统类加载的方案,慎用。 -Xbootclasspath/a:bootclasspath 将指定的classpath追加到默认的bootclasspath的后面加载 -Xbootclasspath/p:bootclasspath 将指定的classpath追加到默认的bootclasspath的前面加载 -Xcheck:jni 在调用JNI函数时做额外的检查。这个参数会降低JVM的执行性能。 -Xfuture 执行严格的class文件格式检查,不加这个参数,默认使用JDK1.1./ 版本的class格式检查方法。 -Xnoclassgc 禁用class垃圾收集。 -Xincgc 开启增量垃圾收集机制。默认为关闭。增量垃圾收集能减少因垃圾收集而引起的程序中断,它会在程序运行期间不定期地以并发的方式运行,在运行期间不会引起中断但是会减少分配给程序的cpu资源。 -Xloggc:file GC详情日志。效果如-verbose:gc ,不过这个可以输出到一个文件。除了-verbose:gc包含的信息,还有显示发生的时间。 文件可以是远程的,但是考虑到网络延迟会引起JVM中断,一般建议使用本地文件, -Xms 分配的堆空间初始值: -Xms6291456 -Xms6144k -Xms6m -Xmx 分配的堆空间最大值: -Xmx83886080 -Xmx81920k -Xmx80m -Xprof 在运行程序时给出分析数据。适用于开发环境,不适用于生产环境。 -Xrs 减少JVM的操作系统信号的使用量。 -Xss 线程栈内存。 *非标准-XX参数

有三种-XX参数的形式:

  • Boolean 型的参数 开启如 -XX:+
  • Numeric 型参数 -XX:
  • String 参数 -XX:

以下是-XX参数列表,本来想都翻译过来,但是发现一些技术术语如果硬是翻译可能会导致词不达意,因此大部分描述都保持原文。

参数名和默认值 描述 -XX:-AllowUserSignalHandlers 允许使用用户自定义的信号处理器 (只对应Solaris和Linux) -XX:AltStackSize=16384 修改栈容量 (单位 Kb) (对应Solaris, JDK 5.0以后弃用) -XX:-DisableExplicitGC 禁止手动调用System.gc() -XX:+FailOverToOldVerifier 如果新的类型校验器验证失败使用旧版本的类型校验器 (开始于JDK6.) -XX:+HandlePromotionFailure The youngest generation collection does not require a guarantee of full promotion of all live objects. (Introduced in 1.4.2 update 11) [5.0 and earlier: false.] -XX:+MaxFDLimit 将文件描述符加到最大 (对应Solaris) -XX:PreBlockSpin=10 Spin count variable for use with -XX:+UseSpinning. Controls the maximum spin iterations allowed before entering operating system thread synchronization code. (Introduced in 1.4.2.) -XX:-RelaxAccessControlCheck 放宽类型校验机的准入控制(JDK6) -XX:+ScavengeBeforeFullGC 在full GC之前先做年轻代GC (开始于JDK1.4.1.) -XX:+UseAltSigs Use alternate signals instead of SIGUSR1 and SIGUSR2 for VM internal signals. (Introduced in 1.3.1 update 9, 1.4.1. Relevant to Solaris only.) -XX:+UseBoundThreads Bind user level threads to kernel threads. (Relevant to Solaris only.) -XX:-UseConcMarkSweepGC 使用并发的mark-sweep GC收集年老代 (始于JDK1.4.1) -XX:+UseGCOverheadLimit 使用一种限制VM做GC操作的时间所占比例过高的策略 (始于JDK6.) -XX:+UseLWPSynchronization 使用轻量级进程同步替代线程同步 (始于JDK1.4.0. Solaris相关) -XX:-UseParallelGC 使用并发平行GC(始于JDK1.4.1) -XX:-UseParallelOldGC 使用并发平行GC做 full GC. (始于JDK5.0 update 6.) -XX:-UseSerialGC 使用串行GC (始于JDK5.0.) -XX:-UseSpinning Enable naive spinning on Java monitor before entering operating system thread synchronizaton code. (Relevant to 1.4.2 and 5.0 only.) [1.4.2, multi-processor Windows platforms: true] -XX:+UseTLAB Use thread-local object allocation (Introduced in 1.4.0, known as UseTLE prior to that.) [1.4.2 and earlier, x86 or with -client: false] -XX:+UseSplitVerifier Use the new type checker with StackMapTable attributes. (Introduced in 5.0.)[5.0: false] -XX:+UseThreadPriorities Use native thread priorities. -XX:+UseVMInterruptibleIO Thread interrupt before or with EINTR for I/O operations results in OS_INTRPT. (Introduced in 6. Relevant to Solaris only.)

参数名和默认值 描述 -XX:+AggressiveOpts Turn on point performance compiler optimizations that are expected to be default in upcoming releases. (Introduced in 5.0 update 6.) -XX:CompileThreshold=10000 Number of method invocations/branches before compiling [-client: 1,500] -XX:LargePageSizeInBytes=4m Sets the large page size used for the Java heap. (Introduced in 1.4.0 update 1.) [amd64: 2m.] -XX:MaxHeapFreeRatio=70 Maximum percentage of heap free after GC to avoid shrinking. -XX:MaxNewSize=size Maximum size of new generation (in bytes). Since 1.4, MaxNewSize is computed as a function of NewRatio. [1.3.1 Sparc: 32m; 1.3.1 x86: 2.5m.] -XX:MaxPermSize=64m Size of the Permanent Generation. [5.0 and newer: 64 bit VMs are scaled 30% larger; 1.4 amd64: 96m; 1.3.1 -client: 32m.] -XX:MinHeapFreeRatio=40 Minimum percentage of heap free after GC to avoid expansion. -XX:NewRatio=2 Ratio of new/old generation sizes. [Sparc -client: 8; x86 -server: 8; x86 -client: 12.]-client: 4 (1.3) 8 (1.3.1+), x86: 12] -XX:NewSize=2.125m Default size of new generation (in bytes) [5.0 and newer: 64 bit VMs are scaled 30% larger; x86: 1m; x86, 5.0 and older: 640k] -XX:ReservedCodeCacheSize=32m Reserved code cache size (in bytes) - maximum code cache size. [Solaris 64-bit, amd64, and -server x86: 48m; in 1.5.0_06 and earlier, Solaris 64-bit and and64: 1024m.] -XX:SurvivorRatio=8 Ratio of eden/survivor space size [Solaris amd64: 6; Sparc in 1.3.1: 25; other Solaris platforms in 5.0 and earlier: 32] -XX:TargetSurvivorRatio=50 Desired percentage of survivor space used after scavenge. -XX:ThreadStackSize=512 Thread Stack Size (in Kbytes). (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.] -XX:+UseBiasedLocking Enable biased locking. For more details, see this tuning example . (Introduced in 5.0 update 6.) [5.0: false] -XX:+UseFastAccessorMethods Use optimized versions of GetField. -XX:-UseISM Use Intimate Shared Memory. [Not accepted for non-Solaris platforms.] For details, see Intimate Shared Memory . -XX:+UseLargePages Use large page memory. (Introduced in 5.0 update 5.) For details, see Java Support for Large Memory Pages . -XX:+UseMPSS Use Multiple Page Size Support w/4mb pages for the heap. Do not use with ISM as this replaces the need for ISM. (Introduced in 1.4.0 update 1, Relevant to Solaris 9 and newer.) [1.4.1 and earlier: false] -XX:+StringCache Enables caching of commonly allocated strings. -XX:AllocatePrefetchLines=1 Number of cache lines to load after the last object allocation using prefetch instructions generated in JIT compiled code. Default values are 1 if the last allocated object was an instance and 3 if it was an array. -XX:AllocatePrefetchStyle=1 Generated code style for prefetch instructions. 0 - no prefetch instructions are generate/d/, 1 - execute prefetch instructions after each allocation, 2 - use TLAB allocation watermark pointer to gate when prefetch instructions are executed.

参数名和默认值 描述 -XX:-CITime Prints time spent in JIT Compiler. (Introduced in 1.4.0.) -XX:ErrorFile=./hs_err_pid.log If an error occurs, save the error data to this file. (Introduced in 6.) -XX:-ExtendedDTraceProbes Enable performance-impacting dtrace probes. (Introduced in 6. Relevant to Solaris only.) -XX:HeapDumpPath=./java_pid.hprof Path to directory or filename for heap dump. Manageable . (Introduced in 1.4.2 update 12, 5.0 update 7.) -XX:-HeapDumpOnOutOfMemoryError Dump heap to file when java.lang.OutOfMemoryError is thrown. Manageable . (Introduced in 1.4.2 update 12, 5.0 update 7.) -XX:OnError=";" Run user-defined commands on fatal error. (Introduced in 1.4.2 update 9.) -XX:OnOutOfMemoryError=";

" Run user-defined commands when an OutOfMemoryError is first thrown. (Introduced in 1.4.2 update 12, 6) -XX:-PrintClassHistogram Print a histogram of class instances on Ctrl-Break. Manageable . (Introduced in 1.4.2.) The jmap -histo command provides equivalent functionality. -XX:-PrintConcurrentLocks Print java.util.concurrent locks in Ctrl-Break thread dump. Manageable . (Introduced in 6.) The jstack -l command provides equivalent functionality. -XX:-PrintCommandLineFlags Print flags that appeared on the command line. (Introduced in 5.0.) -XX:-PrintCompilation Print message when a method is compiled. -XX:-PrintGC Print messages at garbage collection. Manageable . -XX:-PrintGCDetails Print more details at garbage collection. Manageable . (Introduced in 1.4.0.) -XX:-PrintGCTimeStamps Print timestamps at garbage collection. Manageable (Introduced in 1.4.0.) -XX:-PrintTenuringDistribution Print tenuring age information. -XX:-TraceClassLoading Trace loading of classes. -XX:-TraceClassLoadingPreorder Trace all classes loaded in order referenced (not loaded). (Introduced in 1.4.2.) -XX:-TraceClassResolution Trace constant pool resolutions. (Introduced in 1.4.2.) -XX:-TraceClassUnloading Trace unloading of classes. -XX:-TraceLoaderConstraints Trace recording of loader constraints. (Introduced in 6.) -XX:+PerfSaveDataToFile Saves jvmstat binary data on exit.

参考资料:

http://java.sun.com/javase/6/docs/technotes/tools/windows/java.html

http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp

Freaker与妖人 | 图解JVM在内存中申请对象及垃圾回收流程

评论

发表评论

您还没有登录,请登录后发表评论(快捷键 Alt+S / Ctrl+Enter)

longdick的博客

longdick

搜索本博客

最近访客 >>更多访客

hqm1988的博客

hqm1988

cesc的博客

cesc shirennostone的博客

shirennostone

etheme的博客

etheme

博客分类

HiYrh.jpg 共 3 张

我的留言簿 >>更多留言

最近加入圈子

存档

最新评论

java中的io系统详解

Posted on

java中的io系统详解

Java 流在处理上分为字符流和字节流。字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。

Java 内用 Unicode 编码存储字符,字符流处理类负责将外部的其他编码的字符流和 java 内 Unicode 字符流之间的转换。而类 InputStreamReader 和 OutputStreamWriter 处理字符流和字节流的转换。字符流(一次可以处理一个缓冲区)一次操作比字节流(一次一个字节)效率高。

( 一 )以字节为导向的 stream------InputStream/OutputStream

InputStream 和 OutputStream 是两个 abstact 类,对于字节为导向的 stream 都扩展这两个鸡肋(基类 ^_^ ) ;

1、 InputStream

42134.gif

1.1

ByteArrayInputStream -- 把内存中的一个缓冲区作为 InputStream 使用 .

construct---

(A)ByteArrayInputStream(byte[]) 创建一个新字节数组输入流( ByteArrayInputStream ),它从指定字节数组中读取数据( 使用 byte 作为其缓冲区数组)

(B)---ByteArrayInputStream(byte[], int, int) 创建一个新字节数组输入流,它从指定字节数组中读取数据。

---mark:: 该字节数组未被复制。

1.2

StringBufferInputStream -- 把一个 String 对象作为 InputStream .

construct---

StringBufferInputStream(String) 据指定串创建一个读取数据的输入流串。

注释:不推荐使用 StringBufferInputStream 方法。 此类不能将字符正确的转换为字节。

同 JDK 1.1 版中的类似,从一个串创建一个流的最佳方法是采用 StringReader 类。

1.3

FileInputStream -- 把一个文件作为 InputStream ,实现对文件的读取操作

construct---

(A)FileInputStream(File name) 创建一个输入文件流,从指定的 File 对象读取数据。

(B)FileInputStream(FileDescriptor) 创建一个输入文件流,从指定的文件描述器读取数据。

(C)-FileInputStream(String name) 创建一个输入文件流,从指定名称的文件读取数据。

method ---- read() 从当前输入流中读取一字节数据。

read(byte[]) 将当前输入流中 b.length 个字节数据读到一个字节数组中。

read(byte[], int, int) 将输入流中 len 个字节数据读入一个字节数组中。

1.4

PipedInputStream :实现了 pipe 的概念,主要在线程中使用 . 管道输入流是指一个通讯管道的接收端。

一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯。

construct---

PipedInputStream() 创建一个管道输入流,它还未与一个管道输出流连接。

PipedInputStream(PipedOutputStream) 创建一个管道输入流 , 它已连接到一个管道输出流。

1.5

SequenceInputStream :把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来,

并且使它们像单个输入流一样出现。每个输入流依次被读取,直到到达该流的末尾。

然后“序列输入流”类关闭这个流并自动地切换到下一个输入流。

construct---

SequenceInputStream(Enumeration) 创建一个新的序列输入流,并用指定的输入流的枚举值初始化它。

SequenceInputStream(InputStream, InputStream) 创建一个新的序列输入流,初始化为首先 读输入流 s1, 然后读输入流 s2 。

2、 OutputSteam

42135.gif 2.1

ByteArrayOutputStream : 把信息存入内存中的一个缓冲区中 . 该类实现一个以字节数组形式写入数据的输出流。

当数据写入缓冲区时,它自动扩大。用 toByteArray() 和 toString() 能检索数据。

constructor

(A)--- ByteArrayOutputStream() 创建一个新的字节数组输出流。

(B)--- ByteArrayOutputStream() 创建一个新的字节数组输出流。

(C)--- ByteArrayOutputStream(int) 创建一个新的字节数组输出流,并带有指定大小字节的缓冲区容量。

toString(String) 根据指定字符编码将缓冲区内容转换为字符串,并将字节转换为字符。

write(byte[], int, int) 将指定字节数组中从偏移量 off 开始的 len 个字节写入该字节数组输出流。

write(int) 将指定字节写入该字节数组输出流。

writeTo(OutputStream) 用 out.write(buf, 0, count) 调用输出流的写方法将该字节数组输出流的全部内容写入指定的输出流参数。

2.2

FileOutputStream: 文件输出流是向 File 或 FileDescriptor 输出数据的一个输出流。

constructor

(A)FileOutputStream(File name) 创建一个文件输出流,向指定的 File 对象输出数据。

(B)FileOutputStream(FileDescriptor) 创建一个文件输出流,向指定的文件描述器输出数据。

(C)FileOutputStream(String name) 创建一个文件输出流,向指定名称的文件输出数据。

(D)FileOutputStream(String, boolean) 用指定系统的文件名,创建一个输出文件。

2.3

PipedOutputStream: 管道输出流是指一个通讯管道的发送端。 一个线程通过管道输出流发送数据,

而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯。

constructor

(A)PipedOutputStream() 创建一个管道输出流,它还未与一个管道输入流连接。

(B)PipedOutputStream(PipedInputStream) 创建一个管道输出流,它已连接到一个管道输入流。

( 二 )以字符为导向的 stream Reader/Writer

以 Unicode 字符为导向的 stream ,表示以 Unicode 字符为单位从 stream 中读取或往 stream 中写入信息。

Reader/Writer 为 abstact 类

以 Unicode 字符为导向的 stream 包括下面几种类型:

  1. Reader

42136.gif

1.1

CharArrayReader :与 ByteArrayInputStream 对应此类实现一个可用作字符输入流的字符缓冲区

constructor

CharArrayReader(char[]) 用指定字符数组创建一个 CharArrayReader 。

CharArrayReader(char[], int, int) 用指定字符数组创建一个 CharArrayReader

1.2

StringReader : 与 StringBufferInputStream 对应其源为一个字符串的字符流。

StringReader(String) 创建一新的串读取者。

1.3

FileReader : 与 FileInputStream 对应

1.4

PipedReader :与 PipedInputStream 对应

  1. Writer

    42137.gif

2.1 CharArrayWrite : 与 ByteArrayOutputStream 对应

2.2 StringWrite :无与之对应的以字节为导向的 stream

2.3 FileWrite : 与 FileOutputStream 对应

2.4 PipedWrite :与 PipedOutputStream 对应

3、两种不同导向的 stream 之间的转换

3.1

InputStreamReader 和 OutputStreamReader :

把一个以字节为导向的 stream 转换成一个以字符为导向的 stream 。

InputStreamReader 类是从字节流到字符流的桥梁:它读入字节,并根据指定的编码方式,将之转换为字符流。

使用的编码方式可能由名称指定,或平台可接受的缺省编码方式。

InputStreamReader 的 read() 方法之一的每次调用,可能促使从基本字节输入流中读取一个或多个字节。

为了达到更高效率,考虑用 BufferedReader 封装 InputStreamReader ,

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

例如: // 实现从键盘输入一个整数

  1. String s = null;
  2. InputStreamReader re = new InputStreamReader(System.in);
  3.        BufferedReader br = new BufferedReader(re);  
    
  4.        try {  
    
  5.        s = br.readLine();  
    
  6.        System.out.println("s= " + Integer.parseInt(s));  
    
  7.        br.close();  
    
  8.        }  
    
  9.        catch (IOException e)  
    
  10.        {  
    
  11.        e.printStackTrace();  
    
  12.        }  
    
  13.        catch (NumberFormatException e)// 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。  
    
  14.        {  
    
  15.        System.out.println(" 输入的不是数字 ");  
    
  16.        }  
    

InputStreamReader(InputStream) 用缺省的字符编码方式,创建一个 InputStreamReader 。

InputStreamReader(InputStream, String) 用已命名的字符编码方式,创建一个 InputStreamReader 。

OutputStreamWriter 将多个字符写入到一个输出流,根据指定的字符编码将多个字符转换为字节。

每个 OutputStreamWriter 合并它自己的 CharToByteConverter, 因而是从字符流到字节流的桥梁。

(三)Java IO 的一般使用原则 :

一、按数据来源(去向)分类:

1 、是文件: FileInputStream, FileOutputStream, ( 字节流 )FileReader, FileWriter( 字符 )

2 、是 byte[] : ByteArrayInputStream, ByteArrayOutputStream( 字节流 )

3 、是 Char[]: CharArrayReader, CharArrayWriter( 字符流 )

4 、是 String: StringBufferInputStream, StringBufferOuputStream ( 字节流 )StringReader, StringWriter( 字符流 )

5 、网络数据流: InputStream, OutputStream,( 字节流 ) Reader, Writer( 字符流 )

二、按是否格式化输出分:

1 、要格式化输出: PrintStream, PrintWriter

三、按是否要缓冲分:

1 、要缓冲: BufferedInputStream, BufferedOutputStream,( 字节流 ) BufferedReader, BufferedWriter( 字符流 )

四、按数据格式分:

1 、二进制格式(只要不能确定是纯文本的) : InputStream, OutputStream 及其所有带 Stream 结束的子类

2 、纯文本格式(含纯英文与汉字或其他编码方式); Reader, Writer 及其所有带 Reader, Writer 的子类

五、按输入输出分:

1 、输入: Reader, InputStream 类型的子类

2 、输出: Writer, OutputStream 类型的子类

六、特殊需要:

1 、从 Stream 到 Reader,Writer 的转换类: InputStreamReader, OutputStreamWriter

2 、对象输入输出: ObjectInputStream, ObjectOutputStream

3 、进程间通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter

4 、合并输入: SequenceInputStream

5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader

决定使用哪个类以及它的构造进程的一般准则如下(不考虑特殊需要):

首先,考虑最原始的数据格式是什么: 原则四

第二,是输入还是输出:原则五

第三,是否需要转换流:原则六第 1 点

第四,数据来源(去向)是什么:原则一

第五,是否要缓冲:原则三 (特别注明:一定要注意的是 readLine() 是否有定义,有什么比 read, write 更特殊的输入或输出方法)

第六,是否要格式化输出:原则二