2019春软件构造知识点总结 第8,10章

▪ 内存管理模型:堆、栈
三种对象管理模式
▪对象管理的形式(特别是在OO中)只是三种常见模式中的一种:

  • 静态的
  • 基于栈
  • 基于堆
  • Entity是指属性、参数、局部变量和结果等在代码中的名字,其值为对象或对对象的引用。
  • Attach将entity同object关联/绑定在一起。
    静态模式
    在静态模式下,在程序执行期内entity至多attach一个运行时对象。
    该技术在程序加载时或开始时一劳永逸地为所有对象分配空间(并将它们附加到相应的实体)
    不支持递归,不支持动态创建数据结构

基于栈的模式
堆栈是存储方法调用和局部变量的位置。

  • 如果调用方法,则将其堆栈帧(栈帧)放在调用堆栈的顶部。
  • 堆栈帧保存方法的状态,包括执行哪行代码和所有局部变量的值。
  • 堆栈顶部的方法始终是该堆栈的当前运行方法。
  • 一个entity在运行时可以先后attach多个对象,运行时机制以堆栈中的后进先出顺序分配和释放这些对象。
  • 当一个对象被释放时,相应的entity会再次attach到之前attached的对象(如果有的话)。

基于堆的模式
将内存分为多份,每份保存对象或未使用。
也称为自由模式,可通过显式请求动态创建对象的完全动态模式。
一个entity可先后attached任意数量的对象; 对象创建的模式在编译时通常是不可预测的, 对象还可以包含对其他对象的引用。
支持创建复杂的动态数据结构。

某些程序利用静态和stack分配结合的方式
有些利用动态分配

  • 某些对象的寿命比创建它们的方法长。
  • 递归数据结构,如列表和树。
  • 避免对数据结构大小进行固定的硬限制。

关于Java内存模型的一些要点
基本数据类型的局部变量保存在线程栈中
局部变量引用了对象,引用保存在栈中,对象本身存储在堆中
对象包含的方法和方法包含的局部变量存储在栈中
对象的成员变量同对象一起存储在堆中, 不论成员变量的类型是基本类型还是对象类型(对其他对象的引用)
静态的类变量同类的定义一起保存在堆中
堆中的对象可以被所有拥有引用的线程访问
当线程有权访问对象时,它还可以访问该对象的成员变量。
如果两个线程同时调用同一个对象上的一个方法,它们都可以访问该对象的成员变量,但是每个 线程都有自己的局部变量副本。

▪ GC,root、reachable、unreachable、live、dead
垃圾收集(GC)

静态模式中不存在内 存空间回收问题
动态模式(stack-based, heap-based)则需要考虑内存空间回收问题

基于堆栈的模式中的空间回收
基于块结构的语言 中:在给定块中声明的所有实体同时发生对象分配,从而允许为整个程序 使用单个堆栈。

基于堆(免费)模式的空间回收
Heap 模式中,对象的创建在编译时未知,所以无法预测对象何时无用(可回收)

root
根集合由系统的root对象以及局部entity、子程序的参数或返回值构成
Root的确定是语言相关的

  • 通常,确定根是依赖于语言的
  • 取决于底层语言定义的运行时结构。
    在通用语言实现中,根包括
  • 静态区域中的单词
  • 寄存器
  • 执行堆栈中指向堆的单词。

reachable
在free模式下管理内存,第一步 是区分可达对象和不可达对象

活对象与死对象
可以将对象和引用视为有向图:

  • 图表的活动对象是可从根目录访问的对象。
  • 执行计算的过程称为变化器,因为它在动态更改对象图。

跟踪可到达/无法访问的对象
我们可以简单地编码这个定义:

  • 第1步:从根开始; 现场设置为空
  • 步骤2:将根指向的任何对象添加到实时集
  • 第3步:重复
    将实时对象指向的任何对象添加到实时集
    直到找不到新的活动对象
  • 步骤4:不在实时集中的任何对象都是垃圾

如果一个对象是可达的,则为存活对象。
它被root中的对象指向或被live中的对象指向(从root间接指向)。
非活动对象称为死对象,即垃圾。

▪ GC的四种基本算法
识别垃圾并释放它占用的内存称为垃圾收集(GC)。
GC的成本指标
执行时间

  • 总执行时间
  • GC执行时间的分配
  • 分配新对象的时间

内存使用情况

  • 额外的内存开销
  • 分裂
  • 虚拟内存和缓存性能

延迟时间

  • 破坏性暂停的时间长度
  • 僵尸时间

其他重要指标

  • 全面性
  • 实现简单性和健壮性

GC的主要任务是区分可达对象和不可达对象(存活对象与死亡对象)
四种方法:

引用计数:

  • 在每个对象上记下一个注释,指示对象的实时引用数。 如果对象的引用计数变为零,则抛出该对象(它已经死了)。

目标:确定您何时是唯一所有者,从而您可以做出处置决定。
基本思路:计算活动对象的引用数。

  • 每个对象都有一个引用计数(RC)
  • 复制引用时,引用的RC递增
  • 删除引用时,引用的RC将递减
  • 当RC = 0时,可以回收对象
    Recursive freeing 递归释放
  • 一旦对象的RC = 0,就可以释放它。
  • 但是对象可能包含对其他对象的引用。
  • 在释放此对象之前,还应释放其成分的RC。

标记-清除

  • 记下你需要的物体(根)。
  • 然后以递归方式标记活动对象所需的任何内容。
  • 之后,检查所有对象并丢弃没有标记的对象。
    通过跟踪活动对象的引用来查找其他活动对象。
    每个对象都有一个标志,标志是否属于live集。
    有两个阶段:
  • 标记阶段:
    从根开始,跟踪图形并在遇到的每个未标记对象中设置标记位。
    在标记阶段结束时,未标记的对象是垃圾。
  • 清除:从root开始,清扫堆
    未设置标记位:回收对象
    标记位置位:标记位清零

标记-压缩

  • 将标记放在您需要的物体上。
  • 将带有标记的任何东西移到库的最后。
  • 烧掉库前放面的一切(全是死内存)。

复制

  • 将您需要的物体移动到新库。
  • 然后递归移动新库中物体所需的任何物品。
  • 之后,烧毁旧库(其中的都已死)
    复制集合是一种更简单的解决方案:它挑选出活动对象并将它们复制到“新鲜”堆中
    复制垃圾收集
    将堆分成两半,称为半空间,命名为Fromspace和Tospace
    在Tospace中分配对象
    Tospace满员时
  • 翻转(翻转)半空间的角色
  • 选择Fromspace中的所有实时数据并将其复制到Tospace
  • 通过在Fromspace副本中保留转发地址来保持共享
  • 将Tospace对象用作工作队列

▪ Java/JVM的内存管理模型:各时期、各区域的GC方法
Java垃圾回收将堆划分成不同的区域(generation代),以便GC可以更快地识别可以删除的对象
JVM会自动重新收集不再使用的内存。

  • 垃圾收集器将自动释放不再引用的对象的内存。
  • 要查看垃圾收集器开始工作,请将命令行参数“-verbose:gc”添加到虚拟机。

JVM中的垃圾收集
▪HotSpotVM(Sun JVM)有三个主要空间:年轻代,旧代和永久/元空间生成。
新对象分配到young generation中
GC后仍然存活的对象,提升到old generation中
PermGen/Metaspace中保存VM和class的元数据,以及类的静态变量
根据不同代的不同特征,采用不同的GC算法

In the young generation
每次GC会发现大量死亡对象,少量存活对象
复制算法适合,代价低

In the old generation
对象存活率高,适合采用标记算法

PermGen和Metaspace
PermGen中保存类的定义、静态方法、静态对象的引用等内容
缺点:
PermGen的容量是固定的(缺省或指定), 固定的容量容易导致运行时的内存溢出错误
Java 8 开始,用Metaspace替代了PermGen,区别在内存分配方面
Metaspace的容量是自动增长的,此外,当类的元数据使用达到了metaspace最大值时,会自动触发GC。
降低了OutOfMemory 的风险,但仍然需要监控和调优,避免内存泄漏

▪ JVM GC性能调优:参数配置、GC模式选择
GC中的性能注意事项:
吞吐量:总时间中非用于垃圾回收的时间
暂停: 由于GC导致的暂停次数
不同用户有不同的GC需求,E.g.,Web用户追求高吞吐量,而图形交互程序则追求低暂停。
scalability(可伸缩性). Promptness(及时性)
需要根据各种情况权衡代的容量,一个代的容量不影响其他代的回收频率和暂停时间。
没有普适的容量设置准则,应根据应用的内存使用和用户需求进行具体问题具体分析。最佳做法是将GC时间控制在执行时间的5%之内。

调整VM堆大小
JVM堆大小决定了虚拟机收集垃圾的频率和时间长短。
对于特定的程序,应分析实际情况后进行调整。
较大的堆,GC速度慢,GC频率低
如果根据内存需求设置堆大小,则完整垃圾回收速度会更快,但会更频繁地发生。

JVM有四种类型的GC实现:

  • 串行垃圾收集器
  • 并行垃圾收集器
  • CMS垃圾收集器
  • G1垃圾收集器

串行垃圾收集器
使用一个线程进行垃圾回收,执行时会冻结所有的应用线程,不适合多线程应用。
适合不要求低暂停时间和单机程序。

并行垃圾收集器
对年轻代的回收采用并行方式(多个线程) ,对老年代的回收还是单线程
它是JVM的默认GC,有时也称为吞吐量收集器。 但它在执行GC时也会冻结其他应用程序线程。

CMS垃圾收集器
利用多垃圾回收线程,适用于短回收暂停,且能够在应用程序运行时与垃圾收集器共享处理器资源。
应用程序平均响应较慢,但不会停止响应以执行垃圾收集。

G1垃圾收集器
适用于运行在多处理器大内存空间的应用程序。

▪ Java性能调优工具:jstat, jmap, jhat, Visual VM, MAT
jstat
获取JVM的heap使用和GC的性能统计数据
jstattool显示已检测的HotSpot Java虚拟机(JVM)的性能统计信息,重点关注内存性能,如堆使用情况和垃圾回收(GC)。

jmap
输出内存中的 对象分布情况

jhat
导出heap dump,浏览/查询其中的对象分布情况

jstack
获取Java线程的stack trace

jps (JVM Process Status Tool) 虚拟机进程状况工具
列出当前运行的JVM 进程

  • jps命令使用java启动程序查找传递给main方法的类名和参数。

VisualVM是一种工具,它提供了一个可视化界面,用于查看有关Java应用程序在JVM上运行时的详细信息
它使用各种技术,包括jvmstat,JMX,Serviceability Agent(SA)和Attach API。

功能
显示本地和远程Java进程
显示流程配置和环境
监控流程性能和内存

  • 应用程序CPU使用率,GC活动,堆和元空间/永久生成内存,已加载类的数量和正在运行的线程。
    可视化流程线程
  • 在Java进程中运行的所有线程都会在时间轴中显示,同时显示聚合的
    Running,Sleeping,Wait,Park和Monitor时间。
    配置文件性能和内存使用
  • 提供采样和仪表分析仪。
    获取并显示线程转储
  • 帮助发现分布式死锁。
    获取并浏览堆转储
  • 帮助发现低效的堆使用情况并调试内存泄漏。
    在线和离线分析核心转储
  • 从核心转储中读取有关崩溃的Java进程及其环境的基本信息,以及提取和打开包含的线程和堆转储。

内存分析器(MAT)
Eclipse Memory Analyzer是一个快速且功能丰富的Java堆分析器,可帮助您查找内存泄漏并减少内存消耗。
使用Memory Analyzer分析具有数亿个对象的高效堆转储,快速计算对象的保留大小,查看谁阻止垃圾收集器收集对象,运行报告以自动提取泄漏嫌疑人。

▪ Java代码调优的设计模式:singleton, prototype/cloneable, flyweight, pool
Singleton Pattern单例模式
某些类在概念上只有一个实例
只创建一个对象,然后复用
对管理重用的代码进行封装
意图:确保类只有一个实例,并提供一个全局访问点
适用性:
只能有一个类的实例,并且客户端可以从众所周知的访问点访问它
当唯一的实例可以通过子类扩展时,客户端应该能够使用扩展实例而不修改它们的代码

好处
对唯一实例的受控访问
缩小命名空间(对全局变量的一种改进)
类通过封装确保对象的复用,不用让客户端考虑
节省创建时间
降低内存消耗
对于系统的核心组件和经常使用的对象,使用单例模式可以有效地提高系统性能。

Flyweight Pattern享元模式
享元模式描述了 如何共享对象,有效支持细粒度的使用而不会产生过高的成本
flyweight是一个共享对象,可在多个情境下同时使用
内在状态:不变的状态,可以共享
外在状态:状态是不固定的,使用时需要计算,不可共享
声明一个接口,flyweights可以通过该接口接收外部状态并对其进行操作

适用性:
应用程序使用了大量的对象,由于对象的数量庞大,存储成本很高
对象的大多数状态是外部状态,一旦删除了外部状态,可以用相对较少的共享对象替代很多组对象
应用程序不依赖于对象标识

Prototype Pattern 原型模式
通过复制已有原型对象新创建对象
目标:创建或初始化对象代价高时,可通过此模式创建相似对象,降低开销
类似于文档模板,创建一次,多次被复制使用,作为撰写文档的起点。
原型模式在初始化创建第一个对象时开销大,然后将这些值作为原型存储在存储库中。
需要再次创建相同或类似对象时,只需从存储库中获取所有值已经预填充的原型副本。

适用性:
当一个系统应该独立于它的产品创建、构成和表示时
当要实例化的类是在运行时刻指定时
为了避免创建一个与产品类层次平行的工厂类层次时
当一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型并克隆它们,可能比每次用合适的状态手工实例化该类更方便一些。

Object Pool Pattern 对象池模式
对象池模式
▪对象池模式是一种软件创建设计模式,它使用一组准备使用的初始化对象 - “池” - 而不是按需分配和销毁它们。
▪例如,数据库连接池,打印机池
▪池的客户端将从池中请求对象并对返回的对象执行操作。
▪客户端完成后,将对象返回池而不是销毁它; 这可以手动或自动完成。

▪ 进程和线程
并发模块本身主要分为两种类型:进程和线程
进程是正在运行程序的一个实例,拥有自己私有专用的内存空间
线程是正在运行程序的一个执行路径(一个进程可对应多个线程),线程有自己的堆栈和局部变量,但是多个线程共享内存空间

进程可抽象为虚拟计算机,拥有独立的执行环境和完整的资源
进程间通常不共享内存
进程不能访问其他进程的内存或对象
需要特殊机制才能实现进程间共享内存
进程通信采用的是消息传递方式(因采用标准I/O 流)
进程通常被视为程序或应用程序的同义词,应用程序实际上可能是一组协作进程
为了实现进程间通信,大多数操作系统都支持”进程间通信(IPC)资源”,例如pipe和socket。
Java虚拟机本身的大多数实现都是作为单个进程运行的

线程可抽象为一个虚拟处理器,线程有时也称为轻量级进程
线程与进程中的其他线程共享相同的资源( 内存,打开的文件等),即“线程存在于进程内”
进程内的线程共享内存,线程需要特殊处理才能实现消息传递和访问私有内存

线程与进程
线程轻量级
进程重量级

线程共享地址空间
进程有自己的

线程需要同步
进程不需要
线程在改变对象时保持锁定

杀死线程是不安全的
安全进程是安全的

▪ 线程的创建和启动,runnable
每个应用程序至少有一个线程
站在程序员角度,main线程是开始线程,可以通过它创建其他的线程
启动线程的方法:
创建Thread类的子类
实现Runnable接口,作为参数传递给 new Thread(…)构造函数

所有的线程都需要实现Runnable接口,并实现run()方法
Runnable接口表示线程要完成的工作。

如何创建一个线程
▪方法1:子类线程

  • Thread类本身实现了Runnable,尽管它的run方法什么都不做。 应用程序可以子类化Thread,提供自己的run()实现。
    ▪调用Thread.start()以启动新线程。

▪方法2:提供Runnableobject

  • Runnable接口定义了一个方法run(),意味着包含在线程中执行的代码。 Runnable对象被传递给Thread构造函数。
    ▪调用Thread.start()以启动新线程。
    惯用法:用一个匿名的Runnable启动一个线程,它避免了创建命名的类

▪ 时间分片、交错执行、竞争条件
时间分片
在某时刻,一个运行核心上只有一个线程可以运行
进程/线程等采用OS提供的时间片机制共享处理时间
当线程数多于处理器数量时,并发通过时间片来模拟,处理器切换处理不同的线程。
时间片的使用是不可预知和非确定性的,这意味着线程可能随时暂停或恢复。

Interleaving 交错交叉/交错
低层的指令间存在交叉

Race Condition 竞争条件
竞争条件:程序的正确性(后置条件和不变性的满足)取决于并发计算A和B中事件的相对时间
在有些情况下会导致违反后置条件和不变性

▪ 线程的休眠、中断
▪ 线程安全的四种策略****
限制可变变量的共享
通过将数据限制在单个线程中,可以避免线程在可变数据上进行竞争
局部变量保存在线程栈中,每个调用都有自己的变量副本
局部变量如果是对象的引用,则要确保不能引用任何其他线程可访问的对象(针对可变对象)
在多线程环境中,取消全局变量

用不可变的共享变量
不可变解决了因为共享可变数据造成的竞争,并简单地通过使共享数据不可变来解决它
final变量不允许再赋值,所以声明为final的变量可以安全地从多个线程访问。
因为这种安全性只适用于变量本身,仍然必须确保变量指向的对象是不可变的。

将共享数据封装在线程安全的数据类型中
StringBuffer 和 StringBuilder都是可变数据类型,功能基本相同
StringBuffer是线程安全的,StringBuilder不是
StringBuilder性能更好,推荐在单线程程序中使用

使用同步机制来防止线程同时使用变量
并发模块彼此间采用同步的方式共享内存,以解决竞争带来的问题
锁是一种抽象,某时刻最多只允许一个线程拥有锁
获取锁的拥有权,如果锁被其他线程拥有,将进入阻塞状态,等待锁释放后,再同其他线程竞争获取锁的拥有权,锁拥有者释放锁的拥有权

同步语句必须指定提供内部锁的对象
同步区域提供了互斥功能: 一次只能有一个线程处于由给定对象的锁保护的同步区域中
锁定时,遵循顺序执行模式

▪ 死锁
由于使用锁需要线程等待(当另一个线程持有锁时),因此可能会陷入两个线程都在等待对方的情况 – 此时都无法继续
死锁描述了两个或更多线程永远被阻塞的情况,都在等待对方
死锁可能涉及两个以上的模块:线程间的依赖关系环是出现死锁的信号
在这里插入图片描述
防止死锁的一种方法是对需要同时获取的锁进行排序,并确保所有代码按照该顺序获取锁定

▪ Message passing
Message passing模型用于在客户端和服务器端的进程间通过sockets传递消息
在同一进程的线程间通过message passing传递消息,比通过锁定机制共享内存更受欢迎
使用同步队列在线程之间传递消息

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值