JVM探究

Posted 轻舟一曲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM探究相关的知识,希望对你有一定的参考价值。

探究

  1. 谈谈对JVM的理解?java8虚拟机和之前的变化更新?

  2. 什么是OOM,什么是栈溢出?怎么分析?

  3. JVM的常用调优参数有哪些?

  4. 内存快照如何抓取,怎么分析Dump文件?

  5. 谈谈JVM的类加载器? rt-jat ext application

  6. 百度

  7. 思维导图 (在线找或者自己动手画)

JVM脑图 | ProcessOn免费在线作图,在线流程图,在线思维导图

jvm | ProcessOn免费在线作图,在线流程图,在线思维导图

image-20210607220714736

JVM的位置

image-20210607221827080

JVM的体系结构

方法调优99%都是在调堆。

image-20210607231753551

JVM的类加载器

作用:加载class文件。

image-20210607232918705 image-20210607232612781 image-20210607232840319
  1. 虚拟机自带的加载器
  2. 启动类(根)加载器
  3. 拓展类加载器
  4. 应用程序(系统类)加载器
image-20210607233308060

第3个在rt.jar包内,第2个拓展加载器在jre/lib/ext文件夹下

image-20210607234441502

双亲委派机制

定义

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

安全:

加载器:APP->EXT->BOOT(最终执行)

若BOOT没有::(最终执行)APP<-EXT<-BOOT

image-20210607233903396

沙箱安全机制

沙箱演进

JDK1.0

image-20210608093056051

JDK1.1

image-20210608093307300

JDK1.2

image-20210608093528458

JDK1.6

image-20210608093711865 image-20210608094021332

沙箱组件

  1. 字节码校验器:确保Java类文件遵循Java语言规范,帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。 (检查时异常)

  2. 类加载器:虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,他们之间互不可见。

    在3个方面对沙箱起作用:

    • 防止恶意代码去干涉善意代码; //双亲委派机制
    • 守护被信任的类库边界; //双亲委派机制
    • 将代码归入保护域,确定代码可以进行哪些操作。 //沙箱机制
    image-20210608094934738
  3. image-20210608095227449

native本地方法接口

public static void main(String[] args) {
    new Thread(()->{
        System.out.println("Thread start..");
    },"my thread name").start();
}

native:凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层c语言的库->进入本地方法栈->调用本地方法本地接口JNI。

native在内存区域中专门开辟了一块标记区域:Native Method Stack,登记native方法,在最终执行的时候,加载本地方法库中的方法通过JNI接口。

JNI作用:拓展Java的使用,融合不同的编程语言为Java所用。

应用了解即可:Java程序驱动机,管理系统,Robot。

image-20210608100740973

PC寄存器

image-20210608101824268

方法区

image-20210608101934488

方法区只存放:static final class 常量池

是堆中占内存比较小的。

image-20210608102348074 image-20210608102444521

数据结构。

栈–队列;

LIFO:后进先出;

栈:栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存释放。

对于栈来说不存在垃圾回收,线程结束栈释放。

栈存放的东西:8大基本类型+对象引用+实例的方法

栈运行原理:栈帧。

程序正在执行的方法一定是在栈顶部。

image-20210608104054894

栈堆:

image-20210608104415040

main方法先执行,后结束。

无限压栈溢出:

image-20210608103240869

面试题:详解JAVA对象实例化过程

1 对象的实例化过程

  • 对象的实例化过程是分成两部分:类的加载初始化,对象的初始化
  • 要创建类的对象实例需要先加载并初始化该类,main方法所在的类需要先加载和初始化
  • 类初始化就是执行方法,对象实例化是执行方法
  • 一个子类要初始化需要先初始化父类

2 类的加载过程

image-20210608105553668
  • 类的加载机制:如果没有相应类的class,则加载class到方法区。对应着加载->验证->准备->解析–>初始化阶段

    • 加载:载入class对象,不一定是从class文件获取,可以是jar包,或者动态生成的class

    • 验证:校验class字节流是否符合当前jvm规范

    • 准备:为 类变量 分配内存并设置变量的初始值( 默认值 )。如果是final修饰的对象则是赋值声明值

    • 解析:将常量池的符号引用替换为直接引用

    • 初始化:执行类构造器( 注意不是对象构造器 ),为 类变量 赋值,执行静态代码块。jvm会保证子类的执行之前,父类的先执行完毕

  • 其中验证、准备、解析3个部分称为 连接

  • 方法由 静态变量赋值代码和静态代码块 组成;先执行类静态变量显示赋值代码,再到静态代码块代码。

3 触发类加载的条件

  • 第一次创建类的新对象时, 会触发类的加载初始化和对象的初始化函数执行,这个是实例初始化,其他6个都是类初始化

  • JVM启动时会先加载初始化包含main方法的类

  • 调用类的静态方法(如执行invokestatic指令)

  • 对类或接口的静态字段执行读写操作(即执行getstatic、putstatic指令);不过final修饰的静态字段的除外(已经赋值,String和基本类型,不包含包装类型),它被初始化为一个编译时常量表达式

    • 注意 :操作静态字段时,只有直接定义这个字段的类才会被初始化;如通过其子类来操作父类中定义的静态字段,只会触发父类的初始化而不是子类的初始化
  • 调用JavaAPI中的反射方法时(比调用java.lang.Class中的方法(Class.forName),或者java.lang.reflect包中其他类的方法)

  • 当初始化一个类时,其父类没有初始化,则需先触发父类的初始化(接口例外)

4对象的实例化过程

  • 对象实例化过程其实就是执行类构造函数 对应在字节码文件中的()方法(称之为实例构造器);()方法由 非静态变量、非静态代码块以及对应的构造器组成

    • ()方法可以重载多个,类有几个构造器就有几个()方法
    • ()方法中的代码执行顺序为:父类变量初始化,父类代码块,父类构造器,子类变量初始化,子类代码块,子类构造器。
  • 静态变量,静态代码块,普通变量,普通代码块,构造器的执行顺序:

    image-20210608110346808
  • 具有父类的子类的实例化顺序如下:

    image-20210608110421928

5类加载器和双亲委派规则,如何打破双亲委派规则

  • 类加载器

    • 通过一个类的全限定名来获取 描述此类的二进制字节流 ,实现这个动作的代码模块称为类加载器

    • 任意一个类都需要其加载器和类本身来确定类在JVM的唯一性;每个类加载器都有自己的类名称空间,同一个类class由不同的加载器加载,则被JVM判断为不同的类

      image-20210608110513333
  • 双亲委派模型

    • 启动类加载器有C++代码实现,是虚拟机的一部分。负责加载lib下的类库

    • 其他的类加载器有java语言实现,独立于JVM,并且继承ClassLoader

    • extention ClassLoader负责加载libext目录下的类库

    • application ClassLoader 负责加载用户路径下(ClassPath)的代码

    • 不同的类加载器加载同一个class文件会导致出现两个类。而java给出解决方法是下层的加载器加委托上级的加载器去加载类,如果父类无法加载(在自己负责的目录找不到对应的类),而交还下层类加载器去加载。如下图:

      image-20210608110618977
  • 打破双亲委派模型

    • 双亲委派模型并不是一个强制的约束模型,而是java设计者推荐给开发者的类加载实现方式
    • 双亲委派模型很好的解决各个类加载基础类的同一问题(越基础的类由越上层的加载器加载),但是基础类总是作为用户代码调用的API,但是如果它的具体实现是下层的代码,此时基础类需要调用下层的代码,则需要打破双亲委派模型
    • 如JNDI服务,JNDI的代码有启动类去加载(rt.jar),它需要调用由独立厂商部署在应用程序classpath下的JNDI的SPI(Service Provider Interface)代码。为了解决SPI代码加载问题,java引入了线程上下文类加载器去加载SPI代码。也就是父类加载器请求子类去完成类的加载动作
    • 线程上下文类加载器,线程创建时会从父线程继承,如果全局范围没有设置过,则默认设置为application Class Loader

3种JVM

image-20210608115803298

Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。

image-20210608120220752

堆内存中细分三个区域:新生区(伊甸园区),老年区,永久区。

image-20210608120645639

GC垃圾回收主要是在伊甸园区和老年区。

假设堆内存满了,OOM,内存爆了

image-20210608120947371

JDK8以后,永久区改了名字(元空间)

新生区

  • 类诞生和成长甚至死亡的地方;
  • 伊甸园区:所有对象都是在这个区new出来的;
  • 幸存者区0,幸存者区1。

老年区

image-20210608121542383

永久区

关闭JVM才会释放这个内存。

image-20210608121821439

一个启动类,加载了大量的三方jar包。比如Tomcat部署了太多的应用,大量动态生成的反射类。不断的被加载,直到内存满,报OOM错误。

image-20210608122320142

方法区里面还有一小块放常量池。

image-20210608122443612

测试堆大小

image-20210608122807180 image-20210608123109614

默认分配总内存是电脑内存的1/4.

初始化内存是1/64.

修改参数

image-20210608123333087 image-20210608123428623

OOM报错解决思路

image-20210608123631150 image-20210608123847758 image-20210608124002413

JProfiler

面试问题

image-20210608154658880

关联IDEA:

image-20210608155025546

安装:

image-20210608154803255

测试:

-Xms设置初始化内存分配大小默认1/64,

-Xmx设置最大分配内存默认1/4

-XX:+PrintGCDetails

-XX:+HeapDumOnOutOfMemoryError

image-20210608161015634 image-20210608161444523 image-20210608161621853

GC垃圾回收器常用算法

image-20210608162557156 image-20210608162949032

GC常见面试题:

  • JVM的内存模型和分区,详细到每个区放什么东西?
  • 堆里面的分区有哪些?Eden,from,to 老年区,都有什么特点!
  • GC算法有哪几种?标记清除算法,标记压缩,复制算法,引用计数器,怎么用的?
  • 轻GC和重GC分别在什么时候发生?
image-20210608163528877

JVM并不采用这种方式,并不高效。

GC复制算法

新生代用的主要是复制算法:

没有被GC掉的对象就会被复制到下一个区域。to区不能有对象,当GC时先将to区对象复制到from区。

image-20210608164143864 image-20210608164623476

每次GC过后:Eden区和to区是空的。

好处:没有内存碎片。

坏处:多一个幸存区,浪费一半的空间。

极端情况:对象100%存活就都复制!

最佳使用场景:对象存活度较低的时候。

GC标记清除算法

优点:不需要额外的空间。

缺点:时间成本,两次扫描,产生内存碎片。

image-20210608165141535

GC标记压缩

对前者的优化。

压缩:防止内存碎片。

多了一个移动成本。

image-20210608165458357

GC总结

image-20210608165735641

面试问题:有没有最优的GC算法?没有最好的算法,只有最合适的算法。

所以GC也被称为分代收集算法。

image-20210608170052866

内存调优调的就是最后标记清除什么时候清除。

JMM

Java内存模型。

定义

image-20210608170558382

作用

image-20210608170919519 image-20210608171827202

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。

image-20210608171254653

JMM内存模型带来的问题:

  1. 可见性问题:CPU中运行的线程从主存中拷贝共享对象obj到它的CPU缓存,把对象obj的count变量改为2。但这个变更对运行在右边CPU中的线程不可见,因为这个更改还没有flush到主存中:要解决共享对象可见性这个问题,我们可以使用java volatile关键字或者是加锁

JMM是一个抽象的概念,本质是需要学习线程同步机制:

image-20210608171622647

学习三部曲:

  • 是什么?
  • 有什么用?
  • 面试问什么?
image-20210608172012259

以上是关于JVM探究的主要内容,如果未能解决你的问题,请参考以下文章

JVM探究

JVM探究

JVM初步了解

从JVM的角度看JAVA代码--代码优化

深入探究 JVM | 探秘 Metaspace

深入探究JVM之垃圾回收器