JVM的基础知识整理

1 内存布局

JVM的基础知识整理

1.1 JVM 内存

  1. 线程私有
    1. 程序计数器
    2. 本地方法栈
    3. 虚拟机栈
  2. 线程共享
    1. 方法区
  3. 直接内存

1.2 对象内存布局

  1. 对象头
    1. mark word
    2. 类型指针
    3. 数组长度标识
  2. 实例数据
  3. 对齐

1.3 JVM 运行时内存

  1. 新生代
    1. 一个伊甸区
    2. 两个幸存区
  2. 老年代
  3. 永久代:1.8移除
  4. 元空间:1.8顶替永久代,不在虚拟机中,使用本地内存

1.4 分配内存方法

  1. 指针碰撞(规整时):空闲内存和使用内存间存放一个指针,每次分配内存就移动该指针

  2. 空闲列表(不规整时):维护一个列表,上面记录空闲内存。每次分配内存时,都会更新表上的记录

2 对象创建到死亡

JVM的基础知识整理

2.1 对象创建过程

  1. 先尝试在栈上分配
  2. 栈上无法分配且对象很大,则直接分配到老年代
  3. 若对象不是很大,先尝试在 TLAB 上分配(TLAB 线程分配本地缓冲区,在伊甸区上线程私有的一块内存)
  4. TLAB 上无法分配,则分配在伊甸区
  5. 伊甸区经过一次 YGC,到幸存区
  6. 幸存区经过多次 YGC,年龄达到(默认值为15),进入老年代

2.2 动态年龄

  1. 幸存区相同年龄的对象大于幸存区一半时
  2. 年龄大于或等于上述年龄的对象直接进入老年代

2.3 分配担保

在一次 YGC 时,幸存区空间不够,直接进入老年代

2.4 对象死亡过程

两次标记

  1. 第一次标记:根不可达
  2. 放置在 F-Queue 队列中;覆盖finalize()方法,且没有调用finalize()方法,暂时不死
  3. 第二次标记:对F-Queue队列中仍未被引用的对象标记
  4. 被二次标记后,死亡

3 垃圾回收

3.1 如何确定垃圾

  1. 引用计数法
    1. 在对象中添加一个引用计数器
    2. 每当有一次引用时+1,引用失效时-1
    3. 当计数器为0时对象没有被使用。
    4. 存在问题:无法解决循环引用
  2. 可达性分析(Java虚拟机使用)
    1. 对象到 GCRoots 间没有任何引用链相连,表明该对象没有被使用
    2. 根对象:线程栈变量 静态变量 常量池 JNI指针

3.2 垃圾回收算法

  1. 标记清除,内存碎片
  2. 标记整理,整理耗时
  3. 拷贝算法,空间利用率低
  4. 分代收集
    1. 新生代:朝生夕灭 -> 标记整理 -> Appel 式回收
    2. 老年代:老不死 -> 标记清除或标记整理

3.3 四种引用类型

  • 强引用:对象赋值给引用变量 Object o = new Object()
  • 软引用:SoftReference 类实现,内存不足时才会被回收
  • 弱引用:WeakReference 类实现,只要垃圾回收器触发就会被回收
  • 虚引用:PhantomReference 类实现,不能单独使用,主要作用是跟踪对象被垃圾回收的状态

3.4 分区收集算法

  1. 将整个堆空间划分为连续的不同小区间,
  2. 每个小区间独立使用、独立回收
  3. 合理回收区间,从而减少一次GC造成的STW时间

3.5 常见垃圾回收器

JVM的基础知识整理

  1. Serial:单线程、复制算法
  2. ParNew:多线程、复制算法
  3. Parallel Scavenge:多线程、复制算法,注重 吞吐量
  4. Serial Old:单线程、标记整理算法
  5. Parallel Old:多线程、标记整理算法
  6. CMS:多线程、标记清除算法
  7. G1之后不使用分代收集理论,使用 分区收集算法

4 类加载

4.1 JVM 类加载过程

  1. 加载:这个阶段会在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类的各种数据的入口
  2. 验证:确保符合虚拟机要求
  3. 准备:类变量(静态变量)分配内存和赋初始值
  4. 解析:将常量池中的符号引用替换为直接引用的过程
  5. 初始化:执行类构造器

4.2 符号引用与直接引用

  1. 符号引用:符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中
  2. 直接引用:指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄,引用目标一定在内存中

4.3 不会执行类初始化

  1. 子类引用父类静态字段,只会初始化父类
  2. 定义对象数组
  3. 常量
  4. 通过类名获取 Class 对象
  5. 通过Class.forName 加载指定类时,指定参数 initialize 为 false 时
  6. 通过 ClassLoader 默认的 loadClass 方法

4.4 类加载器

  1. 启动类加载器: JAVA_HOME\lib
  2. 扩展类加载器:JAVA_HOME\lib\ext
  3. 应用程序类加载器:用户路径(classpath)上的类库
  4. 自定义类加载器:继承 ClassLoader,重写 findClass 方法

4.5 双亲委派机制

JVM的基础知识整理

  1. 自底向上检查该类是否已被加载
  2. 自顶向下进行实际的查找与加载
  3. 总而言之,委托父类加载器加载,除非父类加载器无法加载,才会被子类加载器加载
  4. 好处,保证使用不同的类加载器,都能获得相同的对象