1、类加载子系统
(1)加载阶段
会将.java文件动态编译为.class文件,然后通过类的全限定名,将类的二进制字节流的静态存储结构转化为方法区里的动态数据结构,在内存中生成一个代表该类的java.lang.Class,并提供数据访问入口。
(2)连接阶段
验证:主要确保加载类的正确性
准备:为类分配内存,并且为其静态变量赋默认值
解析:将常量池中的符号引用替换为直接引用(内存地址)的过程。
(符号应用指的是:通过符号来表示描述目标,符号可以是任何形式的字面量, 只要使用时能够无歧义的定位到目标即可.)
(3)初始化阶段:为类的静态变量赋初值
(4)使用阶段
(5)卸载阶段
当加载一个类,首先查看自己的自定义加载器是否有这个内存,如果没有,自定义加载器并不是马上加载,而是会去他的父亲方向的加载器去查看是否已经加载到内存中。当还没有加载时,继续采用同样的机制,往上查找,当发现最顶层的启动类即Bootstrap的内存中仍然没有加载时,则查看自己能不能加载这个类,如果能加载则加载,如果不能加载时,则会往下,让其子加载器进行加载。到了最后自定义类加载器,如果自定义加载器仍然不能加载,则会抛出ClassNotFoundException。
使用双亲委派机制的好处:
节省内存
主要是为了安全,避免用户自己编写的类动态替换 Java的一些核心类,比如 String
避免了类的重复加载
打破双亲委派机制:https://blog.csdn.net/m0_37556444/article/details/81908685
使用类加载调用loadClass方法即可。
自定义ClassLoader重写findClass即可(模板方法模式)
extends ClassLoader
overwrite findClass() -> defineClass(byte[] -> Class clazz)
3.打破双亲委派机制,重写loadClass
JDK1.2之前,自定义ClassLoader都必须重写loadClass()
ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
热启动,热部署
tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)
2、PC程序计数器:记录指令存放位置
3、虚拟机栈:虚拟机栈是针对线程而言,线程里的方法对应栈中的元素--栈帧。
(1) 局部变量表
(2)操作数栈
(3)返回地址
(4)动态链接
4、方法区:
Perm Space (<1.8)
字符串常量位于PermSpace
FGC不会清理
大小启动的时候指定,不能变
Meta Space (>=1.8)
字符串常量位于堆
会触发FGC清理
不设定的话,最大就是物理内存
如何证明1.7字符串常量位于Perm,而1.8位于Heap?
提示:结合GC, 一直创建字符串常量,观察堆,和Metaspace
5、垃圾回收器算法
(1)如何找到垃圾
引用计数
该方法实现为:给每个对象添加一个引用计数器,每当有一个地方引用它时,引用计数值就+1,当引用失效时,引用计数值就-1,任何时刻引用计数值为0的对象就可以被回收,当一个对象被垃圾收集器收集时,被它引用的对象引用计数值就-1,所以在这种方法中一个对象被垃圾收集会导致后续其他对象的垃圾收集行动。
优点:简单、高效;
缺点:当两个对象相互引用的时候就无法回收,导致内存泄漏。
无法解决循环引用问题。(实际上没有任何对象引用,但其结果不为0)
2.根可达算法
为了解决对象间相互引用问题,Java 采用了可达性分析法,基本实现思路为:通过一系列 "GC Roots" 对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到 "GC Roots" 没有任何引用链相连时,则称该对象是不可达的,此时,该对象还处于缓刑阶段,要真正宣判一个对象为可回收对象,至少要经历两次标记过程。
哪些对象可以作为 "GC Roots" ?
(1)通过System Class Loader或者Boot Class Loader加载的class对象
(2)处于激活状态的线程
(3)栈中的对象
(4)JNI栈中的对象
(5)JNI中的全局对象
(6)正在被用于同步的各种锁对象
(7)JVM自身持有的对象,比如系统类加载器等
(2)常用的垃圾回收器算法
标记清除
它是最基础的收集算法。
原理:分为标记和清除两个阶段:首先标记出所有的需要回收的对象,在标记完成以后统一回收所有被标记的对象。
特点:(1)效率问题,标记和清除的效率都不高;(2)空间的问题,标记清除以后会产生大量不连续的空间碎片,空间碎片太多可能会导致程序运行过程需要分配较大的对象时候,无法找到足够连续内存而不得不提前触发一次垃圾收集。
地方 :适合在老年代进行垃圾回收,比如CMS收集器就是采用该算法进行回收的。
2、拷贝算法
原理:它先将可用的内存按容量划分为大小相同的两块,每次只是用其中的一块。当这块内存用完了,就将还存活着的对象复制到另一块上面,然后把已经使用过的内存空间一次清理掉。
特点:没有内存碎片,只要移动堆顶指针,按顺序分配内存即可。代价是将内存缩小位原来的一半。
地方:适合新生代区进行垃圾回收。serial new,parallel new和parallel scanvage
收集器,就是采用该算法进行回收的。
复制算法改进思路:由于新生代都是朝生夕死的,所以不需要1:1划分内存空间,可以将内存划分为一块较大的Eden和两块较小的Suvivor空间。每次使用Eden和其中一块Survivor。当回收的时候,将Eden和Survivor中还活着的对象一次性地复制到另一块Survivor空间上,最后清理掉Eden和刚才使用过的Suevivor空间。其中Eden和Suevivor的大小比例是8:1。缺点是需要老年代进行分配担保,如果第二块的Survovor空间不够的时候,需要对老年代进行垃圾回收,然后存储新生代的对象,这些新生代当然会直接进入来老年代。
3.标记整理
原理:. 当存活的实例过多时,如果复制的话,效率不高,不合适老生代。 这时,把存活的对象标记出来,全部移动到一端,然后对另外一部分进行清除。
特点:不会产生空间碎片,但是整理会花一定的时间。
地方:适合老年代进行垃圾收集,parallel Old(针对parallel scanvange gc的) g1和Serial old收集器就是采用该算法进行回收的。
6、常用的垃圾回收器
Serial
Serial收集器是最基本的、发展历史最悠久的收集器。
特点:单线程、简单高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)。
应用场景:适用于Client模式下的虚拟机。
Parallel Scavenge
与吞吐量关系密切,故也称为吞吐量优先收集器。
特点:属于新生代收集器也是采用复制算法的收集器,又是并行的多线程收集器(与ParNew收集器类似)。
该收集器的目标是达到一个可控制的吞吐量。还有一个值得关注的点是:GC自适应调节策略(与ParNew收集器最重要的一个区别)
GC自适应调节策略:Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。
Parallel Scavenge收集器使用两个参数控制吞吐量。
XX:MaxGCPauseMillis 控制最大的垃圾收集停顿时间
XX:GCRatio 直接设置吞吐量的大小。
parallel old
是Parallel Scavenge收集器的老年代版本。
特点:多线程,采用标记-整理算法。
应用场景:注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器。
Serial Old
Serial Old是Serial收集器的老年代版本。
特点:同样是单线程收集器,采用标记-整理算法。
应用场景:主要也是使用在Client模式下的虚拟机中。也可在Server模式下使用。
Server模式下主要的两大用途(在后续中详细讲解···):
在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用。
作为CMS收集器的后备方案,在并发收集Concurent Mode Failure时使用。
ParNew
ParNew收集器其实就是Serial收集器的多线程版本。
除了使用多线程外其余行为均和Serial收集器一模一样(参数控制、收集算法、Stop The World、对象分配规则、回收策略等)。
特点:多线程、ParNew收集器默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。
和Serial收集器一样存在Stop The World问题
应用场景:ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的。
cms(使用的是三色标记+增量更新)
一种以获取最短回收停顿时间为目标的收集器。
特点:基于标记-清除算法实现。并发收集、低停顿。
应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务。
CMS收集器的运行过程分为下列4步:
初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。不存在stw
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
并发清除:对标记的对象进行清除回收。
CMS收集器的内存回收过程是与用户线程一起并发执行的。
CMS收集器的工作过程图:
CMS收集器的缺点:
对CPU资源非常敏感。
无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。
因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。