本次实验通过对 Lab4 的代码进行静态和动态分析,发现代码中存在的不符 合代码规范的地方、具有潜在 bug 的地方、性能存在缺陷的地方(执行时间热点、 内存消耗大的语句、函数、类),进而使用第 4、7、8 章所学的知识对这些问题 加以改进,掌握代码持续优化的方法,让代码既“看起来很美”,又“运行起来 很美”。 具体训练的技术包括:
⚫ 静态代码分析(CheckStyle 和 SpotBugs)
⚫ 动态代码分析(Java 命令行工具 jstat、jmap、jcmd、VisualVM、JMC、 JConsole 等)
⚫ JVM 内存管理与垃圾回收(GC)的优化配置
⚫ 运行时内存导出(memory dump)及其分析(Java 命令行工具 jhat、MAT)
⚫ 运行时调用栈及其分析(Java 命令行工具 jstack);
⚫ 高性能 I/O
⚫ 基于设计模式的代码调优
⚫ 代码重构
VisualVM安装较麻烦一些,首先进入官网下载eclipse相应插件,解压到根目录,在eclipse下help下面的安装新的软件,选择解压好的文件,一路默认即可。安装完成后要在配置: 在window的preferences中进行VisualVM的配置,需要配置它的启动器(jdk、bin目录下面的jvisualvm.exe)还有jdk目录(注意是jdk不是jre)点击apply,ok即可完成安装配置
MAT和CheckStyle直接在marketPlace搜索傻瓜式安装即可,(注意Checkstyle的C大写,s小写要不然搜不到。。。。。。)
SpotBugs在lab4已经安装
在这里给出你的GitHub Lab5仓库的URL地址(Lab5-学号)。
https://github.com/ComputerScienceHIT/Lab5-1170300527.git
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
1.Switch和case,自动format时没有缩进,Google要求有缩进,手动调整
2.要求每个函数都有doc,构造方法等之前没有添加,此次添加
3.缩进要求两个,以前都是4个,更改java->Code Style->format中设置
4.Google中不能使用tab要使用空格,更改Text Editor设置,format设置
勾选上insert spaces tabs
选择space only,tab size设为2
实现了Reader/Writer ,Buffer/Channel, java.nio.file.Files三种I/O方式,
Reader/Writer使用的bufferreader和bufferwrite,较为简单,直接调用其中的read和write方法即可
Buffer/Channel读取文件时使用MappedByteBuffer RandomAccessFile的内存映射,读入速度较其他方法快了很多倍,但读入的并非string,将其转化为string后就慢了很多,基本和bufferread差不多,写文件采用的bytebuffer
java.nio.file.Files方法也较为简单,直接调用readAllline和write即可将数据直接全部写入
使用strategy设计模式,创建接口readandwrite,包含read和write接口,
创建三个类以三种I/O策略实现接口,ReadWrite,Channel,NioFile分别实现Reader/Writer ,Buffer/Channel, java.nio.file.Files,这里只进行读取与写入,未进行处理,处理仍然交给三个系统分别处理,否则三个系统的匹配,写入格式都不同,将需要创建九个类.
在三个系统中创建策略对象,这样可以通过传入的策略以不同的I/O处理文件
从重构到重写啊….读入之后内存直接炸掉,读写文本简单,处理就炸掉…..改了n久,重构代码数据存储方式,处理方式,更改二重循环等等等等,才使代码可以几秒内建成系统…
使用System.currentTimeMillis()方法获取开始和结束时间,计算时间差
表格方式对比不同I/O的性能。
|
|
SocialNetworkCircle.txt |
StellarSystem.txt |
Read/write |
读文件 |
618ms |
192ms |
写文件 |
426ms |
242ms |
|
Channel/Buffer |
读文件 |
500ms |
160ms |
写文件 |
2365ms |
917ms |
|
Java.nio.file.Files |
读文件 |
764ms |
144ms |
写文件 |
175ms |
116ms |
Channel/buffer读入时采用了内存映射,实际读入速度可以更快,但其读入的是二进制,要将其转化为string需要付出额外的时间。所以其对文件的复制可以远超传统方式
使用Channel/buffer读取SocialNetworkCircle.txt文件构建系统.,进行添加新成员,计算熵,日志查询,写入文件操作
日志中的GC若无修饰则为Minor GC
Minor GC即新生代垃圾回收,Full GC针对整个堆进行垃圾回收
其中读入文件时发生18次Minor GC,3次Full GC
写入文件时发生,发生四次Minor GC
Minor GC的时间从0.1到0.8不等, Full GC为0.26,0.45和0.19.
在进行Full GC时会同时使用PSYoungGen、ParOldGen和Metaspace进行垃圾回收,同时释放新生代、年老代和元空间的内容。
GC或Full GC后面的括号内容就是本次GC产生的原因,可以看出,所有的Minor GC产生的原因都是Allocation Failure(新生代中没有足够的空间),而Full GC的原因是Ergonomics(HotSpot自动选择和调优引发的FullGC)。
由控制台可以看出,由于需要产生大量新的对象,导致内存的新生代区域不足,发生了频繁的Minor GC,尤其在读取文件时最为明显。
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
首先使用命令行工具jps查看vmid
然后使用jstat 运行前
运行后
其中
参数 |
描述 |
S0 |
年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 |
S0C |
年轻代中第一个survivor(幸存区)的容量 (字节) |
S0U |
年轻代中第一个survivor(幸存区)目前已使用空间 (字节) |
S1 |
年轻代中第二个survivor(幸存区)已使用的占当前容量百分比 |
S1C |
年轻代中第二个survivor(幸存区)的容量 (字节) |
S1U |
年轻代中第二个survivor(幸存区)目前已使用空间 (字节) |
E |
年轻代中Eden已使用的占当前容量百分比 |
EC |
年轻代中Eden(伊甸园)的容量 (字节) |
EU |
年轻代中Eden(伊甸园)目前已使用空间 (字节) |
O |
old代已使用的占当前容量百分比 |
OC |
Old代的容量 (字节) |
OU |
Old代目前已使用空间 (字节) |
M |
元空间(MetaspaceSize)已使用的占当前容量百分比 |
CCS |
压缩使用比例 |
YGC |
年轻代垃圾回收次数 |
YGCT |
新生代垃圾回收耗时 |
FGC |
老年代垃圾回收次数 |
FGCT |
老年代垃圾回收消耗时间 |
GCT |
垃圾回收消耗总时间 |
PC |
Perm(持久代)的容量 (字节) |
PU |
Perm(持久代)目前已使用空间 (字节) |
新生代的大小就是伊甸园区+两个幸存区的大小。可以看出两个幸存区的占用不停的交换,每一次交换都是由于一次Minor GC。而在一次Full GC后,两个区域的占用都归为0。
新生代垃圾回收的机制为:最初,所有的对象都在伊甸园区和“From”区(某一个幸存区),垃圾回收时会将伊甸园区的所有存活的对象复制到“To”区(另一个幸存区),而在“From”区,年龄达到一定的阈值的对象会被复制到年老区,没有达到的会被复制到“To”区。完成后“From”和“To”的关系互换。此时原“From”区被清空。
报错版本冲突改了一晚上,发现不知道什么原因竟然单独装了jre,eclipse还指向了那个非jdk自带的jre,重新更改eclipse设置后成功运行,用了我一晚上,哭了
此时为读入文件创建系统后,伊甸园区总大小为205M使用79.9%。From Space 90M使用率为0,To Space大小为131.使用率都是0,这是因为最后发生了Full GC,使其都被清空了,PS old generation使用61.7%,
使用jmap -histo vmid
其中[C is a char[] [S is a short[] [I is a int[] [B is a byte[] [[I is a int[][]可以看出其中有大量的char和string和存储数据的hashmap,double和Object。
这个命令真的慢,开始等了两个小时………………………关了可惜,等着还不知道能不能出来结果,最终卡死,白等了两个小时,重新开始等了几分钟出来的.
其中共有4个类加载器,一共加载了994个类,占用字节1896174个,其中1个已经dead,剩余3个alive。
bootstrap classloader,用于加载核心类库,共加载了926个类,且为alive。
剩下一个ExtClassLoader,一个 AppClassLoader,一个 RBClassLoader
Jdk8不支持,使用的jmap -clstats
其中有两个峰值,每个有分为两个小的峰值,第一个的首个小峰值为读入文件,第二个小峰值为构建系统,第二个前一个小峰值为根据关系构建将要写入的字符串,第二个为写入文件
堆在读入文件呵写入文件时增大两次,由于在写入文件时我复制了一部分内容,写入后又删去了所以已用堆有一个峰值之后又恢复
装载的类为1879个,共享和已卸载均为0
活动9,守护进程8,时时峰值10,已启动总数10
在程序开始时由于读入大量数据新生代不足所以发生了多次GC,之后由于建立系统,创建了大量对象存储在不同结构中,使其再次发生多次GC,写文件时由于重新存储了大量的string类型的list,使得新时代再次不足,再次发生GC,Full GC都是由于系统调优产生的
当读入大量内容和创建大量对象时会造成伊甸园区或者“From”区占用过高时,在新生代进行垃圾回收,对象会不停的在两个幸存区间复制,从而引发多次GC
更改后参数为
-Xms3072M -Xmx3072M -Xss256k -XX:+UseParallelOldGC -XX:NewSize=2048M
-XX:MaxNewSize=2048M
其中,将堆大小设置为3072M,每个线程堆栈设为256k, 使用并行的年老代垃圾回收机制,提升Full GC的速度,将新生带区域设为2048M,减少GC次数
使用Channel/buffer读取SocialNetworkCircle.txt文件构建系统.,进行添加新成员,计算熵,日志查询,写入文件操作
只在构建系统时发生了一次GC,时间为0.77s
更改参数后GC次数明显减少,Full GC也不再发生
使用Channel/buffer读取SocialNetworkCircle.txt文件构建系统.进行添加新成员,删除成员,日志查询,写文件操作
可以看出大部分时间花在了TCPTRansport$ConnectionHandler.run上,其次在socialMethod上(等待输入进行操作),然后是getLog,因等待输入日志的过滤条件然后是比较耗时的写操作,edgeToTrack为对10w人的广搜所以耗费了些时间,删除操作是也要进行搜索所以较慢,其他的都是执行较快的方法
在3.4.1的条件下直接进行内存分析,如图
byte[]和char[]是因为Java里面的String操作最终都会转化为char[]的,凡是IOStream的操作都会转化为byte[],其次为Object,创建系统时生成了大量的类,几十万个,读入文件和写入文件时以string类型存储了很多数据,存储关系,等等使用的大量的map,set等,存储编号等还使用了大量的int
使用Channel/buffer读取SocialNetworkCircle.txt文件执行耗费内存的构建系统写文件操作,使用visualVM下的堆 heap直接导出hrpof文件
视图 |
含义 |
histogram |
列举内存中对象存在的个数和大小 |
Dominator tree |
该视图会以占用总内存的百分比来列举所有实例对象,注意这个地方是对象而不是类了,这个视图是用来发现大内存对象的 |
Top Consumers |
视图展示了当前内存的占用热点 |
leak suspects |
视图展示了可能的内存泄漏位置以及原因 |
Unreachable指的是可以被垃圾回收器回收的对象,但是由于没有GC发生,所以没有释放,这时抓的内存使用中的Unreachable就是这些对象。
leak suspects视图展示了可能的内存泄漏位置以及原因
Dominator Tree:
通过“引用树”的方式来展现内存的使用情况的,通俗点来说,它是站在对象的角度来观察内存的使用情况的,主要看是否存在异常的大内存对象
列名 |
含义 |
Object |
该类在内存当中的对象个数 |
Shallow Heap |
对象自身所占用的内存大小,不包括它所引用的对象的内存大小 |
Retained Heap |
该对象被垃圾回收器回收之后,会释放的内存大小 |
byte[]和char[]是因为Java里面的String操作最终都会转化为char[]的,凡是IOStream的操作都会转化为byte[],string存储了很多内容其次是存储结构等使用到的大量的hashmap.由于存储了大量的人,自建的person类也比较靠前,还有常用的double,int等
通过“引用树”的方式来展现内存的使用情况的,通俗点来说,它是站在对象的角度来观察内存的使用情况的,主要看是否存在异常的大内存对象
Shallow heap |
表示对象自身占用的内存大小,不包括它引用的对象 |
Retained heap |
表示当前对象大小+当前对象可直接或间接引用到的对象的大小总和 |
作为当前主要运行的系统socialNetworkCircle占据了99%以上的空间,其中包含系统中的所有信息
可以看出,占用内存最多的仍然是socialNetworkCircle,其作为当前主要运行的系统
与前面看到的一致,认为是socialNetworkCircle占用了99.13%,可能造成泄露
可以看出其中占用最多的为hashmap和arraylist,对他们show objects by class -> by outgoing referneces,可以查看内部引用情况
Hashmap中有两个占用极大,并且一个是另一个的二倍,在程序中使用了hashmap存储社交关系,而在写文件时复制了一份,并且在写入后只剩下单向关系,正好是原来的一半,猜测其为占用内存的原因
在文件读入与写入时都是以arraylist传递的,而这些数据在创建系统后及写入文件后就没有用了,却占用了大量的空间.
于是在使用这些数据之后对其clean,并执行system.gc手动回收垃圾
执行后内存由192.4减少到131.6
1.OQL无法查询接口的信息,所以使用实现了接口并作为所有系统父类的ConcreteCircularOrbit进行查询.只有当前生成的社交系统类
2.查找长度大于20的字符串
查找大于1024的对象
查找物体类,当前为社交系统,所以都为Person类
如下为使用Channel/buffer读取SocialNetworkCircle.txt文件执行耗费内存的构建系统写文件操作后等待输入的调用栈
等待输入要删除的人:
等待输入选项
其中86为所在系统的代码位置,291和275为等待输入删除人姓名和等待输入操作代码的位置
优化前系统heap溢出,需几小时才能建立系统,优化后系统可以在5秒内建出系统,速度提高还是非常明显的