深入JVM 原理(二)JVM 垃圾收集-堆内存

运行时数据区就是我们的java内存管理,我们java能管理的地方只在java运行时数据区,其他我们无法控制。

java运行时数据区的大小,我们可根据自己的需求自行更改控制,进行调优,但其中

栈内存是线程独享。

堆内存是保存对象信息,是所有线程共享的。

所以,我们所说的java内存调优都是在运行时数据区进行的,即共享的数据区越大越好,所以,关键是在堆内存中,如果我们要真正做到对程序的理解,就需要对堆内存进行一定的控制。

 

下面我们来看堆内存的结构:

备注: 永久代就是方法区。  

有认为方法区(永久代)不在堆内存。有的认为方法区也是堆内存的一块区域。

 

 

一定要记住在 JDK1.8 之后将最初的永久代内存空间取消了,使用元空间代替。

以下为1.8之前的内存空间组成:(取消永久代的目的:是为了将 HotSpot与JRockit 两个虚拟机标准联合成一个。)
在整个的JVM堆内存之中实际上将内存分为三块:
年轻代:新对象和没达到一定年龄的对象都在年轻代;
老年代:被长时间使用的对象,老年代的内存空间比年轻代更大
元空间:像一些方法中的操作临时对象等,直接使用物理内存;最初的永久代是需要在JVM堆内存里面进行划分;这样可以解决内存溢出的问题。

 

JVM堆内存分为2块:Permanent Space(1.8后Meta space)Heap Space

  • Permanent 即永久代(Permanent Generation),主要存放的是Java类定义信息,与垃圾收集器要收集的Java对象关系不大。
  • Heap = { Old + NEW = {Eden, from, to} },Old 即 年老代(Old Generation),New 即 年轻代(Young Generation)。年老代和年轻代的划分对垃圾收集影响比较大。

1、年轻代

所有新生成的对象首先都是放在年轻代。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代一般分3个区,1个Eden区,2个Survivor区(from(s0) 和 to(s1))。 一般默认Eden:s0:s1  = 8:1:1 的比例分配。

大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当一个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当另一个Survivor区也满了的时候,从前一个Survivor区复制过来的并且此时还存活的对象,将可能被复制到年老代。

2个Survivor区是对称的,没有先后关系,所以同一个Survivor区中可能同时存在从Eden区复制过来对象,和从另一个Survivor区复制过来的对象;而复制到年老区的只有从另一个Survivor区过来的对象。而且,因为需要交换的原因,Survivor区至少有一个是空的。特殊的情况下,根据程序需要,Survivor区是可以配置为多个的(多于2个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。

针对年轻代的垃圾回收即Young GC

2、年老代

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

针对年老代的垃圾回收即Full GC

3、永久代(1.8后 Meta space元空间)

用于存放静态类型数据,如Java Class, Method 等。持久代对垃圾回收没有显著影响。但是有些应用可能动态生成或调用一些Class,例如Hibernate CGLib 等,在这种时候往往需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型。

对于整个GC流程里,最需要处理的就是年轻代和老年代的内存清理操作,而元空间(永久代)都不在GC范围内;

 

所以,当一组对象生成时,内存申请过程如下

  1. 当现在有一个新的对象产生,那么对象一定需要内存空间,于是现在就需要为该对象进行内存空间的申请。
  2. 首先会判断伊甸园区是否有内存空间,如果此时有内存空间,则将新对象保存在伊甸园区;
  3. 但如果伊甸园区的内存空间不足,那么会自动执行一个 Minor GC 操作,将伊甸园区无用的内存空间进行清理,当清理之后会继续判断伊甸园区的内存空间是否充足?充足则将新的对象进行空间分配;
  4. 如果执行了 Minor GC 之后发现伊甸园区的内存依然不足,那么这个时候会进行存货区判断,如果存活区有剩余空间,则将伊甸园区的部分对象保存在存活区,那么随后继续判断伊甸园区的内存空间是否充足,如何内存充足,则在伊甸园区进行空间分配;
  5. 如果此时存活区也已经没有内存空间了,则开始判断老年区,如果此时老年区的空间充足,则将存活区中的活跃对象保存在老年代,而后存活区就会存现有空余空间,随后,伊甸园区将活跃对象保存在存活区之中,而后在伊甸园区里为新对象开辟内存空间;
  6. 如果这个时候老年代也满了,那么这个时候将产生 Major GC(Full GC),进行老年代的内存清理;
  7. 如果老年代执行了 Full GC 之后,依然无法进行对象的保存,就会产生 OOM()异常“OutOfMemoryError”。

 

 

OOM(“Outof Memory”)异常一般主要有如下2种原因

1. 年老代溢出,表现为:java.lang.OutOfMemoryError:Javaheapspace

这是最常见的情况,产生的原因可能是:设置的内存参数Xmx过小或程序的内存泄露及使用不当问题。

例如循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存。还有的时候虽然不会报内存溢出,却会使系统不间断的垃圾回收,也无法处理其它请求。这种情况下除了检查程序、打印堆内存等方法排查,还可以借助一些内存分析工具,比如MAT就很不错。


2. 持久代溢出,表现为:java.lang.OutOfMemoryError:PermGenspace

通常由于持久代设置过小,动态加载了大量Java类而导致溢出,解决办法唯有将参数 -XX:MaxPermSize 调大(一般256m能满足绝大多数应用程序需求)。将部分Java类放到容器共享区(例如Tomcatshare lib)去加载的办法也是一个思路,但前提是容器里部署了多个应用,且这些应用有大量的共享类库。

 

来个面试题,看看上面知识点掌握程度

请解释“StackOverflowError”和“OutOfMemoryError”的区别:

1、stackoverflow: 每当java程序启动一个新的线程时,java虚拟机会为他分配一个栈,java栈以帧为单位保持线程运行状态;当线程调用一个方法是,jvm压入一个新的栈帧到这个线程的栈中,只要这个方法还没返回,这个栈帧就存在。
如果方法的嵌套调用层次太多(如递归调用),随着java栈中的帧的增多,最终导致这个线程的栈中的所有栈帧的大小的总和大于-Xss设置的值,而产生生StackOverflowError溢出异常。
2、OutOfMemoryError: 如上。

 

 

4、 参数说明

  • -Xmx3550m:设置JVM最大堆内存为3550M。
  • -Xms3550m:设置JVM初始堆内存为3550M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
  • -Xss128k:设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M,之前每个线程栈大小为256K。应当根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。
  • -Xmn2g:设置年轻代大小为2G。在整个堆内存大小确定的情况下,增大年轻代将会减小年老代,反之亦然。此值关系到JVM垃圾回收,对系统性能影响较大,官方推荐配置为整个堆大小的3/8。
  • -XX:NewSize=1024m:设置年轻代初始值为1024M。
  • -XX:MaxNewSize=1024m:设置年轻代最大值为1024M。
  • -XX:PermSize=256m:设置持久代初始值为256M。
  • -XX:MaxPermSize=256m:设置持久代最大值为256M。
  • -XX:NewRatio=4:设置年轻代(包括1个Eden和2个Survivor区)与年老代的比值。表示年轻代比年老代为1:4。
  • -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的比值。表示2个Survivor区(JVM堆内存年轻代中默认有2个大小相等的Survivor区)与1个Eden区的比值为2:4,即1个Survivor区占整个年轻代大小的1/6。
  • -XX:MaxTenuringThreshold=7:表示一个对象如果在Survivor区(救助空间)移动了7次还没有被垃圾回收就进入年老代。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代,对于需要大量常驻内存的应用,这样做可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代存活时间,增加对象在年轻代被垃圾回收的概率,减少Full GC的频率,这样做可以在某种程度上提高服务稳定性。

 

未完,待续......

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值