jvm浅析

作者: 西西弗斯XD | 来源:发表于2020-05-07 08:37 被阅读0次

    一 、jvm 内存模型

    作用:JVM不仅承担了Java字节码的分析(JIT compiler)和执行(Runtime),同时也内置了自动内存分配管理机制。

    JVM模型图.jpg

    1 、 堆(Heap)

    堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象数组都被分配到了堆内存中

    2、程序计数器(Program Counter Register)

    程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。

    3、方法区(Method Area)

    方法区主要是用来存放已被虚拟机加载的类相关信息
    包括类信息、运行时常量池、字符串常量池。
    类信息又包括了类的版本、字段、方法、接口和父类等信息。

    方法区与堆空间类似,也是一个共享内存区,所以方法区是线程共享的。
    假如两个线程都试图访问方法区中的同一个类信息,而这个类还没有装入 JVM,那么此时就只允许一个线程去加载它,另一个线程必须等待。

    4、虚拟机栈(VM stack)

    线程私有的内存空间,和线程一起创建,
    当创建一个线程时,虚拟机栈中申请一个线程栈,用来保存方法的局部变量、操作数栈、动态链接方法和返回地址等信息,并参与方法的调用和返回。
    每一个方法的调用都伴随着栈帧的入栈操作,方法的返回则是栈帧的出栈操作。

    5、本地方法栈(Native Method Stack)

    Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈则用于管理本地方法的调用。
    本地方法由 C 语言实现的。

    二 、JVM的运行原理

    1 、JVM 向操作系统申请内存,通过配置参数或者默认配置参数向操作系统申请内存空间,
    根据内存大小找到具体的内存分配表,然后把内存段的起始地址和终止地址分配给 JVM,接下来 JVM 就进行内部分配。

    2 、JVM 获得内存空间后,会根据配置参数分配堆、栈以及方法区的内存大小。

    3 、class文件加载、验证、准备以及解析,其中准备阶段会为类的静态变量分配内存,初始化为系统的初始值

    4 、初始化阶段,JVM首先会执行构造器方法,编译器会在.java文件被编译成.class 文件时,
    收集所有类的初始化代码,包括静态变量赋值语句、静态代码块、静态方法,收集在一起成为方法。

    5 、 执行方法、堆内存中会创建一个对象,对象引用存放在栈中。

    三 、 类编译加载执行过程

    类编译加载执行过程.jpg

    1、类编译

    .java文件变成.class文件,编译后的字节码文件主要包括常量池和方法表集合这两部分

    2 、类加载

    不同的实现类由不同的类加载器加载,
    JDK 中的本地方法类一般由根加载器(Bootstrp loader)加载进来,
    JDK 中内部实现的扩展类一般由扩展加载器(ExtClassLoader )实现加载,
    而程序中的类文件则由系统加载器(AppClassLoader )实现加载
    在类加载后,class 类文件中的常量池信息以及其它数据会被保存到 JVM 内存的方法区

    3 、类连接

    验证:验证类符合 Java 规范和 JVM 规范。
    准备:为类的静态变量分配内存,初始化为系统的初始值。
    解析:将符号引用转为直接引用的过程(JVM 可以直接获取的内存地址或指针)。

    4 、类初始化

    JVM首先将执行构造器方法,
    编译器会在将 .java 文件编译成 .class 文件时,
    收集所有类初始化代码,包括静态变量赋值语句、静态代码块、静态方法,收集在一起成为构造方法
    JVM 会保证构造方法的线程安全,保证同一时间只有一个线程执行。

    5 、即时编译

    在字节码转换为机器码的过程中,虚拟机中还存在着一道编译,那就是即时编译。
    即时编译器(JIT)会把运行比较频繁的代码编译成与本地平台相关的机器码,
    并进行各层次的优化,然后保存到内存中。

    6、编译优化技术

    1、方法内联
    把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用,减少时间和空间的消耗。
    2 、逃逸分析
    是判断一个对象是否被外部方法引用或外部线程访问的分析技术,编译器会根据逃逸分析的结果对代码进行优化。
    栈上分配
    如果发现一个对象只在方法中使用,就会将对象分配在栈上;相比于在堆中分配内存,垃圾回收机制回收消耗性能与时间更少。
    锁消除:
    在局部方法中创建的对象只能被当前线程访问,无法被其它线程访问,
    这个变量的读写肯定不会有竞争,这个时候 JIT 编译会对这个对象的方法锁进行锁消除。
    标量替换:
    逃逸分析证明一个对象不会被外部访问,将对象拆分后,可以分配对象的成员变量在栈或寄存器上,原本的对象就无需分配内存空间了

    四、 垃圾回收机制

    1、回收发生在哪里?

    VM 的内存区域中,程序计数器、虚拟机栈和本地方法栈这 3 个区域是线程私有的,随着线程的创建而创建,销毁而销毁,因此这三个区域的内存分配和回收都具有确定性。
    那么垃圾回收的重点就是堆和方法区中的内存,
    堆中的回收主要是对象的回收
    方法区的回收主要是废弃常量和无用的类的回收。

    2、对象在什么时候可以被回收?

    一般一个对象不再被引用,就代表该对象可以被回收。
    引用计数算法:当对象的引用计数器的值为 0 时,就说明该对象不再被引用,可以被回收了。
    可达性分析算法:当一个对象到 GC Roots 没有任何引用链相连时,就证明此对象是不可用的(HotSpot 虚拟机采用的就是这种算法)。

    3、 如何回收这些对象?

    自动性:当 JVM 处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一块空闲的内存块。
    不可预期性: 垃圾回收线程在 JVM 中是自动执行的,Java 程序无法强制执行。只能通过System.gc方法建议回收。

    4、GC算法


    GC算法.jpg

    5、GC性能衡量指标

    吞吐量:这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。
    停顿时间:指垃圾收集器正在运行时,应用程序的暂停时间。
    垃圾回收频率:适当地增大堆内存空间,保证正常的垃圾回收频率。

    6、GC调优策略

    1、 降低 新生代 GC( Minor GC) 频率
    通常在虚拟机中,复制对象的成本要远高于扫描成本。
    2、 降低 Full GC 的频率
    减少创建大对象,增大堆内存空间。

    7、选择合适的 GC 回收器

    响应速度较快:CMS(Concurrent Mark Sweep)回收器和 G1 回收器
    吞吐量高:Parallel Scavenge 回收器
    在 JDK1.8 环境下,默认使用的是 Parallel Scavenge(年轻代)+Serial Old(老年代)垃圾收集器

    堆外内存减少内存拷贝:

    Socket 堆外内存:
    使用ByteBuffer.allocateDirect()得到一个DirectByteBuffer对象,初始化堆外内存大小;
    里面会创建Cleaner对象,绑定当前this.DirectByteBuffer的回收,通过put,get传递进去Byte数组,或者序列化对象;
    Cleaner对象实现一个虚引用(当内存被回收时,会受到一个系统通知)当Full GC的时候,如果DirectByteBuffer标记为垃圾被回收,则Cleaner会收到通知调用clean()方法,回收堆外内存DirectByteBuffer。

    内容源于极客时间app -《java性能调优实战》,侵删。

    相关文章

      网友评论

          本文标题:jvm浅析

          本文链接:https://www.haomeiwen.com/subject/hklxwhtx.html