zoukankan      html  css  js  c++  java
  • 令人头痛的JVM

    klass模型

    Java的每个类,在JVM中,都有一个对应的Klass类实例与之对应,存储类的元信息如:常量池、属性信息、方法信息……

    1606013740690

    从继承关系上也能看出来,类的元信息是存储在元空间的

    普通的Java类在JVM中对应的是instanceKlass类的实例里面放着元信息,虚拟机层面与Java类对等的数据结构它的三个字类

    元信息:属性信息 方法信息

    1. InstanceMirrorKlass:用于表示java.lang.Class的实例,Java代码中获取到的Class对象,实际上就是这个C++类的实例,存储在堆区,学名镜像类, 静态属性是存储在镜像类中的
    2. InstanceRefKlass:用于表示java/lang/ref/Reference类的子类
    3. InstanceClassLoaderKlass:用于遍历某个加载器加载的类,描述类加载器的实例

    Java中的数组不是静态数据类型,是动态数据类型,即是运行期生成的,Java数组的元信息用ArrayKlass的子类来表示:

    1. TypeArrayKlass:用于表示基本类型的数组
    2. ObjArrayKlass:用于表示引用类型的数组

    类加载的过程:

    类的生命周期是由7个阶段组成,但是类的加载说的是前5个阶段

    加载:

    1、通过类的全限定名获取存储该类的class文件 ,并获取其二进制字节流

    2、解析成运行时数据,即instanceKlass实例,存放在方法区

    3、在堆区生成该类的Class对象,即instanceMirrorKlass实例

    验证:

    1. 文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
    2. 元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。
    3. 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
    4. 符号引用验证:确保解析动作能正确执行。

    准备:

    正式为类中定义的变量分配内存并设置类变量初始值

    1. 为静态变量分配内存、赋初值 int value=0; String value =null;
    2. 实例变量是在创建对象的时候完成赋值的,没有赋初值一说
    3. 如果被final修饰,在编译的时候会给属性添加ConstantValue属性,准备阶段直接完成赋值
    4. 准备过程通常分配一个结构用来存储类信息,这个结构中包含了类中定义的成员变量方法接口信息

    解析:

    实际上是把类的符号引用替换为直接引用的过程。

    解析后的信息存储在ConstantPoolCache()类实例中

    常量池缓存是为常量池预留的运行时数据结构。保存所有字段访问和调用字节码的解释器运行时信息。缓存是在类被积极使用之前创建和初始化的。每个缓存项在解析时被填充

    1. 类或接口的解析
    2. 字段解析
    3. 方法解析
    4. 接口方法解析

    初始化:

    1. 执行静态代码块,完成静态变量的赋值
    2. 静态字段、静态代码段,字节码层面会生成clinit方法
    3. 方法中语句的先后顺序与代码的编写顺序相关

    初始化的步骤

    1. 如果此类没被加载连接,则先加载连接此类;
    2. 如果此类的直接父类还未被初始化,则先初始化其直接父类;
    3. 如果类中有初始化语句,则按照顺序依次执行初始化语句。

    初始化的时机

    1. 创建类的实例(new关键字);
    2. java.lang.reflect包中的方法(如:Class.forName(“xxx”));反射
    3. 对类的静态变量进行访问或赋值;
    4. 访问调用类的静态方法
    5. 初始化一个类的子类父类本身也会被初始化;
    6. 作为程序的启动入口,包含main方法(如:SpringBoot入口类)。

    主动引用

    主动引用:在类加载阶段,只执行加载连接操作,不执行初始化操作

    被动引用

    被动引用: 在类加载阶段,会执行加载连接初始化操作。

    被动引用的几种形式:

    1. 通过子类引用父类的的静态字段,不会导致子类初始化;
    2. 定义类的数组引用不赋值,不会触发此类的初始化;
    3. 访问类定义的常量,不会触发此类的初始化

    JVM内存结构:

    JVM内存结构主要有三大块:

    1. 堆内存:是JVM中最大的一块,由年轻代和老年代组成,而年轻代内存又被分成三部分,

      • Eden空间、8

      • From Survivor空间、1

      • To Survivor空间,1

    2. 方法区 :存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。

    3. : java栈 本地方法栈 程序计数器

    控制参数

    • -Xms设置堆的最小空间大小。
    • -Xmx设置堆的最大空间大小。
    • -XX:NewSize设置新生代最小空间大小。
    • -XX:MaxNewSize设置新生代最大空间大小。
    • -XX:MetaspaceSize设置元空间最小空间大小。
    • -XX:MaxMetaspaceSize设置元空间最大空间大小。
    • -Xss设置每个线程的堆栈大小。

    调优 -xms和-xmx最大最小调成一样大-XX:MaxNewSize和-XX:MetaspaceSize最大最小调成一样大防止内存抖动

    或者物理内存的1/32

    Java堆(Heap)

    Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。最小值为内存1/4

    Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,

    所以Java堆中还可以细分为:新生代和老年代;

    新生代:

    再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。

    类出生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,

    老年代:

    新生代经过多次GC仍然存货的对象移动到老年区。若老年代也满了,这时候将发生Major GC(也可以叫Full GC),进行老年区的内存清理。若老年区执行了Full GC之后发现依然无法进行对象的保存,就会抛出OOM

    方法区(Method Area)

    java调用c,c++的动态链接库,运行里面函数需要的栈

    方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

    方法区有时被称为持久代(PermGen ), 但是实际上应该说使用永久代来实现方法区,1.8叫元空间而已。

    可以吧方法区比作java的接口,永久代和原空间是对接口的实现

    虚拟机栈(Stack):

    一个线程有一个虚拟机栈

    一共有方法调用次数个栈帧

    每个方法从被调用到执行完成的过程,其实就是一个栈帧在虚拟机栈中从入栈到出栈的过程。

    不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致

    本地方法栈(Native Method Stack):

    和栈作用很相似,区别不过是Java栈为JVM执行Java方法服务,

    而本地方法栈为JVM执行native方法服务。

    登记native方法,在Execution Engine执行时加载本地方法库

    程序计数器(Program Counter Register)

    其是很小的一块内存空间,可以看作是当前线程所执行的字节码的行号

    因为Java虚拟机的多线程时通过线程轮流切换,轮流得到处理器的资源来进行的,所以就需要在线程中有一个计数器来记录执行到哪一行,方便切换回来,所以每个线程都需要一个独立的程序计数器,他们相互独立,互不影响。

    类加载机制

    全盘负责委托机制

    当一个ClassLoader加载一个类的时候,除非显示的使用另一个ClassLoader,该类所依赖和引用的类也由这个ClassLoader载入

    双亲委派机制

    双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类

    • 1、当 AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
    • 2、当 ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader```去完成。
    • 3、如果 BootStrapClassLoader加载失败(例如在 $JAVA_HOME/jre/lib里未查找到该class),会使用 ExtClassLoader来尝试加载;
    • 4、若ExtClassLoader也加载失败,则会使用 AppClassLoader来加载,如果 AppClassLoader也加载失败,则会报出异常 `ClassNotFoundException
    双亲委派模式的优势
    • 沙箱安全机制:比如自己写的String.class类不会被加载,这样可以防止核心库被随意篡改
    • 避免类的重复加载:当父ClassLoader已经加载了该类的时候,就不需要子ClassLoader再加载一次

    GC算法和收集器

    如何判断对象可以被回收

    堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)

    引用计数法

    给对象添加一个引用计数器,每当有一个地方引用,计数器就加1。当引用失效,计数器就减1。任何时候计数器为0的对象就是不可能再被使用的。

    这个方法实现简单,效率高,但是目前主流的虚拟机中没有选择这个算法来管理内存,最主要的原因是它很难解决对象之前相互循环引用的问题。所谓对象之间的相互引用问题,通过下面代码所示:除了对象a和b相互引用着对方之外,这两个对象之间再无任何引用。但是它们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数器法无法通知GC回收器回收它们。

    可达性分析算法

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

    GC Roots根节点:类加载器、Thread、虚拟机栈的局部变量表、static成员、常量引用、本地方法栈的变量等等

    如何判断一个类是无用的类

    需要满足以下三个条件:

    • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
    • 加载该类的 ClassLoader 已经被回收。
    • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

    垃圾回收算法:

    1.标记-清除算法:

    它是最基础的收集算法,这个算法分为两个阶段,“标记”和”清除“。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它有两个不足的地方:

    1. 效率问题,标记和清除两个过程的效率都不高;
    2. 空间问题,标记清除后会产生大量不连续的碎片

    2.复制算法

    为了解决效率问题,复制算法出现了。它可以把内存分为大小相同的两块,每次只使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块区,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收

    3.标记-整理算法

    根据老年代的特点提出的一种标记算法,标记过程和“标记-清除”算法一样,但是后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象向一段移动,然后直接清理掉边界以外的内存

    4.分代收集算法

    现在的商用虚拟机的垃圾收集器基本都采用"分代收集"算法,这种算法就是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

    在新生代中,每次收集都有大量对象死去,所以可以选择复制算法,只要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率时比较高的,而且没有额外的空间对它进行分配担保,就必须选择“标记-清除”或者“标记-整理”算法进行垃圾收集

  • 相关阅读:
    C# 中自定义配置
    git 打标签
    状态模式
    组合模式
    intellij自动生成java代码注释(java文件注释和方法注释)
    git版本回退
    Error:Unable to make the module:***, related gradle configuration was not found. Please, re-import the Gradle project and try again.
    Typo: In word 拼写检查
    javax.persistence.EntityNotFoundException: Unable to find报错
    报错org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet"
  • 原文地址:https://www.cnblogs.com/RUAYO/p/14088782.html
Copyright © 2011-2022 走看看