关于你需要知道的JVM基础

Java对象的内存布局

  • 对象头

    • class对象指针

    • markword(64个bit位)

      结构:

      • 哈希码:对象的哈希码,用于支持基于哈希的集合操作
      • GC分代年龄:对象的分代年龄,用于垃圾回收器的分代收集策略
      • 锁状态的标识:用于标识对象的锁状态,如未锁定、轻量级锁定、重量级锁定等。
      • 偏向线程ID(在偏向锁的状态下):记录持有偏向锁的线程ID
      • 锁记录指针(在轻量级锁的状态下):指向当前线程栈中Lock Recent的指针

      作用:

      • 帮助垃圾回收机制:通过存储对象的分代年龄等信息,协助JVM的垃圾回收机制(GC)进行对象的回收决策。
      • 支持线程同步:存储锁状态标志、线程持有锁、偏向线程等信息,支持JVM实现多种锁机制,如偏向锁、轻量级锁和重量级锁,以提高并发性能。

      锁机制

      • 无锁:通过原子操作来避免所得适用,如一个线程在执行
      • 偏向锁:针对大部分时间仅有一个线程访问同步代码块的情形设计,当某个线程首次取得锁的时候,MarkWord会被更新记录下该线程的ID,之后此线程再次请求相同锁的时候,会直接越过同步操作。
      • 自旋锁(轻量级锁):在偏向锁不足以应对多线程竞争,但锁保持时间较短情况下的优化手段。轻量级锁通过自旋方式尝试获取锁,避免了线程阻塞和上下文切换的开销
      • 重量级锁:当轻量级锁的自选多次仍然无法获取锁时,锁会升级为重量级锁,此时线程会调用操作系统的互斥原语来阻塞线程,待锁释放后在唤醒相应等待的线程。

      前三种均在用户态,最后一个在内核态

  • 实例数据

    对象的实际数据

  • 对齐填充

JVM内存结构

线程私有

  • 程序计数器:记录代码执行的位置
  • Java虚拟机栈:每个线程都有自己的Java虚拟机栈,并且java开启线程都会开启一个虚拟机栈,默认大小为1M,加载的是==普通方法==
  • 本地方法栈:每个线程都有自己的本地方法栈,和Java虚拟机类似,Java虚拟机栈加载的是普通方法,本地方法加载的==native修饰==的方法

线程共享

  • 堆:无论是通过反射还是new出来的对象都放在堆中

  • 元空间:常量池,跟类有关的均在元空间里面。

    (JDK1.8之前叫方法区)

    元数据存在本地内存中,所以元空间的内存上限取决于内存大小,但是元数据在虚拟机启动时会默认给个大小。

关于你需要知道的JVM基础_第1张图片

堆内存

堆内存结构

  • Young年轻代(年轻代内存=S0内存大小+S1内存大小+Eden内存大小)

    • S0,S1
    • Eden

    注:新创建的对象都会存放在年轻代里面,其中S0和S1的大小一致

  • Tenured老年代(一般空间比年轻代大)

    • 经过年轻代垃圾回收机制存活下来的对象存在老年代中。

GC垃圾回收

注:线程私有的不存在垃圾回收机制,而针对线程共享的只有堆需要垃圾回收机制,回收堆中的对象,防止堆内存溢出。

JVM的垃圾回收大致分两步走:

  1. 如何发现垃圾
  2. 如何回收垃圾

如何发现垃圾

常见的垃圾回收算法有两种:引用计数算法和根搜索算法

①引用计数算法

核心思想:堆中的对象每被引用一次,则计数器加1,每减少一个引用就减1,当对象的引用计数器为0时就可以被当做垃圾进行回收。

优点:比较快

缺点:无法检测出堆内对象之间的循环引用,它们引用计数器永远不可能为0


②根搜索算法(根可达性分析)

核心思想:根搜索算法是把所有的引用关系看做一张图,从节点==GCROOT开始,寻找对应的引用节点==,根据找到的节点,再找到引用该节点的引用节点,当所有的引用节点寻找完毕,剩余的节点则被认为是没有被引用的节点,即可将其当成垃圾进行回收。

Java中可作为==GCROOT==的对象有:

  • java虚拟机栈中的引用对象
  • 本地方法栈引用的对象
  • 元空间中静态属性引用的对象
  • 元空间中常量引用的对象

如何回收垃圾

Java中用于常见的垃圾回收算法有4种:

①标记-清除算法(markandsweep)

该算法分为标记清除两个阶段:

  • 标记:首先需要标记所有要回收的对象。
  • 清除:然后在标记完成后统一回收所有被标记的对象。

缺点:效率不高,而且在标记清除后会产生大量不连续的内存碎片。


②标记-整理算法(Mark-Compact Algorithm)

该算法是在标记-清除算法的基础上做了改进,同样是两阶段:

  • 标记:其标记阶段与标记-整理算法是相同的。

  • 清理:在标记完成后并未对可回收的对象进行清理,而是让存活的对象向一边移动,在移动的过程中清理掉可回收的对象,这个过程叫做整理。

    优点:内存被整理后不会产生大量的不连续内存碎片

    缺点:耗时耗力


③复制算法(copying)

该算法是将可用的内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存用完了,将还存活的对象复制到另一块内存上,然后将使用过的内存空间一次清理掉。

缺点:可使用的内存只有原来的一半。在某一时刻点,总有一个S 的内存空间是空的,可能是S0,也可能是S1。


④分代收集算法(generation)

分代收集算法是当前主流的JVM都采用的算法,这种算法会根据对象存活周期的不同将内存划分为年轻代、年老代,不同生命周期的对象可以采用不同的回收算法,以便提高回收效率。

  1. 年轻代(YoungGeneration)
    • 所有新生成的对象都放在新生代的。
    • 新生代内存按照8:1:1的比例分为eden区和两个Survivor(S0,S1)区,大部分对象在Eden区中生成。回收时先将Eden区中存活的对象复制到一个Survivor0区,然后清空Eden区,当这个Survivor0区,也存放满时,则将Eden区和Survivor0区存活对象复制到另一个Survivor1区,然后清空Eden和这个Survivor0。此时的Survivor0区是空的,然后将Survivor0区和Survivor1区交换,即保持Survivor1区为空。如此往复。
    • 特殊情况:当一个大对象不足于存放到Eden区时,就将存活对象直接存放到年老代中。若是年老代也满了就触发一次FullGC,也就是新生代和年老代都进行回收。
    • 新生代发生的GC也叫做MinorGC,MinorGC发生的频率比较高。
  2. 年老代(OldGeneration)
    • 在年轻代经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代存放的都是一些生命周期比较长的对象。默认是15次
    • 内存新生代也很多,一般是新生代的2倍,当老年代内存存满时触发MojorGC,即FullGC,但是FullGC发生频率比较低,老年代对象存活时间比较长,存活率比较高。
  3. 云空间-持久代(PermanentGeneration)
    • 用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,从JDK8以后已经废弃,将存放静态文件,如Java类、方法等这些存储到了元数据区。

JVM调优参数

-Xms8g: 设置JVM中堆初始堆大小为8g。此值可以设置与-Xmx相同,当内存打满的时候,会先触发FullGC再申请内存。并且以避免每次垃圾回收完成后JVM重新分配内存。

-Xmx8g: 设置JVM中堆最大可用内存为8g。

-Xmn4g: 设置年轻代大小为4G。

-XX:MaxMetaspaceSize=128m: 设置元空间最大为为128m

-XX:MetaspaceSize=128m 用于设置元空间的初始大小,默认值约21

注:

-X:标准选项

-XX:非标准选项

m:memory

s:size

x:max

n:new

你可能感兴趣的:(jvm)