JVM原理及调优(2)——内存管理

本文是JVM原理及调优系列的第二篇,主要探讨JVM的内存区域,包括程序计数器、栈、堆、方法区等,并详细讲解垃圾回收的原理,如对象可达性分析、引用类型、以及GC的执行过程。同时,介绍了方法区的回收策略和HotSpot的永久代特点。
摘要由CSDN通过智能技术生成

系列文章规划:

  1. JVM原理及调优(1)——内存模型
  2. JVM原理及调优(2)——内存管理
  3. JVM原理及调优(3)——编译机制
  4. JVM原理及调优(4)——类加载机制
  5. JVM原理及调优(5)——垃圾回收和调优
  6. JVM原理及调优(6)——G1收集器及G1日志分析
  7. JVM原理及调优(7)——JDK常用内置工具

1. 内存区域

JVM内存包括以下运行时数据区域:

  • 程序计数器。使用的内存很小,线程私有,可看作是当前线程所执行字节码的行号指示器。JVM规范中唯一一个没有规定OutOfMemoryError的区域。
  • 。线程私有,生命周期和线程相同。每个java方法执行时对应一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,进入方法时创建栈帧,出方法时销毁栈帧。线程请求的栈深度超过JVM允许的深度,抛出StackOverflowError。如果栈可以动态扩展,扩展时无法申请到足够内存,抛出OutOfMemoryError异常。
  • 。所有线程共享的内存区域。几乎所有的对象实例以及数组都在堆上分配。如果堆中没有足够内存完成实例分配,同时堆无又法扩展,抛出OutOfMemoryError异常。
  • 方法区。所有线程共享的内存区域。存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
  • 本地方法栈。和JVM栈作用类似,区别在于本地方法栈为JVM使用到的Native方法服务。和JVM栈一样会出现StackOverflowError和OutOfMemoryError异常。

另外,需要注意直接内存(Direct Memory),直接内存不是JVM规范定义的内存区域。但这部分内存也经常使用(如NIO使用Native函数库直接分配堆外内存),可能导致OutOfMemoryError异常。

2. 垃圾回收

堆为新对象分配存储空间,如果分配了空间,而又不回收空间,空间迟早会被用完,然后OutOfMemoryError。Java并不在代码层提供内存释放的API,而是由JVM去自主清理内存,将不可达的对象清理掉,这个过程就叫GC。

如何判断对象不可达?

如何判断对象不可达?最经典的是引用计数算法,给对象中添加一个引用计数器,有地方引用它时,计数器值加1,引用失效时,计数器值减1,计数器值为0表示对象不再被使用,即不可达。当计数器在一段时间内保持为0时,该对象就认为是可以被回收了。但是,这个算法有明显的缺陷:当两个对象相互引用,但是二者已经没有作用时,按照常规,应该对其进行垃圾回收,但是其相互引用,又不符合垃圾回收的条件,因此无法完美处理这块内存清理。因此Sun的JVM并没有采用引用计数算法来进行垃圾回收。因此在java中,单纯使用引用计数法实现垃圾回收是不可行的。

目前主流的商用程序语言都是使用可达性分析(也叫根搜索算法) 来判定对象是否存活。基本思想是,设立若干种根对象(GC Roots)作为起始点,从这些节点开始向下搜索,搜索路径成为引用链。对于一个对象,当任何一个GC Roots均没有引用链到该对象时,判定该对象不可达,可以被回收。JAVA中可以当做GC Roots的对象有以下几种:

1. 栈(栈帧中的本地变量表)中引用的对象。
2. 方法区中的静态成员。
3. 方法区中的常量引用的对象(全局变量)
4. 本地方法栈中JNI(一般说的Native方法)引用的对象。
注:第一和第四种都是指方法的本地变量表,第二种表达的意思比较清晰,第三种主要指的是声明为final的常量值。

什么是引用?

JDK1.2之前,如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用,在这种引用定义下,对象只有被引用和未被引用两种状态,不能完整的描述不同用途的对象,如一些缓存对象:当内存空间足够时,保留在内存;如果垃圾回收之后,内存空间紧张,就抛弃这些对象。JDK1.2之后,引用概念得到扩展(按引用强度依次减弱):

  • 强引用。普遍存在,类似“Object obj = new Object();”的引用。强引用对象,GC不回收。
  • 软引用。SoftReference。系统内存溢出之前,GC回收软引用对象,回收后仍然没有足够内存,抛OutOfMemoryError异常。
  • 弱引用。WeakReference。弱引用对象只能生存到下一次GC之前。
  • 虚引用。PhantomReference。虚引用不对对象生存时间产生影响,也无法通过虚引用来取得一个对象实例。其唯一目的是:虚引用对象在被GC时收到一个系统通知。

什么是逃逸回收?被判定不可达的对象,只是处于“缓刑”阶段,真正宣告对象死亡,至少需要经历两次标记过程:

可达性分析出的不可达对象会被第一次标记并且进行一次筛选,筛选条件是该对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法以及被JVM调用过,JVM将这两种情况视为“没有必要执行”。

如果被判定为有必要执行finalize()方法,对象将被加入F-Queue队列中,然后由JVM自动建立的低优先级Finalizer线程去依次触发对象的finalize()方法,但不保证它运行结束。然后GC将对F-Queue中的对象进行二次小规模标记,如果在finalize()方法执行时,对象重新与引用链上的任何一个对象建立关联,二次标记时它将被移除出“即将回收”的集合,这就是“逃逸回收”。如果没有出现逃逸回收,那么对象就真被回收了。

JVM是如何回收的?

JVM自主GC是在弱分代假设的前提下设计的:

1. 大部分新对象立即不可达;
2. 只存在少量old对象对young对象的引用。

为了保证这两个假设的有效性,HotSpot VM 将Heap分为两类区域:

  • Young Generation。绝大多数新对象会被分配到这里,由于大部分对象在创建后会立即不可达,所以很多对象被创建在新生代,然后消失。对象从该区消失的过程叫“minor GC”或“young GC”。

    Young Generation又被分为3个区Eden、From和To。执行如下:

    1. 绝大多数New Object会存放在Eden;
    2. Eden满,或者新对象大小 > Eden所剩空间,Eden执行GC,GC后存活的对象被移动到From;
    3. 此后Eden执行GC后幸存的对象会被堆积在From;
    4. 当From饱和,From执行GC,GC幸存的对象会被移动到To,然后清空From,并将To置为From,From置为To;
    5. 在以上步骤中重复几次依然存活的对象,就会被移动到Old Generation。
    
  • Old Generation。从Young Generation幸存下来的对象移动到该区。对象从该区消失的过程叫“major GC”或“full GC”通常该区比Young Generation要大,发生GC的频率要比Young Generation小。Old Generation的GC事件基本上是在空间已满时发生,执行的过程根据GC类型不同而不同。

方法区如何进行回收?JVM规范不要求对方法区进行GC,而且方法区GC性价比很低。HotSpot JVM的永久代是有GC的
永久代GC主要回收废弃常量和无用类。回收废弃常量和回收堆对象类似,无引用时回收。JVM可以对无用类进行回收,判定无用类需要满足三个条件:

1. 该类所有实例均已被回收。
2. 加载该类的ClassLoader已被回收。
3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

参考文献

  1. JVM自动内存管理:内存区域基础概念(视频)
  2. JDK,JRE,JVM区别与联系
  3. Java虚拟机的内存组成以及堆内存介绍
  4. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收
  5. Java堆内存与栈内存的区别
  6. Java 堆和栈的区别)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值