【软件构造】课程提纲(6)
第八章
1. 内存管理模型
(1)静态:程序开始时就已分配好
·在程序执行期内实体至多关联一个运行时对象
- 实体:属性、参数、局部变量和结果等在代码中的名字,其值为对象或对对象的引用
- 在执行过程中的某个阶段,如果x的值是O或对O的引用,则实体x附加到对象O 类型
·不支持递归和创建动态数据结构
(2)堆:将内存分为多份,保存对象或未使用
·通过显式请求动态创建对象,完全动态,亦称自由模式
·一个实体可连续附加到任意个对象,对象可包含对其他对象的引用
·对象创建的模式在编译时通常不可预测
·自由模式允许开发者设计复杂的动态数据结构
(3)栈:方法调用和局部变量的存储位置,保存基本类型
·如果一个方法被调用,它的栈帧被放到调用栈的顶部
·栈帧保存方法的状态,包括执行哪行代码以及所有局部变量的值,栈顶始终是当前运行方法
·一个实体可以在运行时连续地连接到多个对象,并且运行时机制以堆栈中的后进先出顺序分配和释放这些对象
(4)堆和栈的关系
·基本类型保存在线程栈中,对象保存在堆中
·局部变量保存在线程栈中
·局部变量引用了对象,引用保存在栈中,对象存储在堆中
·对象包含的方法和方法包含的局部变量存储在栈中
·静态类变量保存在堆中
·所有对该对象有引用的线程都可以访问堆中的对象
·当一个线程访问一个对象时,它也可以访问该对象的成员变量
·如果两个线程同时调用同一个对象上的一个方法,它们都可以访问该对象的成员变量,但是每个线程都有自己的局部变量副本
2. 垃圾收集相关概念
(1)GC:识别垃圾并释放其占用的内存
(2)Root
·根集合由root对象和局部对象构成
·root对象:Class(不能被回收)、Thread、Java方法/接口的本地变量或参数、全局接口引用等
(3)可达/不可达对象(Reachable/Unreachable):free模式
·从根可以直接或间接到达的对象为可达的,否则为不可达的
·从根开始,不断将指向的对象加入活动集,剩下的是垃圾
(4)活动/死亡对象(Live/dead):在stack和free的结合模式下,对象的引用被视为有向图,可以从根访问的对象为活动对象,否则为死亡对象
3. GC的四种基本算法
(1)引用计数:在每个对象上留下一个注释,指出对象的实时引用数量,复制时引用加一,若一个对象的引用计数为零,则该对象已死,抛出
·优点:易于实现,在整个计划中分配费用,只涉及新旧目标的RC,很少有对象共享、大多短命,对象从变成垃圾到被回收的时间很短,可立即完成
·缺点:不全面,操纵成本高,并发性不好,与增变器紧密耦合,额外空间占用高,递归释放级联仅受堆大小限制
(2)标记-清除:标记出root,递归地标记所有活动对象所需的对象,清除无标记的
·优点:自然循环收集,指针操作无运行时间开销,松散耦合到增变器,不移动对象
·缺点:堆占用高时影响性能,容易造成碎片,需要找到root
(3)标记-压缩:标记出所有需要的对象,将其放到仓库后面,然后烧掉前面的
(4)复制:把有用对象复制到新仓库,递归地复制所有有用对象,烧掉旧仓库
·当Tospace满了时,翻转角色,将Fromspace中的活动对象复制到Tospace中,在Fromspace副本留下转发地址保留共享,将Tospace中的对象作为工作队列使用
·优点:只处理事实数据(通常为堆的一小部分),固定的空间开销,各种大小对象的分配都很便宜,自然地收集垃圾,简单、合理、高效
·缺点:停下来复制可能具有破坏性,降低居住权,占用其他收集器两倍的开销,对于大对象开销很大,长寿的数据被重复复制,所有引用必须更新,移动对象会破坏增变器的不变形,广度优先复制可能会干扰局部模式
4. Java/JVM 的内存管理模型
(1)年轻代(young):存放新对象,使用复制算法,满了以后使用Minor GC
(2)老年代(old):存放垃圾回收后仍然存活的对象,使用标记-清除或标记-压缩算法,满了以后进行Full GC
(3)持久代(permanent):存放class元数据,满了以后进行Full GC
5. Java 性能调优:GC最佳控制时间为5%左右
(1)参数配置:
·-Xmx/-Xms:指定年轻代和老年代空间的初始值和最大值;Xms小于Xmx时,年轻代和老年代所消耗的空间量可以根据应用程序的需求增长或收缩;Java堆的增长不会比Xms大,也不会比Xmx小
·-XX: NewSize=<n>[g|m|k]:年轻代空间的初始和最小尺寸,<n>是大小,[g | m | k]指示大小是否应解释为千兆字节,兆字节或千字节
·-XX: MaxNewSize=<n>[g|m|k]:年轻代空间的最大值
·-Xmn<n>[g|m|k]:将年轻代的初始值、最小值、最大值设为同一值
·老年代大小与年轻代相关,等于总体值减年轻代
(2)GC模式选择
·增长或收缩年轻代或老年代的空间时需要Full GC
·Full GC可能会降低吞吐量并导致超出期望的延迟
·串行收集器(-XX:+UseSerialGC):使用单个线程执行所有垃圾收集工作
·并行收集器(-XX:+UseParallelGC):并行执行Minor GC,显著减少垃圾收集开销
·并发低暂停收集器(-XX:+UseConcMarkSweepGC):收集持久代,与执行应用程序同时执行大部分收集,在收集期间会暂停一小段时间
·增量低暂停收集器(-XX:+UseTrainGC):收集每个Minor的部分老年代,并尽量减少Major的大停顿
·-verbose:gc:打印GC信息
6. Java 性能调优工具
(1)Jstat:获取JVM的Heap使用和GC的性能统计数据,命令如-gcutil
(2)Jmap:输出内存中的对象分布情况
(3)Jhat:导出heap dump,浏览/查询其中的对象分布情况
(4)Visual VM:提供了一个可视化界面,用于查看Java应用程序在JVM上运行时的详细信息,使用各种技术,包括jvmstat,JMX,Serviceability Agent(SA)和Attach API等
(5)MAT:内存堆导出文件的分析工具,生成饼状图等,能够对问题发生时刻的系统内存状态获取一个整体印象,找到最有可能导致内存泄露的对象,进一步查看其是否有异常行为
7. 堆转储(Memory dump):记录JVM中堆内存运行的情况,可使用jmap或JConsole命令生成,jhat分析
8. 栈跟踪(Stack trace):可使用jstack查看,定位线程出现长时间停顿的原因
9. Java 代码调优的设计模式
(1)单例模式(Singleton)
·某些类在概念上只有一个实例,类负责管理其唯一实例
·对管理重用的代码进行封装,并提供一个全局访问点
·提供static方法和字段允许对唯一实例进行调用
·延迟加载,不在启动时创建实例,在首次访问时创建
(2)原型模式(Prototype)
·通过复制已有原型对象新创建对象,当创建或初始化对象代价高时可降低开销
·适用场合:
- 当一个系统应该独立于它的产品创建、构成和表示时
- 当要实例化的类是在运行时刻指定时
- 为了避免创建一个与产品类层次平行的工厂类层次时
- 当一个类的实例只能有几个不同状态组合中的一种时
·类中要声明clone()方法,调用super.clone()(in Object),实现java.lang.Cloneable接口
·浅拷贝:将对象A的所有字段复制到B中,共享对对象的引用
·深拷贝:复制对象引用时,会创建新的被引用对象,A和B完全独立
(3)享元模式(Flyweight)
·共享对象,在多个情境下同时使用
·内在状态不变,可共享;外在状态不固定,不可共享
·flyweight声明一个接口来接受外部状态并采取行动
·ConcreteFlyweight保存可共享状态,UnsharedConcreteFlyweight不可共享
·FlyweightFactory负责flyweight的创建、管理、提供给客户端
·客户端通过索引获取flyweight对象,调用方法计算外在状态
(4)Pool
·已经有了一个对象不再使用时,可以让其他对象继续使用
·使用时从pool取出,用完后归还,类似工具箱
·与flyweight的区别
- pool中对象只能一个client用,flyweight可以被多个client使用
- pool中对象时同质的,取哪个都一样,flyweight需查找特定的
- pool的应用场景是对象被频繁创建、销毁、使用、生命周期短,同一时刻存在的对象数量少;flyweight的应用场景是同一时刻存在大量对象
10. 常见的Java I/O 方法
(1)Stream(字节流):直接操作文件,不关闭也会输出,是更好的方式
(2)Reader(字符流):缓存到Buffer再操作,不关闭无输出,除非flush清空缓冲区
(3)Nio:基于通道和缓冲区的同步非阻塞I/O方式
·Channel(字节流):涵盖了UDP 和 TCP 网络IO,以及文件IO
- FileChannel 从文件中读写数据
- DatagramChannel 通过UDP读写网络中的数据
- SocketChannel 通过TCP读写网络中的数据
- ServerSocketChannel像Web服务器那样监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel
·Buffer:覆盖了能通过IO发送的基本数据类型
- 物理存储器的一个区域,用于在数据从一个地方移动到另一个地方时临时存储数据
- 上层应用组件无需等待下层组件接受全部数据即可返回操作,加快了上层组件的处理速度,从而提升系统整体性能
- 最重要的是ByteBuffer类,代表了原始字节的固定大小的向量
·Selector:允许线程处理多个Channel
- 使用Selector时向Selector注册Channel,调用其select()方法,这个方法一直阻塞到某个注册的Channel有事件就绪
- 一旦这个方法返回,线程就可以处理这些事件,如新连接进来,数据接收等。
(4)Scanner:可以从字符串、输入流、文件等等来直接构建Scanner对象,默认使用空格作为分割符来分隔文本,但允许使用useDelimiter指定新的分隔符