
目录
Java虚拟机内存管理
1.JVM整体架构
2.JVM运行时内存
JVM加载机制
1.类装载子系统
1.1 类加载子系统介绍
1.2 类加载器ClassLoader角色
1.3 类加载的执行过程
2.类加载器
2.1 类加载器的作用
2.2 类加载器分类
3.双亲委派模型
3.1 什么是双亲委派
3.2 如何实现双亲委派模型
4. 自定义加类加载器
4.1 为什么要自定义类加载器
4.2 自定义函数调用过程
垃圾回收机制及算法
1.垃圾回收概述
2.垃圾回收-对象是否已死
2.1 判断对象是否存活 - 引用计数算法
2.2 判断对象是否存活-可达性分析算法
3.垃圾收集算法
3.1 分代收集理论
3.2 标记-清除算法
3.3 标记-复制算法
3.4 标记-整理算法
4.垃圾收集器
4.1垃圾收集器概述
根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分。从线程私有:程序计数器、虚拟机栈、本地方法栈
线程共享:元空间(MetaSpace)、Java堆
JVM分为五大模块: 类装载器子系统 、 运行时数据区 、 执行引擎 、 本地方法接口 和 垃圾收集模块 。
Java 虚拟机有自动内存管理机制,如果出现面的问题,排查错误就必须要了解虚拟机是怎样使用内存的。
Java7和Java8内存结构的不同主要体现在方法区的实现
方法区是java虚拟机规范中定义的一种概念上的区域,不同的厂商可以对虚拟机进行不同的实现。我们通常使用的Java SE都是由Sun JDK和OpenJDK所提供,这也是应用最广泛的版本。而该版本使用的VM就是HotSpot VM。通常情况下,我们所讲的java虚拟机指的就是HotSpot的版本。
JDK8虚拟机内存详解:
JDK7和JDK8变化
对于Java8,HotSpots取消了永久代,那么是不是就没有方法区了呢?
当然不是,方法区只是一个规范,只不过它的实现变了。
在Java8中,元空间(Metaspace)登上舞台,方法区存在于元空间(Metaspace)。同时,元空间不再与堆连续,而且是存在于本地内存(Native memory)。
方法区Java8之后的变化
Java8为什么要将永久代替换成Metaspace?
类使用的7个阶段
类从被加载到虚拟机内存中开始,到卸载出内存,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initiallization)、使用(Using)和卸载(Unloading)这7个阶段。其中验证、准备、解析3个部分统称为连接(Linking),这七个阶段的发生顺序如下图:
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。
双亲委派模型的原理很简单,实现也简单。每次通过先委托父类加载器加载,当父类加载器无法加载时,再自己加载。其实 ClassLoader 类默认的 loadClass 方法已经帮我们写好了,我们无需去写。
4.3 自定义类加载器实现实现方式:
所有用户自定义类加载器都应该继承ClassLoader类
在自定义ClassLoader的子类是,我们通常有两种做法:
java垃圾回收的优缺点:
优点:
缺点:
引用计数算法可以这样实现:给每个创建的对象添加一个引用计数器,每当此对象被某个地方引用时,计数值+1,引用失效时-1,所以当计数值为0时表示对象已经不能被使用。引用计数算法大多数情况下是个比较不错的算法,简单直接,也有一些著名的应用案例但是对于Java虚拟机来说,并不是一个好的选择,因为它很难解决对象直接相互循环引用的问题。
优点:
实现简单,执行效率高,很好的和程序交织。
缺点:
无法检测出循环引用。
可达性分析算法
在主流的商用程序语言如Java、C#等的主流实现中,都是通过可达性分析(Reachability Analysis)来判断对象是否存活的。此算法的基本思路就是通过一系列的“GC Roots”的对象作为起始点,从起始点开始向下搜索到对象的路径。搜索所经过的路径称为引用链(Reference Chain),当一个对象到任何GC Roots都没有引用链时,则表明对象“不可达”,即该对象是不可用的。
思想也很简单,就是根据对象的生命周期将内存划分,然后进行分区管理。 当前商业虚拟机的垃圾收集器, 大多数都遵循了“分代收集”(Generational Collection)的理论进 行设计, 分代收集名为理论, 实质是一套符合大多数程序运行实际情况的经验法则
最早出现也是最基础的垃圾收集算法是“标记-清除”(Mark-Sweep) 算法, 在1960年由Lisp之父 John McCarthy所提出。 如它的名字一样, 算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回 收的对象, 在标记完成后,统一回收掉所有被标记的对象, 也可以反过来, 标记存活的对象, 统一回 收所有未被标记的对象。
标记过程就是对象是否属于垃圾的判定过程, 这在前一节讲述垃圾对象标记 判定算法时其实已经介绍过了。 之所以说它是最基础的收集算法, 是因为后续的收集算法大多都是以标记-清除算法为基础, 对其 缺点进行改进而得到的。
标记-清除算法有两个不足之处:
第一个是执行效率不稳定, 如果Java堆中包含大量对 象, 而且其中大部分是需要被回收的, 这时必须进行大量标记和清除的动作, 导致标记和清除两个过 程的执行效率都随对象数量增长而降低;
第二个是内存空间的碎片化问题, 标记、 清除之后会产生大 量不连续的内存碎片, 空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找 到足够的连续内存而不得不提前触发另一次垃圾收集动作。
为了解决标记-清除算法面对大量可回收对象时执行效率低的问题, 1969年Fenichel提出了一种称为“半区复制”(Semispace Copying) 的垃圾收集算法, 它将可用 内存按容量划分为大小相等的两块, 每次只使用其中的一块。 当这一块的内存用完了, 就将还存活着 的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。 如果内存中多数对象都是存活的, 这种算法将会产生大量的内存间复制的开销, 但对于多数对象都是可回收的情况, 算法需要复制的就是占少数的存活对象, 而且每次都是针对整个半区进行内存回收, 分配内存时也就不用考虑有空间碎片的复杂情况, 只要移动堆顶指针, 按顺序分配即可.
但是这种算法也有缺点:
标记-复制算法在对象存活率较高时就要进行较多的复制操作, 效率将会降低。 更关键的是, 如果不想浪费50%的空间, 就需要有额外的空间进行分配担保, 以应对被使用的内存中所有对象都100%存活的极端情况, 所以在老年代一般不能直接选用这种算法。
针对老年代对象的存亡特征, 1974年Edward Lueders提出了另外一种有针对性的“标记-整 理”(Mark-Compact) 算法, 其中的标记过程仍然与“标记-清除”算法一样, 但后续步骤不是直接对可回收对象进行清理, 而是让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存 。
标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法, 而后者是移动式的。 是否移动回收后的存活对象是一项优缺点并存的风险决策:
是否移动对象都存在弊端, 移动则内存回收时会更复杂, 不移动则内存分配时会更复杂。 从垃圾收集的停顿时间来看, 不移动对象停顿时间会更短, 甚至可以不需要停顿, 但是从整个程序的吞吐量来看, 移动对象会更划算。
1.垃圾回收器与垃圾回收算法
垃圾回收算法分类两类,第一类算法判断对象生死算法,如引用计数法、可达性分析算法 ;第二类收集死亡对象方法有四种,如标记-清除算法、标记-复制算法、标记-整理算法。一般的实现采用分代回收算法,根据不同代的特点应用不同的算法。垃圾回收算法是内存回收的方法论。垃圾收集器是算法的落地实现。和回收算法一样,目前还没有出现完美的收集器,而是要根据具体的应用场景选择最合适的收集器,进行分代收集。
2.垃圾收集器分类
串行垃圾回收(Serial)
串行垃圾回收是为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,不适合交互性强的服务器环境
并行垃圾回收(Parallel)
多个垃圾收集器线程并行工作,同样会暂停用户线程,适用于科学计算、大数据后台处理等多交互场景。
并发垃圾回收(CMS)
用户线程和垃圾回收线程同时执行,不一定是并行的,可能是交替执行,可能一边垃圾回收,一边运行应用线程,不需要停顿用户线程,互联网应用程序中经常使用,适用对响应时间有要求的场景。
G1垃圾回收
G1垃圾回收器将堆内存分割成不同的区域然后并发地对其进行垃圾回收。
3.七种垃圾收集器及其组合关系
根据分代思想,我们有7种主流的垃圾回收器
新生代垃圾收集器:Serial 、 ParNew 、Parallel Scavenge
老年代垃圾收集器:Serial Old 、 Parallel Old 、CMS
整理收集器:G1
垃圾收集器的组合关系
JDK8中默认使用组合是: Parallel Scavenge GC 、ParallelOld GC
JDK9默认是用G1为垃圾收集器
JDK14 弃用了: Parallel Scavenge GC 、Parallel OldGC JDK14 移除了 CMS GC
4.GC性能指标
吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
暂停时间:执行垃圾回收时,程序的工作线程被暂停的时间
内存占用:java堆所占内存的大小
收集频率:垃圾收集的频次