软件构造实验五报告

 

目录

1 实验目标概述···· 1

2 实验环境配置···· 1

3 实验过程···· 1

3.1 Static Program Analysis· 1

3.1.1 人工代码走查(walkthrough)···· 1

3.1.2 使用CheckStyle和SpotBugs进行静态代码分析··· 1

3.2 Java I/O Optimization· 1

3.2.1 多种I/O实现方式···· 1

3.2.2 多种I/O实现方式的效率对比分析··· 2

3.3 Java Memory Management and Garbage Collection (GC) 3

3.3.1 使用-verbose:gc参数···· 3

3.3.2 用jstat命令行工具的-gc和-gcutil参数···· 3

3.3.3 使用jmap -heap命令行工具···· 3

3.3.4 使用jmap -clstats命令行工具···· 3

3.3.5 使用jmap -permstat命令行工具···· 3

3.3.6 使用JMC/JFR、jconsole或VisualVM工具···· 3

3.3.7 分析垃圾回收过程···· 3

3.3.8 配置JVM参数并发现优化的参数配置···· 3

3.4 Dynamic Program Profiling· 3

3.4.1 使用JMC或VisualVM进行CPU Profiling· 3

3.4.2 使用VisualVM进行Memory profiling· 3

3.5 Memory Dump Analysis and Performance Optimization· 3

3.5.1 内存导出···· 3

3.5.2 使用MAT分析内存导出文件··· 3

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

3.5.4 在MAT内使用OQL查询内存导出···· 4

3.5.5 观察jstack/jcmd导出程序运行时的调用栈··· 4

3.5.6 使用设计模式进行代码性能优化···· 4

4 实验进度记录···· 4

5 实验过程中遇到的困难与解决途径···· 4

6 实验过程中收获的经验、教训、感想··· 5

6.1 实验过程中收获的经验和教训··· 5

6.2 针对以下方面的感受···· 5

 

 

 

 

  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. 实验环境配置

除此之外,本次实验需要你在 Eclipse IDE 中配置并运行 VisualVM 和 Memory

Analyzer (MAT) ( 用 于 Java 程 序 动 态 性 能 分 析 的 工 具 )。 请 分 别 访 问

https://visualvm.github.io 和 http://www.eclipse.org/mat/,获取更多信息。

还要配置并运行 CheckStyle 和 SpotBugs 工具(代码静态分析工具),请访问

http://checkstyle.sourceforge.net 和 https://spotbugs.github.io/获取更多帮助信息。

在eclipse中装载CheckStyle工具,SpotBugs之前已经下好了,CheckStyle工具在市场里没有,去百度中下载CheckStyle的安装包,到eclipse中安装新软件装好CheckStyle。

在这里给出你的GitHub Lab5仓库的URL地址(Lab5-学号)。

https://github.com/ComputerScienceHIT/Lab5-1170300724.git

  1. 实验过程

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

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

1.包名不能包含大写字母。

       将包名进行更改,全都改为小写字母

2.import的写法是有顺序的

  先写自己导入的

3.运算符号前后要有空格

  比如=,+,-,!=

  利用替换进行修改,将=替换为 = ,再将=  =替换为==

  再将! =替换为!=

4.不能有制表符\t

  将所有的\t替换成空格

5.else和if的语句要用大括号,即使只有一行

  按照要求进行修改

6.缩进有要求

  在网上下载Google规范的xml,在eclipse中导入这个规范,然后重新格式化。

7.方法要有javadoc

  对部分方法前面加了/**   */来写javadoc(太多了只写了70%)

  9

8.每行的长度不能超过100字符

  过长的改成两行

  

9.类名不能出现两个连续的大写字母

  进行了重构修改,

10.居然变量名也不可以有两个连续的大写字母

  进行了查找替换修改,但是有的会涉及到重名问题,没有修改。

      1. 使用CheckStyle和SpotBugs进行静态代码分析

1.javadoc第一行必须要有一个结束

  在第一行加一个句号

2.import,方法声明的前面必须有一行空行

  按照要求修改了代码

3. 数组声明的时候,括号要放在前面

4.一行只能声明一个变量,并且前面必须有空行

  进行了修改

5.还有形如这种问题

虽然理解了问题在哪里,但是该位置会导致结构出错,以后写代码时会注意,这次没修改

 

3.1对原来的lab4进行了修改,删除了debug text 这些不需要的东西,但是3.2不是在3.1的基础上改的,3.2还是在lab4改的,所以3.2没有遵循Google规范,以后会注意的。

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

由于没有电子的,所以只做了trackgame和socialnetwork。

首先在原本的读入中加入了写入文件的功能,

      File file2 = new File("txt/Trackgamestorage.txt");

      if (!file2.exists()) {

        file2.createNewFile();

      }

      FileWriter fw = new FileWriter(file2.getAbsoluteFile());

      BufferedWriter bw = new BufferedWriter(fw);

      bw.write(content);

close在后面

 

然后创造其他的读写文件的方法,在原本类中加入新的方法creat2creat3

Creat2读文件利用了Stream流,写文件也用了Stream

      InputStream is = new FileInputStream(Filename);

      OutputStream fos = new FileOutputStream("txt/Trackgamestorage.txt");

      byte[] b = new byte[1024];

      int len = 0;

      StringBuffer sb = new StringBuffer("");

 

      try {

        while ((len = is.read(b)) > 0) {

          sb.append(new String(b, 0, len));

          fos.write(b, 0, len);

        }

在写的时候发现,stream流在读完之后好像就莫得了,所以读写文件需要写在一起,不能分开写。

Creat3读文件利用了BufferedReader,写文件利用了FileWriter

StringBuffer sb = new StringBuffer("");

      InputStreamReader read = new InputStreamReader(new FileInputStream(Filename));

      String lineTxt = null;

      BufferedReader bufferedReader = new BufferedReader(read);

      while ((lineTxt = bufferedReader.readLine()) != null) {

        sb.append(lineTxt);

      }

      BufferedWriter bw = new BufferedWriter(new FileWriter(new File("txt/Trackgamestorage.txt")));

      bw.write(sb.toString());

 

类似的,Socialnetwork也是用同样的方法完成的,只不过构建的判断条件和正则表达式有一点区别。

 

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

代码运行的时间利用了网上的方法,

long startTime = System.currentTimeMillis();是起始时间

中间是需要计算时间的代码

long endTime = System.currentTimeMillis();是终止时间

    float excTime = (float) (endTime - startTime) / 1000;

    System.out.println("执行时间为:" + excTime + "s");输出实际用的时间,单位是秒(s

整个的测试代码

不同IO切换是靠改代码来改的。

测试结果表格

  

Trackgame

Socialnetwork

Creat

读文件

0.33

0.42

 

写文件

0.22

0.66

Creat2

读文件

0.85

1.26

 

写文件

0.24

0.67

Creat3

读文件

0.09

0.08

 

写文件

0.23

0.73

 

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

[GC (Allocation Failure)  33280K->10858K(125952K), 0.0052853 secs]

[GC (Allocation Failure)  44138K->29192K(159232K), 0.0098043 secs]

[GC (Allocation Failure)  95752K->66008K(159232K), 0.0183733 secs]

[Full GC (Ergonomics)  66008K->38274K(189440K), 0.0157239 secs]

[Full GC (Ergonomics)  178562K->115879K(278016K), 0.0456882 secs]

[GC (Allocation Failure)  182439K->126944K(323072K), 0.0112205 secs]

[GC (Allocation Failure)  238560K->145649K(344576K), 0.0229850 secs]

[GC (Allocation Failure)  278769K->167781K(404480K), 0.0259430 secs]

执行时间为:2.429s

Minor GC的频率远大于Full GC发生的频率

其中单次Minor GC的时间在比较短单次Full GC时间比较长,最后一次时间较长为0.61s

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

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

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

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

      1. 使用JMC/JFRjconsoleVisualVM工具

      1. 分析垃圾回收过程

1、所有new出来的对象都会最先分配到新生代区域中

2、当伊甸园区域满了之后,就会引发一次小型垃圾回收

3、当在小型垃圾回收,存活下来的对象就会被移到S0区域;

4、当伊甸园区域再次填满时,又会发生下一次垃圾回收。但是,不同的是,在这次垃圾回收中,存活对象和之前在S0区域中的对象都会移到S1区域中。一旦所有对象都被移到S1区域中,那么S0中的对象就会被清除;

5、下一次的垃圾回收的时候,又会重复上次的步骤,清除需要回收的对象,并且切换一次survivor区域,所有存活的对象都被移到S0,伊甸园区域和S1区域被清除;

6、重复以上步骤,并记录垃圾回收的次数,当有对象的年龄达到一定的阈值时,就将新生代中的对象移到到老年代。比如下图,这个阈值为8;

7、接下来垃圾回收就会重复以上步骤,不断的进行对象的清除和年代的移动;

8、观察上述步骤发现,大部分的垃圾回收过程都是在新生代进行的,直到老年代的内存不够用了才会发起一次minor GC ,会进行标记和整理压缩。

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

-XX:+UseG1GC
使用G1收集器
-XX:MaxGCPauseMillis=200 
用户设定的最大gc 停顿时间,默认是200ms. 
-XX:InitiatingHeapOccupancyPercent=45 
默认是45,也就是heap中45%的容量被使用,则会触发concurrent gc

-XX:NewRatio=n
新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n    eden/survivor
空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n
提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n
设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.
-XX:ConcGCThreads=n
并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=n
设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:G1HeapRegionSize=n
使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.

经过调试最后的优化结果是

-XX:MaxHeapFreeRatio=85

-XX:NewRatio=2

-XX:+UseConcMarkSweepGC

 

 

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

读取文件所花的时间是最长的,因为读取文件的时候要对数据进行处理,新建每一个对象,其次是添加轨道,因为对象每到达一定数量都要新建一个轨道。最后是输出,因为程序会输出生成好的组的内部信息,所以需要的时间也比较长。

      1. 使用VisualVM进行Memory profiling

Char很多,因为在读写的时候用到了很多char,而且物体的内部数据也有char类型。Byte也有很多,因为读文件的时候构建了若干byte数组来分段读取,boolean也多因为函数本身是boolean型,当初为了判断方法是否完成。

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

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

利用这个输出hprof文件

打开该hprof文件

 

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

 原来程序中有checkRep还在运行…删掉了检测异常的代码。性能对比:原本无法在20min之内完成SocialNetworkCircle构建 在< 5min之内完成

删掉了一些条件的判断,对范围的判断进行了优化。性能对比:3824ms              à   3243ms

 

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

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

 

      1. 使用设计模式进行代码性能优化

Prototype模式

在不指定类名的前提下生成实例。通过clone方法的Cloneable接口的使用来创建出实例的副本。

Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

因为Java中的提供clone()方法来实现对象的克隆(具体了解clone()按这里),所以Prototype模式实现一下子变得很简单.

  public TrackGame clone(TrackGame trackGame) {

    TrackGame resulTrackGame = new TrackGame();

    EmptyCentral emptyCentral = trackGame.getCenter();

    Map<Track, List<Player>> trackMap = new HashMap<Track, List<Player>>();

    for (Track k : trackGame.getTracks().keySet()) {

      List<Player> list = new ArrayList<Player>();

      Track track = new Track(k.getName(), k.getR());

      for (Player j : trackGame.getTracks().get(k)) {

        list.add(j);

      }

      trackMap.put(track, list);

    }

    Map<Player, Double> centralrelation = new HashMap<Player, Double>();

    Map<Player, Map<Player, Double>> relation = new HashMap<Player, Map<Player, Double>>();

    Map<Player, Position> positionMap = new HashMap<Player, Position>();

    int type = trackGame.getGametype();

    int number = trackGame.getNumOfTracks();

    List<Player> playersList = new ArrayList<Player>();

    for (Player k : players) {

      Player player = new Player(k.getName(), k.getNumber(), k.getNation(), k.getAge(), k.getBestScore());

      playersList.add(player);

    }

    resulTrackGame.setGametype(type);

    resulTrackGame.setNumOfTracks(number);

    resulTrackGame.setCenter(emptyCentral);

    resulTrackGame.setTracks(trackMap);

    resulTrackGame.setCenterRelation(centralrelation);

    resulTrackGame.setRelation(relation);

    resulTrackGame.setPositions(positionMap);

    resulTrackGame.setPlayers(playersList);

    return resulTrackGame;

  }

    1. Git仓库结构

 

请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚本实验中要求的多个分支和master分支所指向的位置。

  1. 实验进度记录

请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。

每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。

不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。

日期

时间段

计划任务

实际完成情况

2019.05.27

18.00-23.00

完成3.1.1

完成

2019.05.28

18.00-23.00

完成3.1.2

完成

2019.05.29

14.00-17.30

完成3.1.2

完成

2019.05.29

18.30-23.00

完成3.2.1

完成

2019.05.30

15.30-22.00

完成3.2余下内容

完成

2019.06.02

9.30-12.30

完成余下内容

完成

  1. 实验过程中遇到的困难与解决途径

遇到的难点

解决途径

CheckStyle中改缩进太费劲了

百度到了已经编辑好的格式,重新格式化

读大文件时无法成功构建

 

加大了内存,实在构建不出来时放弃构建,只计算读取时间

 

MAT看不明白

 

问了一下已经做好的同学,帮忙操作

  1. 实验过程中收获的经验、教训、感想
    1. 实验过程中收获的经验和教训
    2. 针对以下方面的感受
  1. 代码“看起来很美”和“运行起来很美”,二者之间有何必然的联系或冲突?哪个比另一个更重要些吗?在有限的编程时间里,你更倾向于把精力放在哪个上?

我觉得他们之间冲突不是很大,虽然我觉得Google规范过于苛刻,有些并不实用,但是按照规范格式化之后,代码确实变得一睹为,清晰,更方便其他人看,运行起来很美可以让用户用起来比较舒服,我更倾向于运行起来很美。

  1. 诸如SpotBugs和CheckStyle这样的代码静态分析工具,会提示你的代码里有无数不符合规范或有潜在bug的地方,结合你在本次实验中的体会,你认为它们是否会真的帮助你改善代码质量?

会改善,变得易读一些,不过因为要让我更改包名和类名,重构之后出了一些奇怪的bug,所以以后在写代码的时候还是直接写的符合规范比较好,要不然之后再改也不好改。

  1. 为什么Java提供了这么多种I/O的实现方式?从Java自身的发展路线上看,这其实也体现了JDK自身代码的逐渐优化过程。你是否能够梳理清楚Java I/O的逐步优化和扩展的过程,并能够搞清楚每种I/O技术最适合的应用场景?

额,还不太了解,但是认识了许多读取形式,写的时候还发现了Scanner在本次实验中并不好发挥作用,起码在读文件的时候

  1. JVM的内存管理机制,与你在《计算机系统》课程里所学的内存管理基本原理相比,有何差异?有何新意?你认为它是否足够好?

我觉得计算机系统讲的更细致,但是是在linux系统下进行分析的,直接分析windows下的程序感觉还是有一些差距的,尤其是程序的选择和表示方法

  1. JVM自动进行垃圾回收,从而避免了程序员手工进行垃圾回收的麻烦(例如在C++中)。你怎么看待这两种垃圾回收机制?你认为JVM目前所采用的这些垃圾回收机制还有改进的空间吗?

自动的肯定是减少了负担,还有改进的空间,现在采用的方法就是第9次就肯定删掉了,不知道会不会影响一些特别大的程序,不过肯定影响不到我

  1. 基于你在实验中的体会,你认为“通过配置JVM内存分配和GC参数来提高程序运行性能”是否有足够的回报?

对于一些大程序来说,这种优化肯定能提升不少速度

  1. 通过Memory Dump进行程序性能的分析,JMC/JFR、VisualVM和MAT这几个工具提供了很强大的分析功能。你是否已经体验到了使用它们发现程序热点以进行程序性能优化的好处?

可以针对占用内存最多,运行时间最长的地方进行优化,但是我没太用明白,不过给优化提供了一个具体的方向。

  1. 使用各种代码调优技术进行性能优化,考验的是程序员的细心,依赖的是程序员日积月累的编程中养成的“对性能的敏感程度”。你是否有足够的耐心,从每一条语句、每一个类做起,“积跬步,以至千里”,一点一点累积出整体性能的较大提升?

啊…先别挂科再说吧

  1. 关于本实验的工作量、难度、deadline。

难度适中,主要是对新软件不熟悉,操作起来不太适合

  1. 到目前为止,你对《软件构造》课程的意见与建议。

还行吧,希望实验指导书能写的再详细一点。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值