即日起在codingBlog上分享您的技术经验即可获得积分,积分可兑换现金哦。

Java虚拟机初步入门基础知识(一):内存空间分配及其回收原理

编程语言 zjt921586518 24℃ 0评论
本文目录
[隐藏]

这几天看了几位大神写的博客,对JVM的内存分配机制有了更为详细的了解,于是便想总结分享下

先从基础说起

JVM在内存分配过程中会涉及到四个区域:栈(stack)、堆(heap)、方法区、静态域。

1.

栈用来存储基本类型的数据和对象的引用,注意是引用

2.

堆用来存放new关键字产生的数据

3.方法区

存放字符串和常量池

4.静态域

存放在对象中用static定义的静态成员



首先从栈和堆说起,栈和堆是java程序运行的关键,简单的来说,栈解决的是程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么放、放在哪儿。随着程序的执行。栈中的变量在退出作用域后其所在空间会被立即释放,然后该空间另作他用,而堆则不同,堆主要存放的是对象和数组的具体内容,在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理,关于垃圾回收后面再具体说。
 
栈和堆的关系有点类似于个人和银行,在Java中一个线程就会相应有一个线程栈与之对应,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。每个人就相当于一个线程栈,你手里拿着日常用的零钱就相当于基本类型的数据,而你手里拿的银行卡就相当于对象的引用,钱并不实际存放在银行卡中,而是存放一个表示钱的相关信息,这些都存放在个人的栈中。而银行就相当于堆,他是存放具体银行卡这个引用所指向的钱这个对象的具体内容的地方。不同的人代表不同的线程栈,但银行却是所有线程共享的。
 
栈和堆的这样分离有很多好处,一是使逻辑更加清楚;二是使得数据共享更加方便,因为堆是所有线程共享的;三是栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分。由于栈只能向上增长,因此就会限制住栈存储内容的能力。而堆不同,堆中的对象是可以根据需要动态增长的,因此栈和堆的拆分,使得动态增长成为可能,相应栈中只需记录堆中的一个地址即可。
 
关于常量池中的数据是在编译期就被确定的,其保存在已编译的.class文件中,除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值(final)还包含一些以文本形式出现的符号引用。JVM为每个被装载的类维护一个常量池,常量池在内存当中是以表的形式存在的。对于String常量,它的值是在常量池中的,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。



再来说说上面提到的一个问题,关于JVM如何处理垃圾回收的问题。
首先需要理解分代的概念:

分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。

Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。

如何进行分代,如下图

如何分代



虚拟机中的共划分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

年轻代:

所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来
对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

年老代:

在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。

持久代:

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

什么情况下触发垃圾回收

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。

Scavenge GC

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

Full GC

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

· 年老代(Tenured)被写满

· 持久代(Perm)被写满

· System.gc()被显示调用

·上一次GC之后Heap的各域分配策略动态变化

文章参考自http://developer.51cto.com/art/201001/175883.htm

转载请注明:CodingBlog » Java虚拟机初步入门基础知识(一):内存空间分配及其回收原理

喜欢 (0)or分享 (0)
发表我的评论
取消评论

*

表情