Java中提倡的自动内存管理最终可以归结为自动化的解决两个问题:

  1. 给对象分配内存
  2. 回收分配给对象的内存

先说说回收这一方面的两个主要知识点

一。垃圾收集算法

1.标记-清理算法

  首先标记出所有需要回收的对象,然后在标记完成后统一回收所有被标记的对象(适用老年代)

  两个缺陷:(1)效率问题,标记和清除两个过程效率都不高

       (2)空间问题,标记清除算法会产生大量不连续的内存空间碎片,导致无法分配较大对象

2.复制算法

  将可用内存按容量划分为等大小的两块,每次只使用其中的一块。清理时将还存活着的对象复制到另一块中,然后把已使用过的内存空间一次清理掉。这样每次都是对整个半区进行回收。商业虚拟机中常用复制算收集新生代对象,但这种算法的代价内存使用率过低,为此对算法进行改进。

  将内存空间划分为8:1:1的三个内存区间(Hotspot虚拟机中默认比例),其中占8比率的是Eden区,另外两个是Survior区,每次使用Eden区和其中一个Survior区;清理时,将Eden区和Survior1区中还存活着的对象复制到Survior2区中,然后清理掉Eden区和Survior1区。此外,这里在复制转移对象时,当Survior2区的空间不够用时,会需要依赖其他内存(老年代)进行分配担保。

3.标记-整理算法

  首先标记处所有需要回收的对象,然后让所有存活的对象向一端移动,最后直接清理掉端边界以外的内存。(针对老年代特点)

4.分代收集算法

    根据对象存活周期的不同将内存划分为几块。一般分为新生代(年轻代)和老年代(年老代),然后根据各个年代特点采用适当的收集算法。

二。垃圾收集器

  书中提到了总共7种不同的垃圾收集器,其中3种适用于年轻代,3种适用于年老代。此外,G1收集器为两者通用

  (一)年轻代中的3种垃圾收集器(都是使用复制算法?):

  1.Serial收集器

    单线程的收集器(单线程,且在垃圾收集时必须暂停其他所有的工作线程,即回收停顿)。Client 场景下的默认新生代收集器。

     这种收集器简单高效,无线程交互开销,因此拥有最高的单线程收集效率;可以配合CMS或Serial Old使用

  2.ParNew收集器

     Serial收集器的多线程版本。Server模式下的虚拟机中首选的新生代收集器(因为只有Serial和ParNew能与CMS收集器配合)

     默认开启的收集线程数与CPU的数量相同;可以配合CMS或Serial Old使用

  3.Parallel Scavenge收集器

     多线程收集器。与其他收集器目的不同,该收集器的目标是达到一个可控制的吞吐量(Throughput).

     吞吐量即CPU运行用户代码的时间和CPU总消耗时间的比值。可以配合Parallel Old或Serial Old使用

  (二)年老代中的三种垃圾收集器:

  1.Serial Old收集器

     Serial收集器的老年代版本,单线程收集器,使用标记-整理算法

  2.Parallel Old收集器

     Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法

  3.CMS(Concurrent Markup Sweap)收集器

    是一种以获取最短回收停顿时间为目标的收集器。是基于标记-清理算法的。

    运行分为四个步骤:

      1.初始标记:标记 GC Roots 能直接关联到的对象,需要回收停顿(即此时为单线程模式)

      2.并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿

      3.重新标记:需要回收停顿。此阶段是为了修正并发标记期间因用户程序继续运作而导致的标记变化部分

      4.并发清除

    CMS有三个明显的缺点:

      1.对CPU资源非常敏感。CMS默认启动的回收线程数量数为 (CPU数量+3)/4;即当CPU数量较少时,会分出较多比率的运算能力去执行多线程,即会导致吞吐量过低

      2.无法处理浮动垃圾。即并发清理过程中用户线程运作产生的垃圾。当超过启动阈值时(JDK1.6,阈值为92%),会出现"Concurrent Model Failure"而导致另一次Full GC产生。

      3.收集结束时会产生大量不连续的内存空间碎片。因为CMS是基于 标记-清除 算法的垃圾收集器。

      

          G1(Garbage-First)收集器

      G1 可以直接对新生代和老年代一起回收。G1收集器中,它将整个Java堆划分成多个大小相等的独立区域(Region),此时新生代和老年代不在是物理隔离的了,都是一部分Region的集合。

      通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。从而可以有计划的在Java堆中进行全区域的垃圾收集,进而能够建立可预测的停顿时间模型。G1通过跟踪各个Region里垃圾堆的价值大小,在后台维护一个优先列表,每次优先回收价值最大的Region。通过使用Region划分内存空间和优先级的区域回收方式,保证G1收集器在有效时间内获得尽可能高的收集效率。此外,G1中的每个Region都有一个与之对应的Remembered Set,用于记录其他Region以及其他收集器中对该Region中对象的引用,以避免虚拟机在做可达性分析的时避免全堆扫描。

      不计算维护Remembered Set的操作,大致步骤如下:

      1.初始标记:需要停顿

      2.并发标记

      3.最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。需要停顿,但可以(多个最终标记线程)并发执行

      4.筛选标记:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。可与用户程序一起并发执行,但停顿用户线程将大幅度提高收集效率。

      G1收集器的四个特点:

      1.并发与并行:通过利用多个CPU来缩短回收停顿时间,需要停顿的GC动作,也可通过并发的方式让Java程序继续执行

      2.分代收集:分代概念依然保留,无需其他收集器配合即可采用不同方式处理不同的生存周期对象

      3.空间整合:整体上看是基于 标记-整理 算法,局部上(两个Region)看是基于 复制 算法实现的。不会产生不连续的内存空间碎片

      4.可预测的停顿:除追求低停顿外,能建立可预测的停顿时间模型,并能让使用者明确指定一个长度为M毫秒的时间片段。

再说说Java中如何自动化解决这两个问题

三。内存分配和回收策略

  1.内存分配

    (1)对象优先在Eden区分配

      大多数情况下,对象在新生代Eden区分配,当Eden区内存空间不够时,将触发Minor GC

    (2)大对象直接进入老年代

      大对象指需要连续内存空间的对象,如很长的字符串和数组;经常出现大对象会提前触发垃圾收集以获取足够长的连续内存空间放置大对象,因此可以通过设置参数来让超过一定长度的大对象直接进入老年代。

    (3)长期存活的对象进入老年代

      为每个对象定义一个单独的年龄计数器,每当该对象经过一次Minor GC之后存活,从Eden区(或survivor1区)复制到Survivor2区,则年龄+1;当对象的年龄增长到一定程度则进入老年代(默认年龄大于15的进入老年代)。

    (4)动态判断对象年龄,满足一定条件进入老年代

      JVM并不是一定要年龄到达一定程度才能进入老年代。当Survivor区中,某一年龄段的所有对象的大小和超过Survivor区的一半时,该年龄的所有对象都直接进入老年代。

  2.回收策略

    Minor GC:回收新生代。由于新生代对象存活时间短,因此Minor GC会频繁执行,执行效率一般较快。  

    Full GC:回收老年代和新生代。由于老年代存活时间较长,因此Full GC很少执行,执行效率也比较慢。

    空间分配担保:

      进行Minor GC前需要先检查老年代的最大可用空间是否大于新生代所有对象的总空间,如果满足,则认为Minor GC是安全的。

      如果不满足,则虚拟机会查看是否允许担保;如果允许,就会查看老年代最大可用连续内存空间是否大于以前每次晋升到老年代的对象的平均大小,大于,则开始Minor GC;如果不允许或小于,则触发一次Full GC

      以HotSpot虚拟机为例,在Minor GC进行时,会将Eden区和Survivor1区的对象复制到Survivor2区中,若此时Survivor2区中的内存空间不足以放下所有的对象,此时会将多余的对象暂时存放在老年代区域。如果此时老年代区域不足以放置剩余对象,则会发生错误,并提前触发Full GC。

四。类加载

  1.类加载的流程

    类的生命周期为以下7个阶段

      加载--->验证--->准备--->解析--->初始化--->使用--->卸载

    其中类加载过程为以下5个阶段

      加载--->验证--->准备--->解析--->初始化

    加载:加载主要分为三个小步骤:

      (1)根据类的全限定路径获取定义该类的二进制字节流

      (2)将字节流的静态存储结构转换为方法区的运行时存储结构

      (3)生成类的class对象,当作方法区中各种数据操作的入口

      此外,获取二进制字节流的方式共有四种。

      (1)Zip包中读取,如Jar包等

      (2)网络中获取,如Applet

      (3)运行时生成,如动态代理技术,在 java.lang.reflect.Proxy中

      (4)由其他文件生成,如Jsp文件生成对应的Class类

     验证:检验字节流是否符合虚拟机规范,且是否会危及虚拟机的安全问题

    准备:对类变量(static修饰的)进行初始赋值,使用的是方法区内存。但这里的初始赋值并不是根据代码进行初始化(除非变量使用final修饰).如 static int i = 23中的i会被初始化为0,但 static final int i = 23 会初始化为23

    解析:将常量池中的符号引用替换为直接引用。这一步也可以发生在初始化阶段之后

    初始化:执行Java代码,根据Java代码对类进行初始化。

        父类静态变量->父类静态代码块->子类静态变量->子类静态代码块->父类成员变量->父类成员代码块->父类构造函数->子类成员变量->子类成员代码块->子类构造函数

  2.类加载器

    启动类加载器,对应加载JRE_HOME目录中的文件

    扩展类加载器,对应于系统变量路径

    应用程序类加载器,对应于用户类路径

 

    双亲委派模型:要求除了启动类加载器之外,其他的加载器都要有自己的父类加载器。这里的父子关系通过组合方式获得,而不是继承方式。

           启动类加载器 --- 扩展类加载器 --- 应用程序类加载器 --- 用户自定义类记载器

    工作流程:双亲委派模型中,子类加载器获得请求后,会将请求转发给其父类加载器(一层一层往上传,直到启动类加载器),只有当父类加载器无法加载完成时,子类加载器才会尝试加载

    优点:使得Java类随着它的类加载器一起具有一种带有优先级的关系,从而使得基础类得到统一。

深入理解Java虚拟机阅读心得(三)的更多相关文章

  1. 深入理解Java虚拟机阅读心得(一)

    JVM(Java Virtual Machine) 即Java虚拟机,是一种用于计算设备的规范,用于运行Java程序编译后得到的字节码文件(Class文件) 一.JVM的内存区域 1.程序计数器(Pr ...

  2. 深入理解Java虚拟机阅读心得(二)

    垃圾收集 程序计数器.虚拟机栈.本地方法栈三个区域随线程而生,随线程而灭:这几个区域的内存分配和回收都具备稳定性,不需要过多的考虑回收的问题.而Java堆和方法区则不一样. Java堆中存储了几乎所有 ...

  3. 深入理解Java虚拟机--阅读笔记三

    垃圾收集器 手机算法是内存回收的方法论,垃圾收集器是内存回收的具体实现. 并行:指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态 并发:值用户线程与垃圾收集线程同时执行(但并不一定是并行的) ...

  4. 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略

    垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...

  5. 《深入理解java虚拟机》第三章 垃圾收集器与内存分配策略

    第三章 垃圾收集器与内存分配策略 3.1 概述 哪些内存需要回收 何时回收 如何回收 程序计数器.虚拟机栈.本地方法栈3个区域随线程而生灭. java堆和方法区的内存需要回收.   3.2 对象已死吗 ...

  6. 深入理解java虚拟机_第三章(上)----->垃圾收集器与内存分配策略

    1.  前言 这一版块内容比较多,分为两篇文章来做笔记.本文讲述上半部分垃圾收集部分;下一篇文章写内存分配部分. 概述 对象已死吗? 引用技术算法 可达性分析算法 再谈引用 两次标记 回收方法区 2. ...

  7. 深入理解Java虚拟机--阅读笔记二

    垃圾收集器与内存分配策略 一.判断对象是否已死 1.垃圾收集器在对堆进行回收前,要先判断对象是否已死.而判断的算法有引用计数算法和可达性分析算法: 2.引用计数算法是给对象添加引用计数器,有地方引用就 ...

  8. 深入理解Java虚拟机--阅读笔记一

    Java内存区域 一.java运行时数据区域 1. 程序计数器:程序计数器占据的内存空间较小,是当前运行线程执行的字节码的计数:分支.循环.跳转.异常处理.线程恢复等都要依赖技术器来对执行的字节码进行 ...

  9. 深入理解JAVA虚拟机阅读笔记4——虚拟机类加载机制

    虚拟机把描述类的Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 在Java语言中,类型的加载.连接和初始化过程都是 ...

随机推荐

  1. Paper | 学习多任务中的最佳分/ 合结构(十字绣结构)

    目录 1. 问题 2. 十字绣结构(Cross-stitch architecture) 3. 实验设计 论文:Cross-stitch Networks for Multi-task Learnin ...

  2. Mysql相关存储函数,函数,游标

    https://www.cnblogs.com/zhanglei93/p/6437867.html >>>>请进入

  3. DB2日常维护常用命令

    1.检查是否有僵尸进程 ps -emo THREAD | grep -i Z | grep -i 实例名 2.处理死锁  --第一步:查看所有死锁  db2 get snapshot for lock ...

  4. 使用 VSTS 进行 CI 的过程中,无法识别 .NET Core 2.x 的情况处理

    大概是由于 .NET Core 2.1 还没有正式发布,使用 VSTS 进行持续集成(CI)的过程中,自动 Build 的环节无法识别 .NET Core 2.1 的框架,查看日志会提示如下错误: V ...

  5. Windows环境下MySQL 5.6安装与配置

    1将MySQL压缩包解压到自定义目录下. 2.添加环境变量 右键单击我的电脑->属性->高级系统设置(高级)->环境变量. 点击系统变量下的新建按钮 1)    输入变量名:MYSQ ...

  6. wordpess设置回复可见

    easy2hide 是一个不错的隐藏部分内容,评论后可见的插件,可在插件安装后台搜索 easy2hide 在线安装,或者在此下载 easy2hide. 在编辑文章的时候,切换到html文本编辑模式 测 ...

  7. 吴恩达机器学习笔记4-代价函数III(cost function)

    这是代价函数的样子,等高线图,则可以看出在三维空间中存在一个使得

  8. Oracle SQL优化器简介

    目录 一.Oracle的优化器 1.1 优化器简介 1.2 SQL执行过程 二.优化器优化方式 2.1 优化器的优化方式 2.2 基于规则的优化器 2.3 基于成本的优化器 三.优化器优化模式 3.1 ...

  9. .Net Core新建解决方案,添加项目引用,使用VSCode调试

    并不是我自己琢磨的,是看了别人学习的,因为写的都不完整,所以就整理一下记录后面忘了回看. 反正.Net Core是跨平台的,就不说在什么系统上了.假设我要建一个名为Doggie的解决方案,里面包含了一 ...

  10. 【Spark调优】内存模型与参数调优

    [Spark内存模型] Spark在一个executor中的内存分为3块:storage内存.execution内存.other内存. 1. storage内存:存储broadcast,cache,p ...