JavaJVM


JVM探究

JVM的学习方法

面试题:

  • 请你谈谈你对JVM的理解?Java8虚拟机和之前相比有什么变化?
  • 什么是OOM?什么是栈溢出StackoverflowError?怎么分析?
  • JVM的常用调优参数有哪些?
  • 内存快照如何抓取,怎么分析Dump文件?知道吗?
  • 谈谈JVM中,你对类加载器的认识?

要素:

  1. JVM的位置

    JVM的位置.png
  2. JVM的体系结构

    JVM体系结构.png

    JVM体系结构2.png

    JVM体系结构3.png

  3. 类加载器

    作用:加载 Class字节码文件

    类加载器.png

    加载器分类

    1. 虚拟机自带的加载器

    2. 启动类(根)加载器

    3. 扩展类加载器

    4. 应用程序加载器

  4. 双亲委派机制

    作用:保证安全。APP--》EXT--》BOOT

    自己定义的包和系统包冲突的时候,会用系统包。

    也就是说,java会向上检查是否存在这个包,如果上面有这个包就用上面的,不会用你自己写的包。

    类加载步骤:

    1. 类加载器收到类加载的请求!
    2. 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
    3. 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,通知子加载器进行加载
    4. 重复步骤 3

    ClassNotFound

    null:java调用不到,底层是用C/C++写的

  5. 沙箱安全机制

    一:什么是沙箱?

    Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

    所有的Java程序运行都可以指定沙箱,可以定制安全策略。

    首先进行一个字节码校验器:确保java类文件遵循java语言规范,这样可以帮助java程序实现内存保护,但并不是所有的类文件都会经过字节码校验,比如核心类。

    接下来类装载器:其中类装载器在3个方面对java沙箱起作用

    1. 它防止恶意代码去干涉善意的代码。//双亲委派机制

    2.它守护了被信任的类库边界。//双亲委派机制

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

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

    类装载器采用的机制是双亲委派模式

    二:java类运行

    第一步:编译

    利用编译器将源程序编译成字节码文件;源文件名.class

    第二步:运行

    利用虚拟机(解释器,java)解析执行class字节码文件

    img

  6. Native,被Native修饰的方法,底层是由C/C++实现。

  7. PC寄存器

    程序计数器:Program Counter Register

    每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

  8. 方法区

    方法区被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单来说,所有定义的方法信息都保存在该区域,此区域属于共享区域。

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

  9. 栈:数据结构

    程序 = 数据结构 + 算法

    栈:先进后出,后进先出。比如弹夹。

    队列:先进先出,比如水管。

    栈:占内存,管理程序的运行,生命周期和线程同步。线程结束,栈内存也就释放,对于栈来说,不存在垃圾回收问题,一旦线程结束,栈就结束。

    栈一般存放8大基本类型、对象引用、实例方法

    堆、栈、方法区交互图:

    栈.png

  10. 三种JVM

    SUN公司:HotSpot,最流行最常用

    BEA公司:JRockit JVM

    IBM公司:J9VM

  11. Heap,一个JVM只有一个堆内存,并且堆内存的大小是可以调整的!

    类加载器读取了类文件后,一般会把什么大小放到堆中?

    堆内存分为三个区域:

    • 新生区

    • 老年区

    • 永久区

    垃圾回收.png

    GC垃圾回收,主要发生在新生区和老年区

    假如内存满了,就会出现OOM,说明堆内存不够!OutOfMemoryError

    在JDK8以后,永久存储区改了一个名字,叫做元空间。

  12. 新生区、老年区、永久区

    新生区

    • 类:诞生和成长的地方,可能死亡
    • 伊甸园:所有对象都是在伊甸园区new出来的!
    • 幸存区:分为幸存者0区和幸存者1区

    新生区.png

    伊甸园满了就用轻GC,活下来的放入幸存区。幸存区满了就用重GC,活下来的放入老年区。

    老年区

    经过研究,98%的对象都是临时对象!

    永久区

    这个区域是常驻内存的。用来存放JDK自带的Class对象。如Interface元数据,存储的是Java运行时的一些环境。这个区域不存在垃圾回收!关闭虚拟机就会释放这个区域的内存。

    永久区崩溃的情景:

    1. 一个启动类加载了大量的第三方jar包。
    2. Tomcat部署了太多的应用。
    3. 大量动态生成的反射类,不断被加载,直到内存满,就会出现OOM。

    版本历史

    • JDK1.6之前:有永久代,常量池在方法区。
    • JDK1.7:有永久代,但是正在退化,提出了去永久代,常量池在堆中。
    • JDK1.8之后:没有永久代,常量池在元空间。

    伊甸园.png

    元空间:逻辑上存在,物理上不存在。

    在一个项目中,突然出现了OOM故障,该如何排错?

    • 看代码第几行出错:使用内存快照分析工具,Jprofiler
    • Debug:一行一行分析代码!

    MAT,Jprofiler作用

    • 分析Dump内存文件,快速定位内存泄漏
    • 获得堆中的数据
    • 获得大的对象
  13. 堆内存调优

  14. GC

    1. 常用算法

    标记清除法、标记整理法、复制算法、分代收集算法

    引用计数法:

    引用计数法.png

    复制算法:

    复制算法.png

    复制算法2.png

    • 好处:没有内存的碎片

    • 坏处:浪费了内存空间,一半空间永远是空to

    复制算法最佳使用场景:对象存活度较低的时候,新生区

    标记清除算法:

    标记清除算法.png

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

    标记压缩算法:

    标记压缩算法.png

  15. JMM

    Java内存模型

  16. 总结

学习途径:

  1. 搜索引擎
  2. 通过前人总结的方法,思维导图等

总结

内存效率:复制算法 > 标记清除算法 > 标记压缩算法(时间复杂度)

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

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

思考一个问题:难道没有最优算法吗?

答案:没有,没有最好的算法,只有最合适的算法 --> GC:分代收集算法

年轻代:

  • 存活率低
  • 采用复制算法

老年代:

  • 区域大:存活率高
  • 采用标记清除(内存碎片不是太多) + 标记压缩混合 实现