第八章软件构造的性能——构造性能的度量、原则与方法(java中的垃圾回收机制及算法)

这节内容真的是多到炸裂,而且全都是概念,不过很挺有用的,学完这节会对内存管理有一个比较深的理解和认识,就是……这也太多了……嘤嘤嘤T_T

1.性能度量(performance metrics)

第一点很少,因为我们主要来说运行时性能,其主要分为两种(就是时空啦):

  • 时间性能:每条指令、每个控制结构、整个程序的执行时间
  • 空间性能:每个变量、每个复杂结构、整个程序的内存消耗

2. 内存管理(Memory Management)

A.操作系统(OS)及应用层面中的内存管理

本机内存会分配给一个进程的物理内存,交给OS管理,注意OS通常用的是虚拟内存,其在虚拟内存空间给进程分配内存,并与物理内存建立映射表。(因为我们课程这是软件构造,这是计算机系统的硬件知识,所以我们只提到用的就可以了,具体细节大家不懂的话,感兴趣可以度娘,硬件有的东西还是挺有趣的,感触最深的是缓存那里很多地方都会有类似思想……不过笔者也在学硬件上有了点心里阴影@.@,不想在了解了,最后说一句GTMD硬件!)

扯远了,不过记住关于内存管理时刻围绕的两个方面——内存分配垃圾回收

因为内存不足,所以需要考虑高效率的内存管理问题,这就需要动态分配回收内存,还有复用已回收的内存。

内存对象模型(object model):

每个对象存储在内存中一段连续的空间中,如果是引用则存储他所指向的内存对象的内存地址。

对象在堆heap中分配内存

对象引用:指向其他对象在堆中的起始地址。

非基本数据类型的变量等价于对象引用,每个对象可包含一组变量,每个变量可指向其他对象的引用,对象引用只指向一个其他对象,但是一个对象可以被多个对象所引用。(挺拗口的,不过很简单)

B.对象管理的三种模式

首先先来谈一下静态内存分配与动态内存分配:

  • 静态内存分配:在编译阶段就已经确定好了内存分配。(静态变量等)
  • 动态内存分配:在运行时动态分配内存,建立新的内存对象。(基于堆栈的内存管理都是动态分配的)

好,现在来说一下对象管理的三种模式:

  • 静态(static)在将程序load进内存的时候或开始执行的时候,就确定了所有对象的分配。不支持递归,不支持动态创建的可变长的复杂数据类型。
  • 动态-基于堆(Stack-based)栈的存储方法调用以及方法执行中的局部变量,先进后出,无法支持复杂的而数据类型。
  • 动态-基于栈(Heap-based【free】)在一块内存中分为多个小块,每块包含一个对象,或者未被占用。代码中的一个变量可以在不同的时间被关联到不同的内存对象上,无法在编译阶段确定。内存对象也可以进一步指向其他对象。
    堆:自由模式的内存管理,动态分配,可管理复杂的动态数据结构。

他们的差异主要在于是如何与何时在程序对象与内存对象之间建立联系。

如果一些应用管理了一些限定数目的内存可以采用栈与静态组合的方式。而另一些只能采用动态分配,比如:

  • 某些对象延续时间比创建它的时间还要长(所以栈不行)
  • 递归的数据结构,长度可变的数据结构(所以静态与栈方法不行)
  • 经常要使用不限定长度的数据结构

3.java内存模型

java中内存管理是交给java虚拟机的,也就是我们平常说的JVM(java virtual machine)

java内部内存模型:堆

在Java中,所有对象都是在堆上创建的,即使是在局部变量中的object也是在堆上创建的。

注意:堆上创建的对象可被所有的线程共享引用,可访问对象,就可访问对象内的成员变量。如果两个线程调用同一个对象上的某个方法,他们分别保存该方法的局部变量的拷贝。

4. 垃圾回收GC(Garbage Collection)

三种模式下的内存回收:

在静态模式下,无需进行内存回收,所有的都是已确定的。

在栈上进行内存空间回收:按block(某个方法)整体进行。

在堆中进行内存空间回收,最复杂,因为无法预知某个object是否变得无用,下面我们就来解决这个问题。

可达与不可达对象(Reachable and Unreachable Object):

我们可以这样认为,把静态区域数据域寄存器和当前执行栈中的数据所指向的内存对象看成一个根节点,如果引用到了一个对象,就在引用对象节点与被引用对象节点间画一条有向边,这样就形成了一个图:

实现代码:

该算法优点:回收彻底,在指针操作上没有运行开销、变值器低耦合、不会违反任何不变量、对优化程序友好

缺点:可能会出现较长的幽灵时间、回收垃圾是都在扫描阶段完成的,这时会扫描整个堆,而不是全部的存活对象,所以是极为耗时的、堆有效占用率低,如果堆快满了,那么堆中标记位会占有很大一部分、如果对象被清除了,可能会在旧内存中留下几个空白,这种碎片化会导致应用程序很严重的性能问题、扫描器必须要找到根等等。

标记-整理方法(Mark-Compact):

基本思想:沿用标记-清除法的思想,不过这种方法会在存活对象中加上一个标记,把带有标记的对象放到垃圾对象的后面,这样扫描的复杂度就降到了死亡对象数目,并处理了碎片化的问题。

值得一提的是:在内存中对象的相对顺序保持不变——也就是说,如果object X的内存地址比GC之前的Y要高,那么它之后仍然会有一个更高的地址。对于像数组这样的特定数据结构,该属性非常重要。

但缺点也更加明显:标记-整理集合的最大问题是时间。它需要比标记-清除收集更多的时间,这将严重影响性能。

过程图:

而该算法与标记整理法处理碎片化的区别在于,标志整理法是对同一个区域内进行整理,本算法是将存活对象全部复制到另一个区域中。

过程图:

算法优点:整理阶段是没有代价的,并且分配阶段对所有大小的对象都是廉价的。只处理存活对象(通常比较少)、固定的空间开销、垃圾回收全面、实现较为简单,高效。

缺点:停止-复制阶段会导致程序中断一段时间、降低内存利用率(一半没用)、大型对象复制成本较高、存活时间久的数据可能会被反复复制、所有引用必须全部更新、可能会破坏某些不变量、第一次复制会干扰局部模式等等。

(终于说完了,揉揉脖子……)

6. JVM中的GC系统

Java中GC将堆分为不同的区域,各区域采用不同的GC策略,以提高GC效率。

偷个懒,这页大家就看看吧……

很形象就不解释啦……

7. java中的GC调整(Garbage Collection Tuning)

首先需要说一下几点:

  • 尽可能减少GC时间,一般不超过程序执行时间的5%。
  • 一旦初始分配给程序的内存满了,就抛出内存溢出异常。
  • 在启动程序中,可为其配置内存分配的具体大小。

调整javaGC步骤:

  1. 确定堆的大小
  2. 选择GC模式
  3. 使用verbose GC参数查看内存详细信息并确定堆的大小
  4. 自动记录内存将要不足的情况
  5. 手动请求GC
  6. 请求线程栈

调整参数:

具体指令大家可以度娘,有好多帖子,这里肝疼就不细讲了……

另外唠叨一句:GC信息是可以记录到log中以便后续分析的。来一个例图,eclipse的最后一条指令是生成日志文件,参数是文件路径(啪!我怎么这么多话呢……):

第八章软件构造的性能——构造性能的度量、原则与方法(java中的垃圾回收机制及算法)

8. java中的常见I/O方法:

还是,java语法的东西……给个方法列表,大佬跳过,不知道的同学可以逐个度娘啦……

  • Stream
  • Reader
  • Nio
  • Scanner
  • Lines

文章知识点与官方知识档案匹配,可进一步学习相关知识算法技能树首页概览33847 人正在系统学习中

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2018年5月17日
下一篇 2018年5月17日

相关推荐