Java堆内存介绍及简单性能调优

Java底层最重要的一部分就是jvm堆内存,它影响着Java的性能。
这篇博客主要介绍Java堆内存的分区及简单的Java调优。

一、Java堆内存

首先看这张图:
在这里插入图片描述

  • 堆中的分区
    Java堆内存分为两部分:年轻代、老年代
    其中,年轻代分两个部分:Eden、Survivor

  • 内存分配
    老年代的内存占堆内总内存的2/3
    年轻代占1/3,在年轻代中,Eden分8/10,From和To都是1/10
    例:堆中总共300M空间,那么老年代有200M,年轻代有100M

  • 运行时到底是什么个情况呢?
    当我们new一个对象之后,首先,这个对象就会被放入Eden区,直到Eden区内存被占满

  • Eden满了之后:
    Eden满了后,就会触发垃圾回收机制gc,但是呢,在这个地方是minor gc。minor gc会回收Eden区的垃圾对象(没有任何指针引用程序的对象)
    JVM虚拟机内部构造图

  • 垃圾对象?
    可以理解为:当main函数结束时,栈帧区域会被销毁掉,然后局部变量也就被释放掉,那么指向堆中对象的指针也就被干掉了,最后,堆中的对象就是个垃圾对象。这里我们要注意一个点:查找垃圾对象的起始是从栈帧中开始的,然后查到的时一个局部变量

  • 可达性分析算法?GC Root?
    在垃圾对象里面说了,堆中的对象是由局部变量指出的,那么我们就可以利用它来判断是不是垃圾对象,这里的局部变量就可以理解为GC Root。可达性分析算法就是以GC Root为起点,往下搜索,找到的对象就不是垃圾对象,找不到的就是垃圾对象。

  • 接着minor gc
    上面已经说了,minor gc只是针对Eden区的。然后,当进行一次minor gc后,Eden中的垃圾对象全部被干掉,剩下的非垃圾对象,要进入Survivor区中的From中,这时候,这些非垃圾对象的分代年龄就会加一。

  • 分代年龄?对象头?
    分代年龄存放在对象的对象头中,每经历一轮的垃圾回收,分代年龄就会加一
    对象头:(https://blog.csdn.net/lkforce/article/details/81128115#1%EF%BC%8CMark%20Word

    1. Mark Word
    2. 指向类的指针
    3. 数组长度(只有数组对象才有)
  • 然后Eden区再次满的时候,又会触发垃圾回收,再次将垃圾对象(包括刚刚放入from中变成新垃圾的对象)干掉,把Eden和From中非垃圾对象放入To中,并将年龄加一。
    当下次Eden再满的时候,又会把Eden和To中的非垃圾对象放入From中,年龄加一,以此不断循环……

  • 所以年龄有什么卵用?
    当进行的次数多了,直到年龄达到了15(可以改这个参数),这时候就会被移到老年代

  • 上面已经说过,老年代也有一定的大小,那么如果老年代满的时候怎么办呢?
    这时候就会发生full gc

下面我们以代码为例,查看运行时的各区情况:
利用上篇微信红包中的main函数,使它进入死循环
(对微信红包算法感兴趣的可以看:https://blog.csdn.net/qq_44357371/article/details/103115263

public static void main(String[] args) {
		while (true) {
			test.thirdMethod(5, 20);
		}
	}

在这里插入图片描述
old就是代表老年区,我们可以看到Eden区是最快的,然后就发生垃圾回收……,和上面所述相符。
在这里插入图片描述
说实话,我想看old区被塞满的情况,可惜它增长的太慢了。。。

  • 接着full gc。STW?
    当发生full gc时,就会产生STW(stop world,,毁掉整个世界),(哈哈哈,没这么牛逼)。这时候呢,就会暂停所有的线程,让垃圾回收机制专心的回收垃圾。这样的话,用户端会卡掉。
  • 为什么要把线程停掉呢?
    考虑一种情况,如果在找垃圾时,刚好已经在某个链条上面了,这时候,如果线程把这条链给干掉了,那么后面的本来应该全部是垃圾的,但是gc没有把它们给找出来,所以停掉线程。

二、性能调优

  • Jvm性能调优到底调的是什么?
    我们上面已经知道,minor gc发生时,对性能的影响不大,但是full gc发生时,对性能的影响是巨大的。所以调优就是要减少full gc发生的次数,减少STW出现次数;还有就是在发生了Full gc时,所有的线程停掉等待垃圾回收,所以,减少垃圾回收时间。

下面举一个调优例子:
在这里插入图片描述

首先分析:
我们设置了堆的大小为3G,老年区就有2G,eden有800M,from和to各占100M
线程在运行时,每秒产生60M的对象,用后直接干掉变垃圾,这样大概每13秒就会把Eden占满,触发minor gc。
但是一个问题,在第13s产生的进程,会被直接移到survivor区,但是它的大小超过了survivor中一个区的一半,这时候触发对象动态年龄判断机制,直接进入老年代。
但是着60M的对象,在下一秒就又变成了垃圾。。。这样算下来,大概5、6分钟就会使老年代触发full gc,这样的效率是极其低下的。
所以呢,我们可以对这个系统进行一个调优:
把这些参数改一下,我们可以尽量让垃圾在年轻代就被干掉。
总共3G,把old区设置为1G(因为没那么多要放的),这样,新生代就有了2G,这样,Eden分了1.6G,from和to分别200M
这时候,每秒60M的对象过来,最后那一秒在Eden区中传入survivor中,但是根据对象动态年龄判断机制,60M小于from或to的一半,不会被传进老年代,变成垃圾,直接被minor gc干死,所以基本上,老年代就不可能被放满,所以就极大的改善了性能!!!

今晚收获巨大。jvm内容还多,路途且长,继续奋斗!
图片来源:诸葛老师

发布了45 篇原创文章 · 获赞 14 · 访问量 2475

猜你喜欢

转载自blog.csdn.net/qq_44357371/article/details/103192906