jvm内存数据管理及GC算法的

前言

JVM不仅承担Java字节码的分析和执行,同时也内置了自动内存分配管理机制。我们在Java开发过程中,有时过度依赖于JVM内存管理自动化,弱化对内存的管理,这样系统很容易发生JVM异常。所以我们需要深入了解JVM内存分配和回收原理,这样在遇到问题时才能通过日志分析快速定位问题。

java内存管理

请输入图片地址

运行时数据区域 java虚拟机在程序运行中会将内存划分为不同的数据区域,这些区域有着不同的用处和不同的生存时间

在Java6版本中,方法区所有数据(除了JIT编译代码在native memory)都在永久代,永久代在非堆内存区

到了Java7版本,runtime constant pool 还是在permGen;SymbolTable 从永久代到了native memory;StringTable,静态变量从PermGen移动到 Java Heap ;

而到了Java8,永久代被元空间取代了(之前永久代类元数据存储在元空间,而静态变量和常量还是在堆)

元空间 used:加载的类的空间量。 capacity: 当前分配块的元数据的空间,那些被实际分配的Chunk大小之和。 committed: 空间块的数量,被commit的Chunk大小之和。 reserved:元数据的空间保留(但不一定提交)的量,jvm启动时根据参数和操作系统预留的内存大小。

当使用一个classLoader加载一个类时,过程如下: 1、当前classLoader是否有对应的Chunk且有足够的空间。 2、查找空闲列表中的有没有空闲的Chunk。 3、如果没有,就从当前虚拟空间中分配一个新的Chunk,这个时候会把对应的内存进行Commit,这个动作就是提交。 4、如果当前虚拟空间不足,则预留(reserves)一个新的虚拟空间。

为什么移除永久代

1官方解释,是为了融合HotSpot与JRockit VM。2永久代回收效率偏低,PermSize大小很难确定分配多大,很容易OutOfMemory

JMM定义的变量 JMM定义的变量包括实例字段,静态字段,构成数组对象,而局部变量,方法参数这些时线程私有的,所以不会发生线程安全问题。

主内存和工作内存 所有定义变量存储在主内存,每条线程都有自己的工作内存,用于拷贝主内存的数据,线程对变量的读取,赋值操作都在工作内存进行。工作内存对应物理位置应该是在CPU寄存器和CPU高速缓存

java对象访问定位方式

java通过在栈上的reference数据(执行对象的指针)来操作堆上的具体对象

java对象存活判断

java引用类型

回收标记

java在对象回收过程中会进行两次标记,对象失去链接–>标记–>是否拯救自己(finalsize or not)对象重写建立引用–>没有建立新引用的对象进行二次标记 –> GC回收

graph LR
    A[对象失去可达性] --> B(标记回收)
    B --> C{是否拯救自己-finalsize块}
    C -->|是| D[建立引用]
    C -->|否| E[二次标记] --> F(GC回收)

请输入图片地址

jvm内存回收

方法区回收 方法区,HotSpot也叫做永久代,通常这个区域回收的性价比比较低,这个区域回收的内容主要是废弃常量无用的类 方法区判断可以回收的方法:

  1. 该类所有的实例被回收
  2. 加载该类的classLocader被回收
  3. 在任何地方该类class对象无引用,无法在任何地方通过反射访问该类方法。

如果使用大量反射,动态代理的框架,地方都应该配置-Xnoclassgc参数进行控制,关闭CLASS的垃圾回收功能

垃圾回收算法

枚举根节点

可达性分析应建立在能确保一致性的快照中进行,确保对象引用关系不会在变化,通常会采用Stop The world操作,停止所有执行线程。而GC停顿时长是敏感的,在HotSpot中使用OopMap数据结构在类加载时就计算对象内的所有类型数据的偏移量,记录栈和寄存器引用。从而实现准确,快速的GS Roots节点的获取

安全点

当JVM进行GC时,要保证所有执行执行线程都处于一个安全的位置,这个位置叫做安全点,而为了保证所有执行线程处于安全点有两种方式

  1. 抢断式中断 jvmGC时停止所有执行线程,如果发现有执行线程不在安全点上,恢复其线程,到达安全点。
  2. 主动式中断 设置一个标志,执行线程主动轮询这个标志,如果标志位置和安全点位置重合,则挂起线程

安全区域

安全点的设置会导致不太长时间就会上面一系列操作,效率会很低。安全区域指一段代码片段之中,引用关系不会发生变化,这个区域任何时候GC都是安全的。当线程进入安全区域是标记自己进入,当jvmGC时,不用关心这个线程;线程当离开安全区域时,检查GC是否完成,未完成则等待释放信号

内存分配,回收策略

优先分配到Eden区

大对象直接进入老年代

graph LR
    A(对象堆内存分配) --> B[分配Eden区]
    B --> J{是否大对象-字符串-数组}
    J -->|是| K[直接进入老年代]
    J -->|否| C{Eden区是否足够}
    C -->|是| D[分配]
    C -->|否| E{老年代最大可用连续空间 > Eden所有对象空间}
    E -->|是| G[minor GC]
    E -->|否| F{HandlePromotionFailure担保失败配置是否开启}
    F -->|否| H[Full GC]
    F -->|是| I{老年代最大可用连续空间 > 历次晋升老年代对象平均大小}
    I -->|否| H[Full GC]
    I -->|是| G[minor GC]

请输入图片地址

长期存活对象直接进入老年代

动态对象年龄判断

graph LR
    A(minor GC) --> B[对象存活--进入survivor区--Age+1]
    B --> C{是否Age值达到进入老年代Age--动态年龄判断--survivor区超过一半对象Age为标准}
    C --> |是| D[直接进入老年代] 
    C --> |否| A(minor GC)

请输入图片地址