JVM以及垃圾回收算法(GC)

1、JVM的位置

jvm本质上也是一个软件,所以jvm是在操作系统上执行的
在这里插入图片描述

2、jvm体系结构

jvm的体系结构主要包含五大部分:方法区、堆、栈、本地方法栈、程序计数器
在这里插入图片描述
类的加载过程分为:加载、验证、准备、解析、初始化五个阶段,也就是加载、链接(验证、准备、解析)、初始化
在这里插入图片描述

3、类加载器

作用:加载Class文件

在jvm里面有三个类加载器:BootStrapClassLoaderExtClassLoader、AppClassLoader

如果想自定义一个类加载器的话,可以继承ClassLoader类
在这里插入图片描述

每个类都有一个Class对象,而每个Class对象都有许多个实例
Class对象通过new创建实例,实例可以通过getClass获得Class对象
在这里插入图片描述

双亲委派机制

双亲委派机制就是当类需要去加载一个.class字节码文件的时候,会首先把它委托给自己的上级去加载,上级没有,自己才会加载,即向上查找缓存,向下加载

作用:

  • 防止重复加载,双亲委派机制向上查找的时候会去查找缓存里有没有加载过这个类,加载过就不用重复加载了。
  • 避免核心代码被篡改,通过委派的方式,如果上级加载可以加载这个类,就会直接加载,即使篡改了这个类,它也不会被加载。保证了安全

例如:
我自己编写了一个String类,并写了个toString方法

package java.lang;

public class String {
    public String toString() {
        return "tossssss";
    }

    public static void main(String[] args) {
        String s = new String();
        System.out.println(s.toString());
    }
}

执行结果:
在这里插入图片描述
由于String是在根加载器中进行加载的,所以双亲委派机制会将String类向上提交,交给根加载器去加载,而根加载器中的String是没有main()方法的,也就是说,我们编写的这个类根本就没有被加载,所以才会说找不到 main 方法
在这里插入图片描述

4、堆

堆里面一般用来存放对象和数组

方法区一般存放静态变量、常量、类信息(构造方法/接口定义) 、运行时常量池

堆一般分为新生区、老年区

在这里插入图片描述

4.1、新生代(新生区)、老年区

1、新生代可以分为三个区域:伊甸园区(Eden)、幸存0去(S0)、幸存1区(S1),它们三个的空间比默认为8:1:1,可以设置

2、所有的对象在刚创建(new)的时候,基本都会放在伊甸园区,但是当这个对象太大了的时候,会直接放在老年区

3、当伊甸园区放满之后,会触发一次轻GC(Minor GC),将伊甸园区(Eden)和幸存0区(From)幸存下来的对象放入幸存1区(To),之后幸存0区和幸存1区互换名字,即幸存0区变成幸存1区,幸存1区变成幸存0区;当有对象在这样一次次Minor GC下存活了15次(默认)的时候,这个对象就会被放入老年区

4、当幸存1区的空间大小小于幸存0区和伊甸园区幸存下来的对象大小时会被放入老年代。若是老年代也放不下,则会触发Full GC

5、当新生区经过Minor GC后,老年区里面已经没有空间可以容纳下新生区存活下来的对象时,会触发一次Major/Full GC,回收用不到的对象,回收后还是没有内存的情况下,则会抛出OOM(Out Of Memery)

4.2、永久代(元空间)

注意:永久代(jdk1.8后变为元空间metaspace),也叫方法区,实际上,无论是永久代还是元空间,它们都是对方法区的实现,存储不在虚拟机中,而是存在本地,所以一般叫“非堆

总结:JDK1.8 :
hotspot移除了永久代,用元空间(Metaspace) 取而代之。这时候,字符串常量池在堆里面,运行时常量池在方法区

逻辑上存在,物理上不存在

当出现了OOM故障,该如何排错
  • Debug代码,一行行分析
  • 使用内存快照分析工具,如MAT、Jprofiler

MAT、Jprofiler的作用:

  • 分析Dump的内存文件,快速定位内存泄露
  • 获得堆中的数据
  • 获得大的对象

4.3、如何判断一个对象已经死亡

引用计数法

这个方法比较简单,就是给对象添加一个引用计数器,这个对象每被引用一次,计数器+1,每释放一次,引用-1,当计数器为0时可以回收。但是会出现循环引用的问题

可达性分析算法

通过GC Root对象作为起点,基于对象引用关系,向下搜索,所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连,则证明对象可以进行回收

在Java中,可作为GC Root的对象有以下几个:

  • 虚拟机栈中引用的对象
  • 方法区中,类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中JNI引用的对象(即一般锁的Native方法)
    在这里插入图片描述
    Object1、Object2、Object3、Object4与GC Root之间有关系,则存活,Object5、Object6、Object7、Object8没有与任何GC Root相连,则表示可以回收

4.4、垃圾回收算法

1、复制算法

将可用内存分为大小相等的两块,每次只是用其中一块,当空间满了之后会将存活的对象复制到另外一块上,再清理掉前一块。JVM中,一般是在新生代使用这个算法。

优点:没有内存碎片问题
缺点:浪费内存空间

在这里插入图片描述

2、标记清除算法

包含“标记”和“清除”两个阶段,首先会标记所有需要进行回收的对象,在标记完成后统一回收所有被标记的对象

主要缺点:

  • 标记和清除的效率都不高(时间)
  • 容易产生内存碎片(空间)
    在这里插入图片描述
3、标记整理算法

标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后清理掉边界以外的内存。JVM中老年代适合使用这种算法

优点:不浪费内存空间,也不会出现内存碎片
缺点:整理过程较麻烦,只适用于存活率高的区域

在这里插入图片描述

4、分代收集算法

根据上面的三个算法我们知道它们各自有各自的优势,各自有各自的劣势,单独使用其中的某一个无法满足JVM的垃圾回收需求,所以我们可以将它们组合起来用

新生代中的对象大多数都是瞬间对象,存活率不高,只需要复制较少对象即可完成垃圾回收工作,因此新生代采用复制算法

老年代中的对象,身经百战,存活率高,又没有额外的担保内存,因此采用标记整理算法

总结

内存效率:复制算法 > 标记清除算法 > 标记整理算法(时间)

内存整齐度:复制算法 = 标记整理算法 > 标记清除算法(内存碎片)

内存利用率: 标记清除算法 = 标记整理算法 > 复制算法(空间)

5、线程暂停、安全点、安全区

在执行可达性分析的时候,会出现在分析的过程中,对象关系引用等发生了变化,为了保证分析的准确性,就必须在分析的时候暂停所有的Java线程,Sun将这一事件称作“Stop The World”;

只有到达某些点才可以进行GC操作,这些点称作安全点(Safepoint),比如,循环的末尾、方法临返回前/调用方法的call指令后、可能跑异常的位置等。

安全区(Safe region)是一块区域,在该区域中引用都不会被修改。比如,当线程进入到安全区的时候会标识自己进入了安全区,等它被唤醒准备离开时,先检查GC是否完成,如果完成则可以离开,否则就在安全区等待。

4.5、垃圾收集器

1、垃圾收集器分类

收集器之间的绿线表示可以搭配使用
例如,如果使用新生代的Parallel Scavenge收集器的话,可以搭配老年代的serial Old收集器或者Parallel Old收集器使用
在这里插入图片描述
该图来自https://www.bilibili.com/video/BV1nR4y1W7Vp?spm_id_from=333.337.search-card.all.click

2、Serial、Serial Old 串行收集器

这两个收集器的方法几乎就是一样的,由于是串行收集器,因此只有一条GC线程

当所有的线程都进入安全区后,GC线程才会进行可达性分析,垃圾回收等操作,之后应用进程会重新启动
在这里插入图片描述

3、ParNew、Parallel Scavenge、Parallel Old 并行收集器

这几个收集器的原理相差不多,与串行收集器的原理也差不多,只是这几个是并行收集器,也就是不只一条GC线程同时运行,这样的话效率比串行收集器的效率高

也是Java8使用的收集器
在这里插入图片描述

4、CMS(Concurrent Mark and Sweep)收集器

CMS:并发的标记和清理收集器

CMS收集器是第一款真正实现了并发收集的老年代收集器,采用多线程并发以及标记-清除算法来实现垃圾回收,CMS只会在初始标记重新标记的时候挂起线程,造成一定的应用停顿(STW),在其他阶段GC线程与应用线程并发交替执行,不必挂起线程,所以并不会造成应用的停顿。CMS可以最大程度的减少因垃圾回收而造成的应用停顿的时间
在这里插入图片描述

5、G1(Garbage-First)收集器

从前面的学习可以知道,各个分区是连续的,但是如果我们使用了G1收集器的话,它会改变我们的内存布局,如下,各个区域是打散分开的,为了做价值分析,价值高的回收,价值低的不管

价值分析:死亡的对象比较多,价值就高
在这里插入图片描述
G1 内存布局:
在这里插入图片描述
G1垃圾回收步骤:
在这里插入图片描述
当垃圾回收停顿时间过长的时候,G1这次就不会去回收价值低的内存块,而是让应用线程先执行,下次进行回收

4.6、JVM内存调优参数

查看
-XX:+PrintGCDetails : 表示输出GC的详细情况
-XX:+PrintGCDateStamps : 指定输出GC时的时间格式,比-XX:+PrintGCTimeStamps可读性更高
-Xloggc : 指定gc日志的存放位置

堆栈设置:
-Xss1024k:每个线程栈的大小为1024k
-Xms1024m:表示初始堆大小为1024m,默认是物理存储的1/64
-Xmx1024m:表示最大堆大小为1024m,默认物理存储的1/4
-Xmn256m:表示新生代大小为256m
-XX:NewSize:设置新生代初始大小,应小于-Xms的值
-XX:NewRatio=4:老年代与新生代的比值为4/1,默认为2
-XX:SurvivorRatio:默认8,表示一个Survivor区占用1/8的Eden内存
-XX:MetaspaceSize:设置元空间大小
-XX:MaxMetaspaceSize:设置最大元空间大小,默认是不受限制的,JVM Metaspace会进行动态扩展

收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParallelOldGC:设置老年代使用并行收集器
-XX:+UseParNewGC:设置在新生代使用并行收集器
-XX:+UseConcMarkSweepGC:设置CMS并行老年代收集器
-XX:+UseG1GC:设置G1收集器
-XX:+UseParallelGCThreads:设置用于垃圾回收的线程数

并行收集器设置
-XX:ParallelGCThreads:设置并行收集器手机是使用的CPU数。并行手机线程数。
-XX:MaxGCPauseMillis:设置并行收集最大暂停时间
-XX:GCTimeRatio:社会组垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值