天天看点

Lab5

3.1.1 人工代码走查(walkthrough)

列出你所发现的问题和所做的修改。每种类型的问题只需列出一个示例即可。

  1. 为了方便对于Google风格的Java代码的,我们将google风格代码直接导入eclipse。具体方法是从https://github.com/codeset/google-java-styleguide中获得xml格式导引文件,并按如下方式设置:Window -> Preferences -> Java -> Code Style -> Formatter -> Import。
  2. 主要检查以下几个方面:

    · import不要使用通配符。错误实例:import circularOrbit.*;

    · 即使只有一条语句(或是空),也应该把大括号写上。错误实例:

    if (a!=0) continue;

    · 左大括号前不换行。错误实例:

    if (a!=0)

    {

    continue;

    }

    · 左大括号后换行。错误实例:

    if (a!=0) { continue;}

    · 右大括号前换行。错误实例:

    if (a!=0) {

    continue;}

    · 每次只声明一个变量。错误实例:

    int a,b;

    · Javadoc是可选的(即,是可以不写的)。实例:如getSex一类有明确而简单目的的方法不需要写Javadoc。

    · 块缩进:2个空格。错误实例:之前以tab键进行缩进。

    · 除了行结束符序列,ASCII水平空格字符(0×20,即空格)是源文件中唯一允许出现的空白字符。错误实例:之前以tab键进行缩进。

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

    使用checkStyle发现的问题:

  3. 缺少JavaDoc。举例:getObjectDistributionEntropy函数未编写doc。补上即可。
  4. @中应当有非空说明。举例:在doc中@param参数后面应当有对该参数的解释。
  5. 导入语句字典顺序错误。举例:在CircularOrbitHelper的import中,应当有导入语句’circularOrbit.AtomStructure’ 字典顺序错误。应在’javax.swing.JPanel’之前。
  6. ‘static’ 修饰符顺序违反 JLS 建议。举例:private final static int CENTRAL_RADIUS = 10;
  7. 顶级类应位于它自己的源文件中。举例:顶级类 Log 应位于它自己的源文件中。
  8. 变量声明及第一次使用距离7行(最多:3 行)。举例:LogSearcher中变量’rTime’声明及第一次使用距离7行(最多:3 行)
  9. WhitespaceAround: ‘}’ is not preceded with whitespace.:举例:在application中的语句else if (choice.equals(“EOF”)) {}
  10. 每一个变量的定义必须在它的声明处,且在同一行。举例:在application中声明时出现Track t1 = null,t2 = null;
  11. ‘if’ 结构必须使用大括号 ‘{}’。举例:if (t1 != null && t2 != null) break;
  12. Javadoc的第一句缺少一个结束时期。举例:

    中第一句没有句号。

    1. 禁止一行有多句代码。举例:SocialNetworkPhysicalObject中的失误import exceptions.DefineMultipleRelationException;;

      使用SpotBugs发现的问题:

    2. SS_SHOULD_BE_STATIC:在变量中一直不变的的变量应该加上static关键字。
    3. SIC_INNER_SHOULD_BE_STATIC:未改变的内部类应当定义为static final的。
    4. SBSC_USE_STRINGBUFFER_CONCATENATION:在将字符串进行拼接时,应尽量使用StringBuffer而不是直接相加。
    5. DM_STRING_CTOR:在确定返回的字符串不会被修改的情况下尽量不要使用String构造方法进行defensive copying。
    6. DM_DEFAULT_ENCODING:找到一个方法的调用,它将执行一个字节到字符串(或字符串到字节)的转换,并假定默认的平台编码是合适的。这会导致应用程序行为在不同平台之间变化。使用替代API InputStreamReader并明确指定字符集名称或字符集对象。
    7. NP_DEREFERENCE_OF_READLINE_VALUE:在使用readLine方法进行读取时,应当判断读到的内容是否为空。
    8. DM_BOXED_PRIMITIVE_FOR_PARSING:应当使用封装/反封装来解析一个基本类型。比如说使用了Integer类型获得一个值,应当使用intValue将其转化为int值。
    9. REC_CATCH_EXCEPTION:在捕获异常时最好不要有直接捕获Exception的情况,
    10. NM_METHOD_NAMING_CONVENTION:应当将方法名称首字母小写。
    11. BX_BOXING_IMMEDIATELY_UNBOXED:装箱之后又迫使编译器自己取消装箱。这时应该用intValue等函数自己取消装箱。
    12. DM_NUMBER_CTOR:方法调用低效的数字构造方法,应使用静态valueOf代替。
    13. UWF_UNWRITTEN_FIELD:某个字段从未被赋值过。这是由于写代码时的疏忽,需在初始化方法中对该字段进行初始化。
    14. URF_UNREAD_FIELD:出现从未被使用的属性,可以直接删除。这是由于编程时的疏忽。
    15. EC_NULL_ARG:调用了equals(null)方法,这是由于对null的本质理解不透彻造成的。应当改为==null。
    16. BX_BOXING_IMMEDIATELY_UNBOXED_TO_PERFORM_COERCION:封装了基本类型的值,然后为了进行基本类型的强制转换,又再次反封装。应当手动进行反封装。
    17. DM_STRING_VOID_CTOR:生成空字符串时使用了new String()构造函数。由于直接赋值空字符串「""」的效率较高,应当直接赋值空字符串。

      3.2 Java I/O Optimization

      我们将相关代码放于output包中。在第一个任务中,定义一个抽象父类Output,再定义三个子类AtomStructureOutput,SocialNetworkOutput和StellarSystemOutput分别实现Output 中的方法,完成写入文件功能。

      3.2.1 多种I/O实现方式

      我们实现Writer,buffer,stream三种文件输入方式。根据strategy设计模式,我们定义一个接口OUTStrategy,并定义两个方法:write用于写入文件,close用于关闭输入方式。并定义三个子类:OUTBuffer,OUTStream和OUTWriter应用这个接口,分别用于实现这三个输入文件方法。

      此后,定义一个外部接口类OUTContext。其中有一个OUTStrategy参数,并有write和close两个方法。用户只需传入不同的OUTStrategy,就可以实现不同方式的输入文件。主要代码如下,我们以AtomStructureOutput为例:

      OUTBuffer buffer = new OUTBuffer(file);

      OUTContext context = new OUTContext(buffer);

      context.write(getElementName());

      context.write(getNumTrack());

      context.write(getNumberOfElectron(log));

      context.close();

    我们实现Reader,buffer,stream三种文件读出方式。根据strategy设计模式,我们定义一个接口INStrategy,并定义两个方法:readLine用于读出一行文件,close用于关闭读出方式。并定义三个子类:INBuffer,INStream和INReader应用这个接口,分别用于实现这三个读出文件方法。

    此后,定义一个外部接口类INContext。其中有一个inStrategy参数,并有readLine和close两个方法。用户只需传入不同的INStrategy,就可以实现不同方式的从文件读入。主要代码如下,我们以AtomStructure的readFromFiles函数为例:

    INBuffer ib = new INBuffer(filename);

    INContext in = new INContext(ib);

    再使用readLine函数进行读取即可。

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

    如何收集你的程序I/O语法文件的时间。

    表格方式对比不同I/O的性能。

    图形对比不同I/O的性能。

    1. 对于这一部分的内容,我们将相关代码存放于包inouttest中的InOutTest类中。分别设计doThingsToSocialNetwork函数和doThingsToStellarSystem函数进行相关行为的测试和时间度量。最后,在main函数中调用这两个函数进行测试。
    2. 我们使用System中的currentTimeMillis函数在要检测的时间开始节点和结束节点获得当前的时间,并相减,就可以得到该段代码执行的时间。
    3. 对于StellarSystem,我们做的操作是删除一个星球及其轨道,再添加一个星球及其轨道。对于SocialNetworkCircle,我们选择删除一个朋友,再添加一个朋友。
    4. 以下是统计表格:

      SocialNetworkCircle.txt StellarSystem.txt AtomicStructure.txt

      Buffer 读 30102μs 18135μs 1662μs

      写 4055μs 1814μs 1776μs

      Reader/

      Writer 读 6406μs 18951μs 307μs

      写 2794μs 1482μs 2497μs

      Stream 读 4525μs 17368μs 654μs

      写 1305μs 1184μs 1602μs

    以下是相关数据的控制台输出截图:
    1. 以下是可视化绘图(时间单位:μs)

    3.3 Java Memory Management and Garbage Collection (GC)

    3.3.1 使用-verbose:gc参数

    输出的日志部分截图下:

    由上图可以看出,对于这个测试程序只在 1.771 秒时执行了一次 FullGC,对于日志文件中 FullGC 这一行的数据显示,垃圾收集 GC 前后所有存活对象使用的内存容量分别为 91456K 和 89824K,说明有 91456K -89824K =1632K 的对象容量被回收. 括号内的数据 818176K 为 堆内存的总容量,收集所需要的时间是 0.461725 秒

    控制台输出每次 GC 前后 heap 中各 区域占用情况的变化,输出为:

    上图中一部分和上面 verbose-gc 参数下运行结果相同,也是体现了一次 FullGC 以及它的GC 前后存活对象使用的内存容量,堆内存总容量,收集时间等结果。 下面则显示了 heap 上具体的空间属性,在上图这个部分截图中,可以看到记录了 young generation ( eden ) , old generation , permanent generation的总空间大小、空间变化、和占用百分比。

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

    前面几个参数显示了 survivor , edem , old , permanent 空间的总大小和占用大小。 也可以看出从程序运行截至到采样时刻,youngGC 执行了 11 次,用时 0.063 秒,fullGC 执 行2次,用时 0.081s,GC 总用时为0.144s。这个数据基本是符合预期的。

    参数设置:间隔 10 秒采样一次,采样次数50次。 在这里只展示前3次。前面几个参数分别是各空间被占用的百分比,而后面是YoungGC和fullGC从程序从开始到采样点的次数与时间。

    3.3.3 使用jmap -heap命令行工具

    运行后可以看到一系列堆栈信息如上图所示。

    3.3.4 使用jmap -clstats命令行工具

    结果如下:

    3.3.5 使用jmap -permstat命令行工具

    查看class loader的统计信息,即程序执行期间装载的class和method相关信息:

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

    我们选择使用VisualVM工具:

    3.3.7 分析垃圾回收过程

    从上述的一系列数据来看,在垃圾回收过程中进行了Minor GC、Full GC,每次GC均成功运行了相应的内存区域,并且时间是可接受的。内存占用和类的装载基本符合预期。

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

    使用多种参数配置后,发现参数设置如上图所示时,花费时间在尝试的数据中为相对较好的水平。

    3.4 Dynamic Program Profiling

    3.4.1 使用JMC或VisualVM进行CPU Profiling

    在 app 中输入读文件指令使程序读文件建图并输出文件,控制台输入如图:

    除了等待用户输入耗时较长之外其他操作需要时间都较短。

    3.4.2 使用VisualVM进行Memory profiling

    从图中可见占用内存较大的首先为char[],其次是继承 Object 的各个类,和后面的char[ ],int[]。char[ ]的占用具体要在 heapdump 中查看,如下图所示:

    可见所谓的 char[ ]其实包括了UTF-8 的编码解码,outputstreamwriter,bufferedwriter,还有构造方法等,是一个复杂的集合,所以也占用较大的内存是合理的。

    3.5 Memory Dump Analysis and Performance Optimization

    3.5.1 内存导出

    VisualVM的heapdump导出HPROF文件即可。

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

    Histogram 视图查看内存中存储的各类型实例数量以及所用内存的情况。结果如下:

    Dominator tree视图查看各实例的引用关系:

    Top Consumers视图查看当前时刻程序的内存占用热点:

    最后是Leak suspects report:

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

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

    使用如下语句进行内存导出:

    得到如下结果:

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

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

    Flyweight设计模式:

    在circularOrbit包中创建一个ElectronicFactory以确保每个轨道上的电子都是同一个对象。

    思路:使用HashMap名为table进行电子和对应轨道的对应。再使用一个HashMap名为count存储对应轨道和其上电子的个数。这样一来,每个轨道上实际上只对应了一个AtomPhysicalObject对象。

    主要的三个函数代码如下:

    public AtomPhysicalObject getElectron(Integer radius) {

    if (table.containsKey(radius)) {

    return table.get(radius);

    } else {

    return null;

    }

    }

    public void addElectron(Integer radius) {

    if (table.containsKey(radius)) {

    Integer value = count.get(radius);

    count.put(radius, value + 1);

    } else {

    table.put(radius, new AtomPhysicalObject(String.valueOf(radius)));

    count.put(radius, 1);

    }

    }

    public boolean deleteElectron(Integer radius) {

    if (table.containsKey(radius)) {

    if (count.get(radius).equals(new Integer(“1”))) {

    count.remove(radius);

    table.remove(radius);

    } else {

    Integer c = count.get(radius) - 1;

    count.put(radius, c);

    }

    return true;

    } else {

    return false;

    }

    }