八股文四:JVM虚拟机

四、Java 虚拟机

(参考知乎:https://zhuanlan.zhihu.com/p/215878135

1、Java 内存结构

 

 

  • 堆:由线程共享,存放 new 出来的对象,是垃圾回收器的主要工作区域。
  • 栈:线程私有,分为 Java 虚拟机栈和本地方法栈,存放局部变量表、操作数栈、动态链接、方法出口等信息,方法的执行对应着入栈到出栈的过程。
  • 方法区:线程共享,存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等信息,JDK 1.8 中方法区被元空间取代,使用直接内存。

2、Java 类加载机制

 

 

  • 加载:加载字节码文件。这一动作是放在Java虚拟机外部去实现的,以便让应用程序自己决定如何获取所需的类。
  • 链接
    • 验证:验证字节码文件.class的正确性。
    • 准备:为静态变量分配内存。
    • 解析:将符号引用(如类的全限定名)解析为直接引用(类在实际内存中的地址)。
  • 初始化:为静态变量赋初值。

双亲委派模式

当一个类需要加载时,判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。

从Java虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现(HotSpot虚拟机中),是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些类加载器都有Java语言实现,独立于虚拟机外部,并且全部继承自java.lang.ClassLoader。
从开发者的角度,类加载器可以细分为:

  • 启动类加载器(BootstrapClassLoader):负责将 Java_Home/lib下面的核心类库加载到内存中(比如rt.jar),开发者无法直接获取到启动类加载器的引用。
  • 标准扩展类加载器(ExtensionClassLoader):它负责将Java_Home /lib/ext或者由系统变量 java.ext.dir指定位置中的拓展类库加载到内存中。开发者可以直接使用标准扩展类加载器。
  • 应用程序类加载器(ApplicationClassLoader):它负责将系统类路径(CLASSPATH)中指定的类库(自己写的类或第三方拓展包)加载到内存中。开发者可以直接使用系统类加载器。
  • 除此之外,还有自定义的类加载器,它们之间的层次关系被称为类加载器的双亲委派模型。

为什么我写一个String类无法代替JAVA自带的String类?

  • 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的BootstrapClassLoader进行加载。
  • 在这个初始化String类的方法中,先检查此类是否已经被ApplicationClassLoader加载过,若没有加载则调用父类加载器ExtensionClassLoader的loadClass()方法,如果父加载失败,再去检查BootstrapClassLoader的java.lang.String,检测到String类,则由BootstrapClassLoader对String进行加载。

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

3、垃圾回收算法

  • Mark-Sweep(标记-清除)算法:标记需要回收的对象,然后清除,会造成许多内存碎片。
  • Copying(复制)算法:将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。
  • Mark-Compact(标记-整理)算法(压缩法):与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。(优先清理老年代)
  • Generational Collection(分代收集)算法:分为年轻代和老年代,年轻代时比较活跃的对象,使用复制算法做垃圾回收。老年代每次回收只回收少量对象,使用标记整理法。

4、典型垃圾回收器

  • CMS

    • 简介:以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是 Mark-Sweep 算法。
    • 场景:如果你的应用需要更快的响应,不希望有长时间的停顿,同时你的 CPU 资源也比较丰富,就适合适用 CMS 收集器。
    • 垃圾回收步骤:
    1. 初始标记 :(Stop the World 事件 CPU 停顿,很短,独占CPU) 初始标记仅标记一下 GC Roots 能直接关联到的对象,速度很快;
    2. 并发标记 :(收集垃圾跟用户线程一起执行) 并发标记过程就是进行 GC Roots 查找的过程;
    3. 重新标记 :(Stop the World 事件 CPU 停顿,比初始标记稍微长,远比并发标记短) 修正由于并发标记时应用运行产生变化的标记;
    4. 并发清理:可以和用户线程并行执行,标记清除算法
    • 缺点:
    1. 并发标记时和应用程序同时进行,占用一部分线程,所以吞吐量有所下降
    2. 并发清除时和应用程序同时进行,这段时间产生的垃圾就要等下一次GC再清除。
    3. 采用的标记清除算法,产生内存碎片,如果要新建大对象,会提前触发 Full GC 。
  • G1

    • 简介 是一款面向服务端应用的收集器,它能充分利用多 CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型,即可以设置 STW 的时间。
    • 垃圾回收步骤:
    1. 初始标记(stop the world 事件 CPU 停顿只处理垃圾);
    2. 并发标记(与用户线程并发执行);
    3. 最终标记(stop the world 事件 ,CPU 停顿处理垃圾);
    4. 筛选回收(stop the world 事件 根据用户期望的 GC 停顿时间回收)。
    • 特点
      • 并发与并行 充分利用多核 CPU ,使用多核来缩短 STW 时间,部分需要停顿应用线程的操作,仍然可以通过并发保证应用程序的执行。
      • 分代回收 新生代,幸存带,老年代
      • 空间整合:总体看是采用标记整理算法回收,是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,通过复制来回收。
      • 可预测的停顿时间 使用 -XX:MaxGCPauseMillis=200 设置最长目标暂停值。能让使用这明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

在 Java 语言中,可作为 GC Roots 的对象包括 4 种情况:

a) 虚拟机栈中引用的对象(栈帧中的本地变量表); b) 方法区中类静态属性引用的对象; c) 方法区中常量引用的对象; d) 本地方法栈中 Native 方法引用的对象。

  • finalize 对象被垃圾回收器回收时执行的方法。

以下是详细说明:

  • 确定一个对象是否可以被回收算法(标记):
  1. 引用计数算法:每个对象添加一个引用计数器,每被引用一次,计数器加1,失去引用,计数器减1,当计数器在一段时间内保持为0时,该对象就认为是可以被回收得了。(在JDK1.2之前,使用的是该算法)
  2. 可达性分析算法:程序把所有的引用关系看作一张图,从一个节点GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
  • 引用
  1. 强引用:是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用对象来解决内存不足的问题。
  2. 软引用:如果一个对象只具有软引用,则内存空间充足时,垃圾回收器不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
  3. 弱引用弱引用软引用的区别在于:只具有弱引用的对象拥有更短暂生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定很快发现那些只具有弱引用的对象。
  4. 虚引用虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
  5. 引用类型被垃圾回收时间用途生存时间
    强引用从来不会对象的一般状态JVM停止运行时终止
    软引用当内存不足时对象缓存内存不足时终止
    弱引用正常垃圾回收时对象缓存垃圾回收后终止
    虚引用正常垃圾回收时跟踪对象的垃圾回收垃圾回收后终止
  • 垃圾收集算法

  1. 标记清除算法:标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收

  2. 复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

  3. 标记整理法:标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代)。

  4. 分代收集算法:对于一个大型的系统,当创建的对象和方法变量比较多时,堆内存中的对象也会比较多,如果逐一分析对象是否该回收,那么势必造成效率低下。分代收集算法是基于这样一个事实:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。当代商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。Java堆内存一般可以分为新生代、老年代和永久代三个模块。

GC Roots原理

GC Roots基本思路就是通过一系列的称为“GC Roots”的对象作为起始点, 从这些节点开始向下搜索, 搜索所走过的路径称为引用链( Reference Chain),当一个对象到 GC Roots 没有任何引用链相连( 用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

GC Roots对象

常说的GC(Garbage Collector) Roots,特指的是垃圾收集器(Garbage Collector)的对象,GC会收集那些不是GC Roots且没有被GC Roots引用的对象

一个对象可以属于多个root,GC Roots有以下几种:

  • Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的Java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots,.
  • Thread - 活着的线程
  • Stack Local - Java方法的local变量或参数
  • JNI Local - JNI方法的local变量或参数,JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植
  • JNI Global - 全局JNI引用
  • Monitor Used - 用于同步的监控对象
  • Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。然而,JVM并没有为这些对象提供其它的信息,因此需要去确定哪些是属于"JVM持有"的了。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值