对JVM中堆、栈、方法区的初步认识

(一)GC垃圾回收器

​ 在java中,程序员是不需要去自己释放一些内存,由虚拟机自行回收(执行),在jvm有一个垃圾回收线程,它的优先级是低优先级,在正常情况下不会执行,只有在虚拟机空闲的时候或者当前内存不足的时候执行,它会扫描没有被任何引用的对象,并将它们添加到要回收的集合当中,进行回收。

(二)堆(Heap)

  • 一个JVM实例只存在一个堆内存,堆内存大小是可以调节的;
  • 类加载并不是顺序执行,“并发” 执行
  • 一个类经过编译后通过一个类的全限定名来获取其定义的二进制字节流 ,然后将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中这些数据的访问入口。
  • 类加载过程:加载、连接过程(验证、准备、解析)、初始化、使用、卸载
  • 第一次使用到该类的时候:先进行类加载,也是唯一一次
2.1.堆的逻辑分类
2.1.1.新生代

​ 占据堆内存的三分之一,经常询问是否垃圾回收,Minor GC。

  • Eden区:

    ​ 占据新生代内存的百分之八十,存储新创建的对象。Java新创建的对象绝大部分会分配在Eden区(如果对象太大,则直接分配到老年代)。当Eden区内存不够的时候,就会触发MinorGC(在新生代中由于对象生存期较短,每次回收都会有大量的对象需要被回收,新生代采用的是复制算法),对新生代进行一次垃圾回收。

  • Survior区:

    ​ 占据新生代内存的百分之二十(SurviorFrom区和SurviorTo区),经过垃圾回收,但是没被垃圾回收成功(次数小于15)的对象

    复制算法

    ​ 为了解决效率问题,复制算法将可用内存分为相等的两个部分,然后每次只使用其中的一块,当一块内存使用完,就将还存活的对象复制到另一块内存上,然后一次性清除完一块内存,然后再将第二块上的对象复制到第一块上。

    ​ 然而这样做内存的代价太大了,基本上每次都要浪费一半的内存。

    ​ 于是改进(现在新生代这样8:1:1):在GC开始的时候,对象只会存在于Eden区和名为From的Survivor区,To区是空的,一次MinorGc过后,Eden区和SurvivorFrom区存活的对象会移动到SurvivorTo区中,然后会清空Eden区和SurvivorFrom区,并对存活的对象的年龄+1,如果对象的年龄达到15,则直接分配到老年代。MinorGC完成后,SurvivorFrom区和SurvivorTo区的功能进行互换。下一次MinorGC时,会把SurvivorTo区和Eden区存活的对象放入SurvivorFrom区中,并计算对象存活的年龄。

2.1.2.老年代

​ 不经常被垃圾回收,Major GC。

  • old区:

    ​ 垃圾回收15次以上依然存活,或者特别大的对象新生代放不下了放到老年代的对象

    注意:

  • 在MaiorGC之前会先进行一次MinorGc,使得新生的对象进入老年代而导致空间不够才会触发。或者当无法找到足够大的连续空间分配给新创建的较大对象也会提前触发一次MajorGC进行垃圾回收腾出空间。

  • MajorGC采用标记—清除算法或者标记—整理算法

    ​ 因为老年代主要存放应用中生命周期长的内存对象,所以使用标记-清除或者标记-整理,首先扫描一次所有老年代里的对象,标记出存活的对象,然后回收没有标记的对象。

    ​ 标记—清除算法存在问题:

    1. MajorGC的耗时比较长效率不高,标记清除效率都比较低,因为要扫描再回收。
    2. MajorGC会产生内存碎片,当老年代也没有内存分配给新来的对象的时候,会提前触发MajorGC,回收还没有内存的话就会抛出OOM(Out of Memory)异常。

    标记—整理算法主要是为了解决标记-清除,产生的大量内存碎片的问题,当对象存活率较高时,也解决了复制算法的效率问题,他的不同之处就是清除对象的时候,可以讲可回收的对象移动到另一端,然后清除掉边界以外的对象,这样就不会产生内存碎片。

通俗来看上述三种算法(实际情况不一定):

  1. 时间复杂度比较效率:复制 > 标记整理 > 标记清除;
  2. 内存利用率: 标记整理 > 标记清除 > 复制;
  3. 内存整齐度: 复制 = 标记整理 > 标记清除。
2.1.3.永久代

​ 永久代是一个常驻内存的区域,用于存放自身携带的Class、Interface的元数据,是运行时环境必须的类信息,这个区域的信息是不会被垃圾回收的,关闭JVM才会释放这个区域的占用的内存


(三)方法区(Method Area)

  • 又称为永久代,位于非堆空间,又称为非堆区

  • 方法区被所有线程共享,属于共享区间;

  • 所有字段和方法字节码,以及一些特殊的方法和构造函数、接口代码都在此定义;

    ​ **静态变量 + 常量 + 类信息(构造方法/接口定义)+ 运行时常量池 **

  • 实例变量存储在堆中,与方法区无关;

  • 在HotSpot中,方法区只是逻辑上的概念,实际还是包含在Java堆中,永久代就是方法区的实现。

3.1.方法区的演变
  • JDK1.7:

    • 字符串常量池:

      ​ 从方法区中移到了堆

    • 运行时常量池:

      ​ 剩下的东西还在方法区中,也就是hotspot中的永久代

  • JDK1.8:

    hotspot移除了永久代,用元空间取代。

    • 元空间与永久代之间最大的区别在于:

      ​ 元空间并不在虚拟机中,而是使用本地内存。

    • 采用元空间而不用永久代的原因:

      1. 为了解决永久代的OOM问题,元数据和class数据存放在永久代中,容易出现性能问题和内存溢出
      2. 类及方法的信息等比较难确定其大小,因此对于永久代大小指定比较困难,太小容易出现永久代溢出,太大容易导致老年代溢出(堆内存不变,此消彼长)。
      3. 永久代会为GC带来不必要的复杂度,并且回收效率偏低。

(四)栈

4.1.大小

Java栈的区域很小 , 大概2m左右 。

4.2.特点:
  • 存取的速度特别快;

    • 原因:

      ​ 通过 ‘栈指针’ 来创建空间与释放空间 ! 指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存 ! 这种方式速度特别快 , 仅次于PC寄存器 !

    • 不足:

      ​ 但是这种移动的方式, 必须要明确移动的大小与范围 , 明确大小与范围是为了方便指针的移动 , 这是一个对于数据存储的限制, 存储的数据大小是固定的 , 影响了程序的灵活性。

  • 先进后出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值