软件构造实验五

  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秒内建出系统,速度提高还是非常明显的