垃圾回收机制详解

一、垃圾回收的概念及意义

垃圾回收(Garbage Collection,简称GC)可以自动清空堆中不再使用的对象。垃圾回收机制最早出现在1959年,被用于解决Lisp语言中的问题。垃圾回收是Java语言的一大特征,并不是所有的语言都有垃圾回收功能,比如在C/C++中,并没有垃圾回收机制,程序员需要手动释放堆中的内存。

Java中使用了垃圾回收机制,不需要手动释放内存,程序员在编程的时候就可以减少犯错的机会,利用垃圾回收,程序员可以避免一些指针和内存泄漏相关的bug(这一类bug通常很隐蔽)。另一方面,垃圾回收需要消耗更多的计算时间,降低了效率。垃圾回收实际上是将程序员的责任转移给计算机。

二、JVM内存模型及堆内存模型

(1)JVM内存模型

Java代码是运行在Java虚拟机上的,有Java虚拟机通过解释执行(解释器)或编译执行(即时编译器)来完成,所以Java内存模型也就是指Java虚拟机的运行时内存模型。

运行时内存模型,分为线程私有和共享数据区两大类,其中线程私有的数据区包括程序计数器、虚拟机栈、本地方法栈,所有线程共享的数据区包括Java堆、方法区,在方法区中有一个常量池。Java运行时的内存模型图如下:
运行时数据区域

内存模型介绍:

<1> 程序计数器
程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。每个程序计数器只用来记录一个线程的行号,所以它是线程私有(一个线程就只有一个程序计数器)的。如果程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是一个本地(native,由C语言编写完成)方法,则计数器的值为Undefined,由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区域中唯一一个没有定义OutOfMemoryError的区域。

<2>虚拟机栈
一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作栈、动态链接、方法出口等,当方法被调用时,栈帧在虚拟机栈中入栈,当方法执行完成时,栈帧出栈。

局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在局部变量表中,只有long和double类型会占用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。需要注意的是,局部变量表是在编译时就已经确定好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变。
  
虚拟机栈中定义了两种异常,如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出);不过多数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,直到内存不足,此时,会抛出OutOfMemoryError(内存溢出)。每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的。

<3>本地方法栈
本地方法栈的作用、运行机制、异常类型等方面都与虚拟机栈相同,唯一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一起使用。本地方法栈也是线程私有的。

<4>堆
堆区是理解Java GC机制最重要的区域,没有之一。在JVM所管理的内存中,堆区是最大的一块,堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上讲,所有的对象都在堆区上分配内存(不过现代技术里,也不是这么绝对的,也有栈上直接分配的)。一般的,根据Java虚拟机规范规定,堆内存需要在逻辑上是连续的(在物理上不需要),在实现时,可以是固定大小的,也可以是可扩展的,目前主流的虚拟机都是可扩展的。如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java heap space异常。

<5>方法区
方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对常量池的内存回收和对已加载类的卸载。

在方法区上进行垃圾收集,条件苛刻而且相当困难,效果也不令人满意,所以一般不做太多考虑,可以留作以后进一步深入研究时使用。在方法区上定义了OutOfMemoryError:PermGen space异常,在内存不足时抛出。

<6>运行时常量池
运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种变量和符号引用。运行期间可以将新的常量池中,用的比较多的就是String类的intern( )方法(注释1:instern()方法),当一个String实例调用intern时,Java常量池中是否有相同的Unicode的字符串常量,若有,则返回其引用,若没有,则在常量池中增加一个Unicode等于该实例字符串并返回它的引用。

(2)堆内存模型

JVM堆内存

JVM的堆内存(注释2:堆(Heap)和非堆(Non-heap)内存 )被分为两部分——年轻代(Young Generation)和老年代(Old Generation)
<1>

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值