JVM的初步学习

在进行学习之前,我们首先来看看几道常见面试题。

  1. 请谈谈你对JVM的理解,Java8的虚拟机有怎样的更新?
  2. 什么是OOM,什么是StackOverflowError?
  3. JVM常用参数调优你知道哪些?
  4. GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方?
    带着这些问题,我们可以开始接下来的学习。

一、JVM体系结构概述

JVM的位置:
在这里插入图片描述
JVM是运行在操作系统之上的,它与硬件没有直接交互。


JVM结构概览
在这里插入图片描述
可以看到,JVM逻辑上可以分为三个部分,作为入口的类加载器子系统、运行时数据区以及作为出口的执行引擎。


类加载器ClassLoader
由于Java类加载的过程比较复杂,这里不做过多的介绍,仅仅只对类加载器的功能和作用进行简单介绍。

ClassLoader类加载器负责加载class文件(class文件在文件的开头有特定的文件标示),将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构。

JVM有三种类加载器

  1. 根类加载器(Bootstrap Class Loader):用来加载Java核心类,是用原生代码实现的。
  2. 扩展类加载器(Extension Class Loader):负责加载JRE的扩展目录。
  3. 应用类加载器(App Class Loader):应用类加载器又称为系统类加载器,主要负责加载程序开发者自己编写的Java类。
public class ClassLoderDemo {
    public static void main(String[] args) {
        ClassLoderDemo demo = new ClassLoderDemo();
        System.out.println(demo.getClass().getClassLoader().getParent().getParent());
        System.out.println(demo.getClass().getClassLoader().getParent());
        System.out.println(demo.getClass().getClassLoader());
    }
}

结果:
在这里插入图片描述
可以很明确的看到三种类加载器的继承关系。


执行引擎Execution Engine
执行引擎是JVM的出口,负责解释命令,提交给操作系统执行。

接下来着重介绍运行时数据区域

运行时数据区域

jdk1.8之前:
在这里插入图片描述
运行时数据区域可以分为两个部分,线程共享区域和线程独占区域。

线程独占区域

  1. 程序计数器
    程序计数器是一块较小的内存区域,用来存储指向下一条指令的地址,从而实现代码的流程控制。在多线程的情况下,程序计数器也用来记录当前线程执行的位置,从而当线程被切换回来时能知道该线程上次运行到哪了。

  2. 虚拟机栈
    虚拟机栈就是我们常说的Java内存中的栈。实际上,Java虚拟机栈有一个个栈帧组成,每一次方法调用都会有一个对应的栈帧压入Java栈中。栈帧中拥有局部变量表、操作数栈、动态链接、方法出口等信息。
    局部变量表中主要存放了8中基本的数据类型(Boolean、Byte、char、short、int、long、float、double)以及对象的引用
    Java虚拟机栈的生命周期与线程相同。

    若Java虚拟机栈的动态内存不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度时,就抛出StackOverFlowError异常

  3. 本地方法栈
    本地方法栈与虚拟机栈的作用相似,但本地方法栈使用到的是native方法

线程共享区域

  1. 方法区
    方法区存储了每一个类的结构信息,包括常量、静态变量、构造函数、成员方法以及运行时常量池等数据。
    方法区是Java虚拟机中定义的一种规范,在JDK1.8之前,永久代是它的一种实现方式。JDK1.8时,永久代被彻底移除了,取而代之的是元空间,元空间使用的是直接内存。

  2. 堆是Java虚拟机中管理的最大的一块内存,是所有线程共享的,在虚拟机启动时创建。堆的作用即是存放对象实例,几乎所有的对象实例和数组都在这分配内存。
    因此,Java堆是垃圾回收器管理的主要区域。

在介绍垃圾收集算法之前,需要先了解堆的体系结构。

二、堆的体系结构概述

堆内存可以分为新生区(年轻代)和养老区(老年代)。
在这里插入图片描述
新生区又可以细分为伊甸区(Eden)、幸存0区(Survivor 0)、幸存1区(Survivor 1)。幸存0区和幸存1区也叫Survivor From 和 Survivor to。

新生区是类的诞生、成长、消亡的区域。一个类在这里产生,应用,最后被垃圾回收器收集,结束生命。所有的类都是在伊甸区诞生的,当伊甸区用完时,JVM的垃圾回收器将对伊甸区进行垃圾回收(Minor GC),将不再被其他对象引用的对象进行销毁。然后将幸存的对象移入Survivor From区。

当伊甸区再次触发GC时,会扫描Eden区和Survivor From区,经过这次回收还存活的对象则直接复制到Survivor to区,同时把这些对象年龄加1。最后,Survivor From区和Survivor to区互换,原Survivor to区成为下一次Survivor from区。部分对象会在From和to区中反复移动,当它们年龄增加到一定程度后(默认15岁,可通过-XX:MaxTenuringThreshold来设定参数)即移动15次后,就会进入到老年代。

如此累计,当养老区满时,会触发FullGC。若执行FullGC后仍旧无法进行对象存储,就会产生OOM异常

OOM

java.lang.OutOfMemoryError:Java heap space异常。表示堆内存溢出。
Java虚拟机内存不够的原因有二:
(1)Java虚拟机堆内存不够,可以通过参数-Xms(初始内存大小)、-Xmx(最大内存大小)来设置。
(2)代码创建了大量的大对象,并且长时间不能被垃圾回收区回收。

三、堆参数调优

-Xms:设置初始分配大小,默认为物理内存的“1/64”。
-Xmx:最大分配内存,默认为物理内存的“1/4”。
-XX:+PrintGCDetails:输出详细的GC处理日志。
在这里插入图片描述
结果为(本机物理内存为16G):
在这里插入图片描述


设置参数:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
结果为:
在这里插入图片描述
年轻代与老年代空间加起来正好等于初始分配内存总量。
即可证明,元空间(Metaspace)物理上不属于堆,属于Java直接内存。


设置参数:-Xms8m -Xmx8m -XX:+PrintGCDetails
将内存空间缩小后构造死循环
在这里插入图片描述
结果为:
在这里插入图片描述
可以看到GC的过程,当最后老年区进行FullGC也无法存储对象后,产生OOM异常。所以一般情况下,出现异常后调节分配内存的大小或者优化代码即可。

四、GC

GC是什么

GC是Java虚拟机的垃圾回收机制,分代收集算法。GC的目的是回收堆内存中不在使用的对象,释放资源。
GC按照回收的区域又分了两种类型,一种是普通GC(minor GC),一种是全局GC(majorGC / Full GC)。
minor GC:只针对新生代区域的GC。
major GC / Full GC:针对老年代的GC,偶尔也会伴随对新生代的GC。

GC算法

  1. 引用计数法
    每个对象拥有一个引用计数器,只要被引用一次,计数器加一,取消引用后减一,当引用计数器为零后可以被回收。
    缺点:
    每次对对象赋值时,均要维护引用计数器,计数器本身也有一定消耗。
    较难处理循环引用。
    JVM的实现一般不采用这种方式
  2. 复制算法
    年轻代中使用的时Minor GC,这种GC算法采用的是复制算法。(将幸存的对象从Form区复制到To区)
    在这里插入图片描述
    优点:
    - 没有标记和清除的过程,只需扫描一次,效率高。
    - 没有内存碎片。
    缺点:
    - 需要双倍的内存空间。
  3. 标记清除(Mark-Sweep)
    在老年代中使用。
    在这里插入图片描述
    优点:不需要额外空间。
    缺点:
    - 两次扫描,耗时严重。
    - 会产生内存碎片
  4. 标记压缩(Mark-Compact)
    同标记清除一样,应用在老年代Full GC中。
    在这里插入图片描述
    优点:没有内存碎片
    缺点:需要移动对象的成本
  5. 标记清除压缩(Mark-Sweep-Compact)
    在进行多次标记清除后,产生内存碎片多了后,此时再进行一次标记压缩。
    原理:Mark-Sweep与Mark-Compact的结合。当进行多次Mark-Sweep后才进行Mark-Compact。可以减少移动对象的成本

总结

没有最好的算法,只有最合适的算法———分代收集

年轻代的特点是区域相对于老年代较小,对象存活率低且GC的频率高。这种情况使用复制算法,速度是最快的。即使需要两倍的内存空间,由于年轻代中两个survivor区域的设计以及存活对象很少,所以影响不大。

老年代的特点是区域较大,对象存活率高且GC频率较低
这种存在大量存活率高的对象的情况,明显不适合使用复制算法。一般使用标记清除与标记压缩的混合实现

参考文献:

  1. JavaGuide面试指南:https://gitee.com/SnailClimb/JavaGuide
  2. 尚硅谷JVM:BV1iJ41197RC
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值