JVM 入门

2020/08/01 JavaSE 共 2495 字,约 8 分钟

JVM探究

这篇JVM笔记是看视频整理出来的,所以并不是特别详细透彻。想更深入了解的话推荐学习《深入理解Java虚拟机这本书》

1、 常见的jvm面试题

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

2、 知识点

  1. JVM的位置 :操作系统之上

  2. JVM体系结构

自己画的 jvm 简图:自己画的 jvm 简图

详细的 jvm 内存图:详细的 jvm 内存图

  1. 类加载器

类加载过程简图:类加载过程

类加载器有四类:等级依次递减

  • 虚拟机自带的加载器
  • 启动类(根)加载器
  • 扩展类加载器
  • 应用程序加载器
  1. 双亲委派机制 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

    作用:

    1. 防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
    2. 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
  2. 沙箱安全机制

  3. Native 凡是带了 native 关键字的,索命 java 的作用范围达不到了,会去调用底层C语言的库,大致流程为:
    • 执行到声明为 native 的方法时会进入 本地方法栈
    • 调用本地方法接口,即 JNI (java native interface)
    • 在最终执行的时候,通过 JNI 加载本地方法库中的方法
  4. 程序寄存器:Program Counter Register 每个线程都有一个pc,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,即将要执行的指令代码),让执行引擎读取下一条指令。 是一个非常小的空间,几乎可以忽略不计

  5. 方法区:Method Area 方法区是被所有线程共享。所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间。 静态变量、常量、类信息(构造方法,接口定义)、运行时的常量池存在方法区中,但是 实例变量存在堆内存中,和方法区无关

  6. 栈: 主管程序的运行,生命周期和线程同步,即线程结束,对应的栈内存释放。对于栈来说,不存在垃圾回收问题 栈中存放8大基本类型 + 对象的引用 + 实例的方法

  7. 三种JVM:了解即可
    • Sun公司:HotSpot
    • BEA : JRockit
    • IBM:J9vm
  8. 堆:Heap 一个JVM只有一个堆内存,堆内存的大小是可以调节的 类加载器读取了类文件后,会将我们所有引用类型的真实对象放入堆中。 堆还要细分为三个区:新生区、老年区、永久区。GC垃圾回收,主要在新生区和老年区 在JDK8以后,永久区改了个名字:元空间

  9. 新生区:对象诞生,成长的地方,也有可能是死亡的地方;研究表明:99%的对象都是临时对象。 新生区还可以分为三个区域:
    • 伊甸园区:所有对象都是在伊甸园区 new 出来的
    • 幸存区者(0,1),经历过轻GC在伊甸园区活下来的对象将进入幸存者区
  10. 老年区:在幸存者区经历过重GC活下来的将进入老年区

  11. 永久区:这个区域常驻内存。用来存放JDK自身携带的Class对象、Interface元数据。即存放的是java运行时的一些环境或类信息。这个区域不存在GC!关闭JVM就会释放这个区域的内存,该区域经历过几次变迁:
    • JDK1.7以前:称为永久代,常量池在方法区
    • JDK1.7:永久代,但是慢慢退化了,即去永久代,常量池在堆中
    • JDK1.7之后:无永久代,改为元空间,并将常量池放入元空间中。
  12. 堆内存调优:默认情况下,分配的总内存是电脑内存的1/4,而初始化内存是分配总内存的1/16。 常用调优命令: -Xms256m -Xmx1024m -XX:+PrintGCDetails ,改变分配总内存和初始化总内存的大小,并输出GC信息。 若碰见 OOM (OutOfMemoryError),解决思路为:
    • 尝试扩大堆内存
    • 分析内存,看一下是哪个地方出现了问题(专业工具:eclipse自带MAT,还有很好用的 Jprofiler)

MAT,Jprofiler 作用

  • 分析 Dump 文件,快速定位内存泄漏 (-XX:+HeapDumpOnOutOfMemoryError)
  • 获得堆中的数据
  • 获得大的对象
  • ….
  1. GC (常用算法)
  2. JMM:java内存模型JMM理解整理

3、 GC

JVM 在进行GC时,并不是对这三个区域(新生代,辛存区(from区,to区),老年区)统一回收,大部分时候,回收的都是新生代。

GC有两类:轻GC和重GC

GC常用算法: 标记清除法,标记压缩法,复制算法,引用计数法

3.1、 引用计数法(主流编程语言GC中,python使用的是此方法)

知乎:垃圾回收机制中,引用计数法是如何维护所有对象引用的?

3.2、 复制算法(新生区常用算法)

  • 复制过程)每次GC,都会将Eden区活的对象移到幸存区中:一旦Eden区被GC后,就是空的
  • 幸存区中的to区和from区不是固定的:哪个区为空,那这个就为to区
  • 当一个对象经历了15次GC(JVM默认值,可以修改)还没有死亡,那么对象进入老年区

好处: 没有内存碎片 坏处:浪费了一半的内存空间(to区永远是空的) 所以复制算法最佳使用场景为:对象存活度较低的时候,即新生区

3.3、标记清除算法

  • 优点:不需要额外的空间
  • 缺点:两次扫描(第一次标记,第二次清除),严重浪费时间,会产生内部碎片

3.4、标记清除压缩算法

  • 即在标记清除算法的基础上,多了一次压缩操作,将存活下来的对象都放在一起,消除了内部碎片。

3.5、 总结

  • 内存效率(时间复杂度):复制算法 > 标记清除算法 > 标记压缩算法
  • 内存整齐度:复制算法 = 标记压缩算法 > 标记清除算法
  • 内存利用率:标记压缩算法 = 标记清除算法 > 复制算法

没有最优的算法,只有最合适的算法 –> GC:分代收集算法 即: 年轻代:存活率低 -> 复制算法 老年代:存活率高 -> 标记清除算法 + 标记压缩算法 混合实现

文档信息

Search

    Table of Contents