Java虚拟机

JVM体系结构

  • 类加载器(Class Loader)

    • 作用:加载class

    • 分类:

      • 应用程序类加载器(ApplicationClassLoader)

      • 扩展类加载器(ExtensionClassLoader)

      • 根类加载器(BootStrapClassLoader)

      • 自定义类加载器(UserClassLoader)

  • 运行时数据区(Runtime Data Area)

    • 线程私有

      • Java栈(Stack):存储函数运行中的变量

      • 本地方法栈(Native Method Stack)

      • 程序计数器

    • 全局共享

      • 堆(Heap):存对象

      • 方法区(Method Area)

    栈和计数器中不存在垃圾,所以JVM调优99%是在调方法区和堆

下面以一个例子说明jvm实际内存运行过程:

类加载和双亲委派机制

  • 类加载机制

    • JVM使用Java类的流程如下:

      1. Java源文件----编译---->class文件

      2. 类加载器ClassLoader会读取这个.class文件,并将其转化为java.lang.Class的实例。有了该实例,JVM就可以使用他来创建对象,调用方法等操作了。

      那么ClassLoader是以一种什么机制来加载Class的?这就要求我们要了解Class文件有哪些来源,针对这些ClassJDK如何分工,谁来加载这些Class。

    • Class文件的来源

      • Java内部自带的核心类,位于$JAVA_HOME/jre/lib,其中最著名的莫过于rt.jar

      • Java的扩展类,位于$JAVA_HOME/jre/lib/ext目录下

      • 我们自己开发的类或项目开发用到的第三方jar包,位于我们项目的目录下,比如WEB-INF/lib目录

    • 类加载器的分类

      • Java核心类,这些Java运行的基础类,由一个名为BootstrapClassLoader加载器负责加载。这个类加载器被称为“根加载器或引导加载器”。

        注意:BootstrapClassLoader不继承ClassLoader,是由JVM内部实现。法力无边,所以你通过java程序访问不到,得到的是null。

      • Java扩展类,是由ExtClassLoader负责加载,被称为“扩展类加载器”。

      • 项目中编写的类,是由AppClassLoader来负责加载,被称为“系统类加载器”。

  • 双清委派机制

    • 类加载器受到类加载的请求,将这个请求向上委托给父类加载去完成,一直向上委托,直到根加载器(寻根:App -> Exc -> Boot)

    • 根加载器检查是否能够加载当前这个类,能加载就结束,使用当前类加载器,否则,抛出异常,通知子加载器进行加载,循环往复,直到找到能够加载此类的加载器;如果最终仍未找到,则报错ClassNotFound(回溯:App <- Exc <- Boot)

沙箱安全机制

组成沙箱的基本组件:

  • 字节码校验器

  • 类装载器(作用)

    • 防止恶意代码干涉善意代码(双亲委派机制)

    • 守护了被信任的类库边界

    • 将代码归入保护域,确定了代码可以进行哪些操作

Native关键字

  • 凡是带了Native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库

  • 会进入本地方法栈,调用本地方法接口 JNI

  • JNI作用:扩展java的使用,融合不同的语言为java所用

  • 它在内存区域中专门开辟了一块标记区域:本地方法栈,登机native方法;在最终执行的时候,通过JNI加载本地方法库中的方法

PC寄存器

每个线程都有一个程序计数器,是线程私有的

方法区

  • 方法区是被所有线程共享的,此区域属于共享区间

  • 静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法中,但是 实例变量存在堆内存中,和方法区无关

  • 栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放了

  • 对于线程来说,不存在垃圾回收问题

  • 栈主要存储:8大基本数据类型 + 对象引用 + 实例方法

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

  • 类加载器读取了类文件后,一般会把什么东西放在堆中?类,方法,常量,变量,所有引用类型的真实对象

  • 分区

    • 新生区(伊甸区):类诞生和成长的地方,甚至死亡

      • 伊甸园(E):所有对象都是在伊甸园中new出来的

      • 幸存0区(S0)

      • 幸存1区(S1)

    • 年老区

    • 永久区(元空间):常驻内存;用来存放jdk自身携带的Class对象。Interface元数据,存储的是java运行时的一些环境;这个区域不存在垃圾回收;关闭JVM虚拟机就会释放这个区域的内存

分析OOM的原因

  1. 先扩大运行内存,若扩大后运行成功,则说明内存不足,否则转第二步(-Xms 设置初始化内存分配大小;-Xmx 设置最大分配内存;-XX: +PrintGCDetails 打印gc信息)

  2. 使用辅助测试工具 Jprofiler

    • 分析Dump内存,快速定位内存泄漏(-XX:+HeapDumpOnOutOfMemoryError)

    • 获取堆中的数据

    • 获取大的对象

GC

  • 如何判断一个对象是否应该删除?

    GCRoot不可删除(被栈,本地方法栈或者方法区直接或间接引用),其余可删除

  • GC算法

    1. 引用计数法

    2. 复制算法

      • 流程:每次GC都会将Eden活的对象移到幸存区中:一旦Eden区被GC后,就会是空的;幸存区谁空谁是To;当一个对象经历了15次GC,都还没有死,就会移入年老区(-XX:MaxTenuringThreshold=9999 设定进入年老代的时间)

      • 优缺点:优点是没有内存碎片;缺点是浪费了内存空间,多了一半空间永远是空To

      • 最佳使用场景:对象存活度较低的时候(新生区)

    3. 标记清除算法

      • 流程:对活着的对象进行标记;对没有标记的对象,进行清除;

      • 优缺点:优点是不需要额外的空间;缺点是两次扫描,严重浪费时间,会产生内存碎片

      • 最佳使用场景:对象存活度较高的时候(养老区)

    4. 标记压缩算法

      • 流程:和标记清除相同,只是多了个压缩的过程:防止内存碎片产生,再次扫描,向一段移动存活的对象;多了一个移动成本

      • 优缺点:和标记清除相同并且多了个移动成本

      • 最佳使用场景:对象存活度较高的时候(养老区)

    GC算法效率对比:

    • 内存效率:复制算法 > 标记清除 > 标记压缩

    • 内存整齐度:复制算法 = 标记压缩 > 标记清除

    • 内存利用率:标记压缩算法 = 标记清除算法 > 复制算法



posted @ 2021-05-06 00:45  离渊灬  阅读(56)  评论(0)    收藏  举报