Java栈与堆的关系,JVM GC基本原理

Java栈与堆

     Java程序运行时,主要涉及到的内存区域有四部分(还有其他部分不做详解):栈内存、堆内存、方法区以及常量池。

1、栈内存:栈中主要保存的信息为:基本数据类型变量的值(short int long float double char boolean byte)、对象的引用(对象保存在堆中)

    栈有如下几个特点:

  • 栈内存中所保存的变量有两个特点,生存期确定和占用内存大小确定。(生存期结束后会立马释放所占用的内存空间)
  • 栈内存的存取速度非常快,仅次于寄存器。
  • 栈内存中保存的变量是共享的。(int a=3,b=3  a==b返回true)

2、堆内存:堆中主要保存的信息为:通过new关键字生成的对象,以及数组

    堆有如下几个特点:

  • 堆内存中保存的变量大小不固定,生存期也不固定。(由JVM GC garbage collector进行垃圾回收)
  • 堆内存的存取速度不如栈内存。

3、常量池:常量池是指在编译器确定,并被保存在已编译的.class文件中的一些数据,除了包含在代码中的基本数据类型变量还包含对象的常量值(final)。

     常量池的特点:

  • 常量池中的变量不是保存在堆内存中,则是保存在方法区中。
  • 常量池中的变量是共享的

下图以String字符串来讲解栈、堆、常量池三者之间的关系

                String a = "xd";
String b = "xd";
String aa = new String("xd");
String bb = new String("xd");

System.out.println(a==b);//true

System.out.println(aa==bb);//false

对于变量a和b,在对代码进行编译期间,会将变量的值"xd"存入常量池中(常量池中仅保存一个变量),因此a和b的地址(对象的引用存入栈内存中)是一致的,都是指向常量池中的"xd",而aa和bb变量的地址分别指向new String("xd")产生的两个对象,因此aa和bb的地址不是一致的。他会先去常量池中查看是否存在"xd"这个常量,如果有则直接将它进行复制,然后存入堆内存中。

关于常量池的讲解,借鉴了下面这个博客:

https://www.cnblogs.com/SaraMoring/p/5687466.html

总之,常量池中存放的都是在编译期间就能确定的变量。如通过双引号String a = "xx";产生的字符串,final关键字修饰的变量,以及8中基本数据类型变量。(有一个技巧,就是通过反编译工具将.class文件进行反编译,会发现很多变量,直接显示的是值,而非变量本身,如final 关键字修饰的变量,这种变量就是存在常量池中的变量)

下图中主要讲解的是Java中8中基本类型和它们对应的封装类型(对象类型)(Integer、Short、Long、Float、Double、Char、Boolean、Byte),下面将主要讲解装箱和拆箱操作。

    由如下代码:

                int a = 3;
Integer b = 3;
Integer c = 3;
Integer d = new Integer(3);
Integer e = new Integer(3);
System.out.println(a == b);//true
System.out.println(a == d);//true
System.out.println(b == c);//true
System.out.println(b == d);//false

System.out.println(d == e);//false

1、int与Integer类型的数据进行比较的时候,Integer都会先将变量转化为int类型再进行比较。因此为true

2、同理1

3、Integer类型数据存在一个常量池(-128到127的数据保存在里面),而常量池中只会保存一个对象,因此,所有指向常量池中这个对象的地址都是相同的,故b=3。

4、由上面可知道,凡是new出来的对象,都是存放在堆内存当中,故该对象与常量池中的对象不是同一个对象,因此b==d为false。

5、凡是new出来的对象,都是一个新对象存放在堆内存中的,因此d==e为false。


JVM 垃圾回收机制(GC)

    首先,要知道一点就是,对于栈中保存的变量,由于它的生存期跟大小都是确定的,因此,当该变量生存期到了之后,会自动释放掉所占用的内存空间。一般我们讲到的GC或者Java 垃圾回收机制都是针对于堆内存而言的,只有堆内存中的变量生存期和大小是不确定的,需要JVM 进行垃圾回收。

    堆内存保存变量的区域按照变量的状态可以划分为三个区域:

  • 新域:存储所有新生对象。(包含三个空间,Eden区、Survivor1区、Survicor2区),新域叫做Young Generation 简称YG,发生在新域的垃圾回收叫做Minor GC。
  • 旧域:对新域中的对象进行一定次数的GC循环后仍然无法回收则转移至旧域。旧域叫做Older Generation 简称OG,发生在旧域的垃圾回收称为Major GC。
  • 永久域:存储类和方法对象。这个区域是独立的,默认为4M,不包括在JVM堆内。

Minor GC:JVM最新产生的对象存放在Eden区,当Eden区被占满了。(对象太多了),此时Minor GC开始工作,这个时候应用程序将停止运行,进行垃圾回收(找不到的对象),将所有可以找到的对象,移至Survivor1区,当Survivor1区占满了后,这个时候Minor GC又开始工作,将Survivor1区内可以找到的对象移至Survivor2区,未找到的对象进行回收。同理,当Survivor2区占满了后,Minor GC又将Survivor2区找到的对象移至Survivor1区。像这样反复GC几次(次数由JVM决定)之后,如果仍然没有被回收掉的对象会被移至Older 区(旧域OG),进行Major GC。

Major GC:对于旧域,采用的是tracing算法的一种,称为标记-清除-压缩收 集器,注意,这有一个压缩,这是个开销挺大的操作。(不一定要OG满,OG满了后触发Full GC)

注:

  • Minor GC和Major GC分别对于YG和OG的垃圾回收,互不干涉。
  • GC区域包括:[Older Generation][Young Generation]。[Young Generation]=[Eden][Survivor1][Survivor2]
  • 当Eden或Survivor1或Survivor2区域满了后,触发Minor GC,经过几次GC后无法清理掉的对象移动至OG。
  • 当Older Generation 满了后,触发Full GC,Full GC很消耗内存,他会吧OG、YG中的大部分垃圾回收掉。这个时候用户线程会被block。

总结:

  1. JVM堆的大小决定了GC的运行时间,如果JVM堆的大小超过一定限度,那么GC的时间会很长。
  2. 对象生存的时间越长,GC需要的回收时间也越长,影响了回收速度。
  3. 大多数对象都是短命的,所以如果能够让这些对象的生存期在GC的一次运行周期内,最好。
  4. 应用程序中,建立与释放对象的速度决定了垃圾回收的频率。
  5. 如果GC一次运行的周期超过了3-5秒,这会很影响应用程序的运行,如果可以应该减小堆的大小。
  6. 前辈经验之谈,通常情况下 JVM堆的大小应该为物理内存的80%。






猜你喜欢

转载自blog.csdn.net/qq824570123/article/details/80184348