# 一、内存分配

# 1. 方法区、永久代和元空间的区别?

方法区:(逻辑上)

  • 逻辑上的东西,是JVM 的规范,所有虚拟机必须遵守的。
  • 是JVM 所有线程共享的、用于存储类的信息、常量池、方法数据、方法代码等。

永久代:(方法区的实现、JDK7及之前、主要是和元空间对比)

  • PermGen , 就是 PermGen space ,全称是 Permanent Generation space ,是指内存的永久保存区域。
  • PermGen space 则是 HotSpot 虚拟机基于JVM规范对方法区的一个落地实现,并且只有 HotSpot 才有 PermGen space。
  • 而如 JRockit(Oracle)、J9(IBM) 虚拟机有方法区 ,但是就没有 PermGen space。
  • PermGen space 是JDK7及之前, HotSpot虚拟机对方法区的一个落地实现。在JDK8被移除。

元空间:(Metaspace,JDK8及之后):

  • 元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
  • 移除PermGen(永久代)从从JDK7 就开始。例如,字符串内部池,已经在JDK7 中从永久代中移除。直到JDK8 的发布将宣告 PermGen(永久代)的终结。
  • 其实,移除 PermGen 的工作从 JDK7 就开始,永久代的部分数据就已经转移到了 Java Heap 或者是 Native Heap。
  • 但永久代仍存在于JDK7 中,并没完全移除,比如:
    • 字面量 (interned strings)转移到 Java heap;
    • 类的静态变量(class statics)转移到Java heap ;
    • 符号引用(Symbols) 转移到 Native heap ;

image-20210314091532064

# 2. 为什么使用元空间替换永久代?

① 由于永久代内存经常不够用或者发生内存泄露,报出异常 java.lang.OutOfMemoryError: PermGen 。

② 字符串存在永久代中,容易出现性能问题和内存溢出。

③ 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

④ 永久代会为 GC 带来不必要的复杂度,而且回收效率偏低。

⑤ Oracle可能会将HotSpot和JRockit合二为一。

# 3. JVM 内存结构

image-20210314091712445

# 4. Java 内存模型

  • 重排序
  • 可见性
  • 原子性

# 5. Java 对象模型

以 HotSpot 虚拟机为例,对象在内存中的布局分为以下 3 个部分:

  • 对象头:
    • Mark Word:存储对象自身的运行数据。如:Hashcode、GC 分代年龄、锁状态标志等。
    • 类型指针:对象指向它的类元数据的指针。
  • 实例数据:
    • 真正存放对象实例数据的地方。
  • 对齐填充:
    • 这部分不一定存在,也没有什么特别含义,仅仅是占位符。因为 HotSpot 要求对象起始地址都是 8 字节的整数倍,如果不是,就对齐。
image-20210225151510797

# 6. JVM 的数据区有哪些?作用是什么?

image-20210314091712445

# 7. Java 堆内存一定是线程共享的吗?

不一定。

为对象分配内存的基本方法:

  • 指针碰撞法

    假设堆内存是完整的,没有什么碎片。指针移动需要的空间的大小的距离,然后分配。

  • 空闲列表法

    列表记录可用的内存信息,然后从列表中找到足够大的空间来分配对象。

所以当多个线程要为对象分配内存的时候,可能存在前一个线程已经把内存占了但是后一个线程还不知道的情况下,就导致了内存分配冲突。

内存分配并发问题的解决:

  • CAS:乐观锁,假设没问题,然后分配,冲突了就重新分配,直到没冲突。
  • TLAB:线程本地分配缓存区,JVM 为每一条线程分配一块区域,该线程在 new 对象的时候就在这块区域进行分配。当 TLAB 内存用完了之后再采用 CAS 进行分配。(分对象的时候线程独享,用对象的时候线程共享)

# 8. JVM 的堆内存结构是怎样的?哪些情况会触发 GC?会触发哪些 GC?

堆内存结构

  • 整个堆大小 = 新生代 + 老年代
  • 新生代 = Eden + 存活区
image-20210225150555532
  • 新生代用来放新分配的对象。新生代中经过垃圾回收,没有回收掉的对象,会被复制到老年代。
  • 老年代存储对象一般比新生代存储对象的年龄大得多。
  • 老年代存储一下大对象。

从前的永久代是用来存放 Class、Method 等元信息的区域, 从 JDK8 开始就去掉了。取而代之的是元空间(MetaSpace),元空间并不在虚拟机里面,而是直接使用本地内存。

哪些情况会触发 GC

当 JVM 无法为一个新的对象分配空间时会触发 Minor GC。

虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间

1、如果大于的话,直接执行minorGC

2、如果小于,判断是否开启 HandlerPromotionFailure,没有开启直接 FullGC

​ (promotion failed是在进行Minor GC时,survivor space放不下,对象只能放入旧生代,而此时旧生代也放不下造成的;)

  • 如果开启了HanlerPromotionFailure,JVM会判断老年代的最大连续内存空间是否大于历次晋升的大小
    • 如果小于直接执行FullGC
    • 如果大于的话,执行minorGC

Minor GC是新生代GC,指的是发生在新生代的垃圾收集动作。由于java对象大都是朝生夕死的,所以Minor GC非常频繁,一般回收速度也比较快。

上次更新: 8/28/2022, 11:43:26 PM