软件构造实验五

  1. 实验目标概述

本次实验通过对 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

⚫ 基于设计模式的代码调优

⚫ 代码重构

  1. 实验环境配置

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. 实验过程

请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。

    1. Static Program Analysis
      1. 人工代码走查(walkthrough)

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

      1. 使用CheckStyle和SpotBugs进行静态代码分析
  1. 类命名不符合规范,person改为Person,使用重构工具,首字母全部改为大写

软件构造实验五

  1. 包命名全部小写,在lab3中要求的包名有些不符合规范未进行修改

软件构造实验五

  1. Javadoc的第一句缺少一个结束时期。第一行加上”.”

软件构造实验五

  1. 变量名,小写字母开头,小写字母要大于两个,rename重构
  2. 方法名开头小写,不能连续两个大写字母,rename重构
  3. *类在自己源文件中,新建class

软件构造实验五

  1. 导入包按的字典顺序

软件构造实验五

  1. 分开定义

软件构造实验五

  1. 字符过多,换行

软件构造实验五

  1. If的{},自动生成的equal也要改…………

软件构造实验五

  1. 等等等
    1. Java I/O Optimization
      1. 多种I/O实现方式

实现了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久,重构代码数据存储方式,处理方式,更改二重循环等等等等,才使代码可以几秒内建成系统…

      1. 多种I/O实现方式的效率对比分析

使用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需要付出额外的时间。所以其对文件的复制可以远超传统方式

软件构造实验五  

软件构造实验五  

    1. Java Memory Management and Garbage Collection (GC)
      1. 使用-verbose:gc参数

使用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 GC0.26,0.450.19.

在进行Full GC时会同时使用PSYoungGen、ParOldGen和Metaspace进行垃圾回收,同时释放新生代、年老代和元空间的内容。

GC或Full GC后面的括号内容就是本次GC产生的原因,可以看出,所有的Minor GC产生的原因都是Allocation Failure(新生代中没有足够的空间),而Full GC的原因是Ergonomics(HotSpot自动选择和调优引发的FullGC)。

由控制台可以看出,由于需要产生大量新的对象,导致内存的新生代区域不足,发生了频繁的Minor GC,尤其在读取文件时最为明显。

      1. jstat命令行工具的-gc-gcutil参数

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”区被清空。

      1. 使用jmap -heap命令行工具

报错版本冲突改了一晚上,发现不知道什么原因竟然单独装了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[][]可以看出其中有大量的charstring和存储数据的hashmapdoubleObject

      1. 使用jmap -clstats命令行工具

这个命令真的慢,开始等了两个小时………………………关了可惜,等着还不知道能不能出来结果,最终卡死,白等了两个小时,重新开始等了几分钟出来的.

软件构造实验五

其*有4个类加载器,一共加载了994个类,占用字节1896174个,其中1个已经dead,剩余3个alive。

bootstrap classloader,用于加载核心类库,共加载了926个类,且为alive。

剩下一个ExtClassLoader,一个 AppClassLoader,一个 RBClassLoader

      1. 使用jmap -permstat命令行工具

Jdk8不支持,使用的jmap -clstats

      1. 使用JMC/JFRjconsoleVisualVM工具

软件构造实验五

其中有两个峰值,每个有分为两个小的峰值,第一个的首个小峰值为读入文件,第二个小峰值为构建系统,第二个前一个小峰值为根据关系构建将要写入的字符串,第二个为写入文件

堆在读入文件呵写入文件时增大两次,由于在写入文件时我复制了一部分内容,写入后又删去了所以已用堆有一个峰值之后又恢复

装载的类为1879个,共享和已卸载均为0

活动9,守护进程8,时时峰值10,已启动总数10

      1. 分析垃圾回收过程

在程序开始时由于读入大量数据新生代不足所以发生了多次GC,之后由于建立系统,创建了大量对象存储在不同结构中,使其再次发生多次GC,写文件时由于重新存储了大量的string类型的list,使得新时代再次不足,再次发生GC,Full GC都是由于系统调优产生的

当读入大量内容和创建大量对象时会造成伊甸园区或者“From”区占用过高时,在新生代进行垃圾回收,对象会不停的在两个幸存区间复制,从而引发多次GC

      1. 配置JVM参数并发现优化的参数配置

更改后参数为

-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也不再发生

    1. Dynamic Program Profiling
      1. 使用JMC或VisualVM进行CPU Profiling

使用Channel/buffer读取SocialNetworkCircle.txt文件构建系统.进行添加新成员,删除成员,日志查询,写文件操作

软件构造实验五

可以看出大部分时间花在了TCPTRansport$ConnectionHandler.run上,其次在socialMethod上(等待输入进行操作),然后是getLog,因等待输入日志的过滤条件然后是比较耗时的写操作,edgeToTrack为对10w人的广搜所以耗费了些时间,删除操作是也要进行搜索所以较慢,其他的都是执行较快的方法

      1. 使用VisualVM进行Memory profiling

在3.4.1的条件下直接进行内存分析,如图

软件构造实验五

byte[]和char[]是因为Java里面的String操作最终都会转化为char[]的,凡是IOStream的操作都会转化为byte[],其次为Object,创建系统时生成了大量的类,几十万个,读入文件和写入文件时以string类型存储了很多数据,存储关系,等等使用的大量的map,set等,存储编号等还使用了大量的int

    1. Memory Dump Analysis and Performance Optimization
      1. 内存导出

使用Channel/buffer读取SocialNetworkCircle.txt文件执行耗费内存的构建系统写文件操作,使用visualVM下的堆 heap直接导出hrpof文件

      1. 使用MAT分析内存导出文件

软件构造实验五

视图

含义

histogram

列举内存中对象存在的个数和大小

Dominator tree

该视图会以占用总内存的百分比来列举所有实例对象,注意这个地方是对象而不是类了,这个视图是用来发现大内存对象的

Top Consumers

视图展示了当前内存的占用热点

leak suspects

视图展示了可能的内存泄漏位置以及原因

Unreachable指的是可以被垃圾回收器回收的对象,但是由于没有GC发生,所以没有释放,这时抓的内存使用中的Unreachable就是这些对象。

leak suspects视图展示了可能的内存泄漏位置以及原因

Dominator Tree:

通过“引用树”的方式来展现内存的使用情况的,通俗点来说,它是站在对象的角度来观察内存的使用情况的,主要看是否存在异常的大内存对象

  1. Histogram

 

软件构造实验五

列名

含义

Object

该类在内存当中的对象个数

Shallow Heap

对象自身所占用的内存大小,不包括它所引用的对象的内存大小

Retained Heap

该对象被垃圾回收器回收之后,会释放的内存大小

byte[]和char[]是因为Java里面的String操作最终都会转化为char[]的,凡是IOStream的操作都会转化为byte[],string存储了很多内容其次是存储结构等使用到的大量的hashmap.由于存储了大量的人,自建的person类也比较靠前,还有常用的double,int等

  1. Dominator Tree:

通过“引用树”的方式来展现内存的使用情况的,通俗点来说,它是站在对象的角度来观察内存的使用情况的,主要看是否存在异常的大内存对象

软件构造实验五

Shallow heap

表示对象自身占用的内存大小,不包括它引用的对象

Retained heap

表示当前对象大小+当前对象可直接或间接引用到的对象的大小总和

作为当前主要运行的系统socialNetworkCircle占据了99%以上的空间,其中包含系统中的所有信息

  1. Top Consumers

软件构造实验五软件构造实验五软件构造实验五

可以看出,占用内存最多的仍然是socialNetworkCircle,其作为当前主要运行的系统

  1. Leak Suspects

软件构造实验五

与前面看到的一致,认为是socialNetworkCircle占用了99.13%,可能造成泄露

      1. 发现热点/瓶颈并改进、改进前后的性能对比分析

软件构造实验五

可以看出其中占用最多的为hashmap和arraylist,对他们show objects by class -> by outgoing referneces,可以查看内部引用情况

软件构造实验五

Hashmap中有两个占用极大,并且一个是另一个的二倍,在程序中使用了hashmap存储社交关系,而在写文件时复制了一份,并且在写入后只剩下单向关系,正好是原来的一半,猜测其为占用内存的原因

软件构造实验五

在文件读入与写入时都是以arraylist传递的,而这些数据在创建系统后及写入文件后就没有用了,却占用了大量的空间.

于是在使用这些数据之后对其clean,并执行system.gc手动回收垃圾

软件构造实验五

执行后内存由192.4减少到131.6

      1. MAT内使用OQL查询内存导出

1.OQL无法查询接口的信息,所以使用实现了接口并作为所有系统父类的ConcreteCircularOrbit进行查询.只有当前生成的社交系统类

软件构造实验五

2.查找长度大于20的字符串

软件构造实验五

查找大于1024的对象

软件构造实验五

查找物体类,当前为社交系统,所以都为Person类

软件构造实验五

      1. 观察jstack/jcmd导出程序运行时的调用栈

如下为使用Channel/buffer读取SocialNetworkCircle.txt文件执行耗费内存的构建系统写文件操作后等待输入的调用栈

软件构造实验五

等待输入要删除的人:

软件构造实验五

等待输入选项

软件构造实验五

其中86为所在系统的代码位置,291和275为等待输入删除人姓名和等待输入操作代码的位置

      1. 使用设计模式进行代码性能优化
  1. 优化二重循环,名字不能重复将二重循环暴力求解改为list存储转set比较和list的大小.
  2. 社交网络的存储结构,list嵌套的邻接矩阵存储会造成heap不足,改为map的邻接表存储,最难改的一点,数据结构变了,之前的很多方法都要改,基本将socialNetwordkCirlcle重写了一遍
  3. arraylist的indexof暴力求解,循环时造成二重循环,改为map存储数据和编号
  4. 将设计大量的list转为hashset与hashmap提高算法效率
  5. Canonicalization思想:string作为基本类型使用时equals改用==,一些类的比较使用其在存储结构中的编号比较
  6. 使用Prototype模式,使物体支持clone
  7. 使用Flyweight设计模式生成电子,在相同轨道的电子只生成一个

软件构造实验五

优化前系统heap溢出,需几小时才能建立系统,优化后系统可以在5秒内建出系统,速度提高还是非常明显的