NO.3 垃圾收集器&ClassLoader

零蚀


前言

  • 很内容其实已经做过专题集整理了,设计相同的领域内容,会标记内容目录,并跳过相应的知识点,所以垃圾回收之后,和 ClassLoader会少一点,thread内容会更少一点(大部分是偏向于锁的学习,这些之前也已经涉猎过了),在学习这本书的时候,作为一个Android端,或者是面向其他端(反正不是后端),我会强调的学习自己迫切需要的内容,很多像后端多并发,检测工具用法等等,我只会了解一下皮毛的方式浏览过去。因为并不想在VM上停留太久,后面还有很多东西。

各个垃圾收集器

  • 垃圾回收器的简介
    • Serial收集器:Serial是一个新生代收集器,也是最经典(古老)的垃圾回收器,它是一种’单线程’垃圾回收器,这种单线程是不是指它只有一个线程,而是说它运行过程中,所有的工作线程必须停掉,等待Serial垃圾回收结束。所以这种机制的垃圾回收顿挫敢强烈,用户体验极不友好。这样做的目的是早期的垃圾回收,为了保证回收时没有新的垃圾产生。以下是Serial & SerialOld收集器的运行,它的优点是简单高效,它是所有收集器里内存消耗最少的。如果不是频繁手机是不会太影响,所以在某些场景下还是有用的。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9AdDpTKT-1589096732815)(media/15890144619510/15890271753101.jpg)]

    • SerialOld收集器: SerialOld是一个老年代垃圾回收器,他同样是一个单线程回收器,如上图的后半段它运用的就是SerialOld进行老年代回收,SerialOld主要是通过标记-整理法进行回收。

    • ParNew收集器: 它是一个回收新生代的垃圾回收器,可以和作为老年代的CMS并用。它与Serial的唯一区别在于它是多线程的。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ld1hdrKk-1589096732816)(media/15890144619510/15890282903140.jpg)]

    • Parallel Scavenge收集器: 这也是一个新生代的垃圾回收器,Parallel Scavenge主要的目的是减少吞吐量,他能通过参数来精准的控制吞吐量,如控制回收时间的-XX:MaxGCPauseMillis和控制吞吐量的参数-XX:GCTimeRatio。吞吐量的定义如下:

    吞 吐 量 = 运 行 用 户 代 码 的 时 间 运 行 用 户 代 码 时 间 + 垃 圾 回 收 的 时 间 吞吐量 = \frac{运行用户代码的时间}{运行用户代码时间+垃圾回收的时间} =+

    • Parallel Old收集器: Parallel Old是Parallel Scavenge的老年代回收机制,在处理器资源稀缺,活着对吞吐量有要求的情况下一般会使Parallel Scavenge&Parallel Old这个组合。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dgo31ohN-1589096732817)(media/15890144619510/15890797827228.jpg)]

    • CMS收集器: CMS(Concurrent Mark Sweep)从名字可以看出来是“并发的标记清除”(并发是指一并发生,强调现象,所以可以用时间片分配来解释,并行,强调行为,可以用多内核来解释),它是以获取最短时间为目的的回收器。它的整个步骤为:

      1. 初始标记(CMS initial mark)
      2. 并发标记(CMS concurrent mark)
      3. 重新标记(CMS remark)
      4. 并发清除(CMS concurent sweep)

      其中“初始标记”和“重新标记”需要Stop The Word初始标记是首先会标记GC root可以直接关联到的对象,这个过程用时短,速度快。并发标记这个时候是通过GC Root直接关联对象开始,直接遍历整个对象图的过程。这个过程不阻碍用户线程,可与垃圾回收机制一同进行。重新标记这个过程是为了防止在之前的过程中,对象的引用发生变化,这个阶段时间略长于初始标记,远短于并发标记。并发清理这个阶段由于不需要对活着的对象进行任何的操作,所以可以和用户进程进行并发操作。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WAFsQnmF-1589096732819)(media/15890144619510/15890847543557.jpg)]

      缺点:在他的并发地停顿的优点下,它比较吃资源,它需要占用一部分线程,当内核数为4个或者更多时候他的资源消耗不是很明显,但是内核数低于4个时,就会要求处理器核心分担任务,这样就会加重处理器负担。对于“浮动垃圾”(出现在标记之后的垃圾),有可能导致CMS回收失败从而导致另一次的FULL GC。最后CMS时基于“标记-清除”算法来进行清理的,所以会造成大量的空间碎片。所以FULL GC时会移动活着的对象,从而又加长了回收的时间。

    • G1收集器: G1收集器(Garbage First)是JDK7引入,在jDK8是完善了G1的支持,它是一款全能的垃圾收集器。G1根据java堆,把连续的内存区块分为大小相等的几个且独立的区域(Region),每个Region都可以扮演新生代的Eden&Survivor区间,还可以根据不同的区域角色做不同的处理。除此之外还会设立Humongous区域来储存大的对象,一般在1M~32M(都为2^N),如果空间不够,会将多个连续的Humongous来储存。这样避免了FULL GC,同时他还为每个Region记录优先级(数值价值)的记录表,来提高回收效率。他的大体流程分为以下4个流程:

      1. 初始标记(initial mark):扫描直接关联GC Root对象
      2. 并发标记(concurrent mark):扫描直接对象,和记录的引用改动对象
      3. 最终标记(Final remark):用户线程暂停,做记录应用改动的最后标记
      4. 筛选回收(Life Data Counting and Evacuation):通过Region控件的对象的标记-复制算法方式,来清理区域,这里必须暂停用户线程,并且可并行完成。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uY6EQAMZ-1589096732820)(media/15890144619510/15890863233896.jpg)]

      相对于CMS优点明显,但是缺点也明显,它的负载高,需要创建很多的记录信息才行。

    • 除了上述的经典收集器外还有Shenandoah(在RedHat,由于不是Oracle的亲儿子被拒之不用),除此之外还有ZGC……


内存担保

  • 对象空间分配策略
    • 对象一般在新生代Eden中进行内存划分,当Eden没有足够的空间的时候,JVM会发起一次Minor GC(局部回收)。需要大的连续的内存空间的对象,例如庞大的数组,这对内存就十分不友好,因为如果需要复制操作的话,就非常占用内存,而新生代又很容易触发GC,而导致在Eden和Survivor之间相互复制,所以虚拟机会有将大型对象放入老年区的策略。

    • 对象一般在Eden(新生代内存之一)中诞生,在第一次Minor GC中存活下来的话,年龄设置为1,并且会被移动入Survivor(新生代内存之一)中,在Survivor中每当经历过一次Minor GC后对象的年龄都会+1。当年龄增加到一定数值后(默认值为15),就会晋升到老年代。(-XX:MaxTenuringThreshold可以设置默认值)。

    • Minor GC之前,会检查一下老年代的可用空间是否大于新生代所有对象的空间总和。如果符合条件,说明此次的Minor GC行为安全,(确保晋升),如果不行,会检查老年代的连续空间是否大于历届晋升的平均空间大小,如果ok会尝试一次,如果不ok,直接FULL GC。(当然尝试就有赌的成分,一旦输了也是要FULL GC)


类的加载过程

  • 类的前世今生
    • 图解:
    java编译器
    Java程序(.java)
    字节码(.class)
    JVM

    一般类从加载进虚拟机到写在出内存的想洗步骤如下:

    <链接 linking>
    验证
    准备
    解析
    加载
    初始化
    使用
    卸载

    当对象遇到new、getstatic、putstatic、invokestatic字节码命令的时候,如果对象没有进行初始化,会进行初始化操作,触发这种情况的有 1、new一个对象,2、读取或者设置静态字段,3、调用静态方法。

  • 类和类加载器
    • 类中经常可以看到equal、isInstance、isAssignableFrom,来判别两个类是否相同,但是,类和类之间的的判别是由类加载器完成的,如果不同的类加载器加载的类,结果也会出现不同。

    • JVM中有两种加载器,一种是启动类加载器,是JVM自身的一部分,由C++编写而成,另一部分是类加载器(ClassLoader),它是对立于java虚拟机的外部,并且全部继承自虚拟类java.lang.Classloader

    • 类加载器分为三层类加载器:

      • 启动类加载器(Bootstrap Class Load):主要负责加载<JAVA_HOME>/lib下的文件,而它加载时会更具特定的名字进行加载。而这部分的代码,是无法被引用的。
      • 扩展类加载器(Extension Class Load):主要负责加载<JAVA_HOME>/lib/ext下的文件。这个加载器在JDK9被模块化的拓展功能所取代。
      • 应用类加载器(Application Class Load):它也被称为系统类加载器,它会加载所有用户路径上的类(ClassPath),这也是程序默认的类加载器。】
  • 双亲委派

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RXygLOIZ-1589096732821)(media/15890144619510/15890960694738.jpg)]

    • 双亲委托是指当类加载器加载一个类的时候它会先把加载任务委托给父加载器,也就是上图不断将任务向上层传递,当父加载器无法加载这个类时候,子加载器才会自己处理掉类的加载任务。这样做的好处是:1、维护了一个优先级;2、保证了同一个类不会被多个加载器加载而导致在判断时判断为两个类。

    • JDK9 之后java开是模块化设计,也就是“可配置封装隔离机制”。而平台类加载器代替了扩展类加载器(Platform Class Loader),

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2KlZlifc-1589096732821)(media/15890144619510/15890966269064.jpg)]

内容参考自《深入理解java虚拟机》,无商业目的


🔗 前言
🔗 Android 知识栈
🔗 JVM 快速排序篇
🔗 NO.1 OpenJDK 前言
🔗 NO.2 内存区域&回收算法
🔗 NO.4 原子&线程

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

零蚀zero eclipse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值