JVM内存结构

java 虚拟机有本身完善的硬体架构,如处理器、堆栈、寄存器等,还具备相应的指令系统
 
 
JVM Heap 分两大块:  一块是 NEW Generation,另外一块是 Old Generation.
在 NewGeneration 中,有一个叫 Eden 的空间,主要是用来存放新生的对象,还有两个 Survivor Spaces(from,to),它们的大小老是同样,它们用来存放每次垃圾回收后存活下来的对象。
在 OldGeneration 中,主要存放应用程序中生命周期长的内存对象。
在NewGeneration 块中,垃圾回收通常用 Copying 的算法,速度快。每次 GC 的时候,存活下来的对象首先由 Eden 拷贝到某个 SurvivorSpace, 当 Survivor Space 空间满了后, 剩下的 live 对象就被直接拷贝到OldGeneration 中去。所以,每次 GC 后,Eden 内存块会被清空。
在 OldGeneration 块中,垃圾回收通常用 mark-compact 的算法,速度慢些,但减小内存要求.
 
垃圾回收分多级:
Full GC:会回收 OLD 和 Perm中的垃圾;
Major GC:会回收OLD 中的垃圾;
Minor GC:只回收 NEW 中的垃圾;
内存溢出一般发生于 OLD 段或 Perm 段垃圾回收后,仍然无内存空间容纳新的 Java对象的状况。 
 
内存申请过程以下:
1. JVM 会试图为相关 Java 对象在 Eden 中初始化一块内存区域
2. 当 Eden 空间足够时,内存申请结束。不然到下一步
3. JVM 试图释放在 Eden 中全部不活跃的对象(这属于minor级别垃圾回收),释放后若 Eden 空间仍然不足以放入新对象,则试图将部分 Eden 中活跃对象放入 Survivor 区
4. Survivor 区被用来做为 Eden 及 OLD 的中间交换区域,当to空间放不下eden和from的对象,且OLD 区空间担保成功时,from区的对象会被移到 Old 区,不然会被移到to 区
5. 当 OLD 区空间不够时,JVM 会在 OLD 区进行full GC级
6. 彻底垃圾收集后,若 to 及 OLD 区仍然没法存放从 Eden 复制过来的部分对象,致使 JVM没法在 Eden 区为新对象建立内存区域,则出现”out of memory 错误”
 
hotspot jvm结构以下(虚拟机栈和本地方法栈合一块儿了):

 

JDK8 永久代变化以下图:java

1.新生代:Eden+From Survivor+To Survivor程序员

2.老年代:OldGen算法

3.永久代(方法区的实现) : PermGen----->替换为Metaspace(本地内存中)架构

为何废弃永久代:因为永久代内存常常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGenjvm

深刻理解元空间(Metaspace):元空间的内存大小:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限制的,须要配置参数。性能

在jdk8中:ui

1.字符串常量由永久代转移到堆中。spa

2.持久代已不存在,PermSize MaxPermSize参数已移除。操作系统

3.类加载(方法区的功能)已经不在永久代PerGem中了,而是Metaspace中线程

  
VM 调优建议:
在用户生产环境上通常将如下三组值设为相同,以减小运行期间系统在内存申请上所花的开销。
1.ms/mx:定义 YOUNG+OLD 段的总尺寸,ms 为 JVM 启动时 YOUNG+OLD 的内存大小;mx 为最大可占用的 YOUNG+OLD 内存大小。在用户生产环境上通常将这两个值设为相同,以减小运行期间系统在内存申请上所花的开销。(进程启动时申请ms内存,当运行过程当中须要的内容超过ms时,进程须要挂起来等待申请到更新内存时才能执行。为了不这种状况发生,最好定义ms和mx相同)
2.NewSize/MaxNewSize:定义 YOUNG 段的尺寸,NewSize 为 JVM 启动时 YOUNG 的内存大小;MaxNewSize 为最大可占用的 YOUNG 内存大小。
3.因为heap大小值相同,Yound大小值相同、因此剩下的Old大小值应该也相同
 
 
1. OLD 段溢出
这种内存溢出是最多见的状况之一,产生的缘由多是:
1) 设置的内存参数太小(ms/mx, NewSize/MaxNewSize)
2) 程序问题
单个程序持续进行消耗内存的处理,如循环几千次的字符串处理,对字符串处理应建议使用 StringBuilder。此时不会报内存溢出错,却会使系统持续垃圾收集,没法处理其它请求,相关问题程序可经过 Thread Dump获取单个程序所申请内存过大,有的程序会申请几十乃至几百兆内存,此时 JVM也会因没法申请到资源而出现内存溢出,对此首先要找到相关功能,而后交予程序员修改,要找到相关程序,必须在 Apache 日志中寻找。当 Java 对象使用完毕后,其所引用的对象却没有销毁,使得 JVM 认为他仍是活跃的对象而不进行回收,这样累计占用了大量内存而没法释放。
2. Perm 段溢出
一般因为 Perm 段装载了大量的 Servlet 类而致使溢出,目前的解决办法:
1) 将 PermSize 扩大,通常 256M 可以知足要求
2) 若别无选择,则只能将 servlet 的路径加到 CLASSPATH 中,但通常不建议这么处理
 
 
3.  Heap 溢出
系统对 Heap 没有限制,故  Heap 发生问题时,Java 进程所占内存会持续增加,直到占用全部可用系统内存
 
4.其余:
JVM 有 2 个 GC 线程。第一个线程负责回收 Heap 的 Young 区。第二个线程在 Heap 不足时,遍历 Heap,将 Young 区升级为 Older 区。Older 区的大小等于-Xmx 减去-Xmn,不能将-Xms 的值设的过大,由于第二个线程被迫运行会下降 JVM 的性能。
 
为何一些程序频繁发生 GC?有以下缘由:
 程序内调用了 System.gc()或 Runtime.gc()。 一些中间件软件调用本身的 GC 方法,此时须要设置参数禁止这些 GC。
 Java 的 Heap 过小,通常默认的 Heap 值都很小。
 频繁实例化对象,Release 对象。此时尽可能保存并重用对象,例如使用 StringBuffer()和 String()。
若是你发现每次 GC 后,Heap 的剩余空间会是总空间的 50%,这表示你的 Heap 处于健康状态。许多 Server端的 Java 程序每次 GC 后最好能有 65%的剩余空间。
 
典型设置:
  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
    -Xmx3550m:设置JVM最大可用内存为3550M。
    -Xms3550m:设置JVM初始内存为3550m。此值能够设置与-Xmx相同,以免每次垃圾回收完成后JVM从新分配内存。
    -Xmn2g:设置年轻代大小为2G。整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小(jdk8之后废弃)。
    -Xss128k:设置每一个线程的堆栈大小。JDK5.0之后每一个线程堆栈大小为1M,之前每一个线程堆栈大小为256K。在相同物理内存下,减少这个值能生成更多的线程。可是操做系统对一个进程内的线程数仍是有限制的,不能无限生成,经验值在3000~5000左右。
  • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
    -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
    -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
2.增长 Heap 的大小虽然会下降 GC 的频率,但也增长了每次 GC 的时间。而且 GC 运行时,全部的用户线程将暂停,也就是 GC 期间,Java 应用程序不作任何工做。
4.Heap 大小并不决定进程的内存使用量。进程的内存使用量要大于-Xmx 定义的值,由于 Java 为其余任务分配内存,例如每一个线程的 Stack 等。
5 .  ystem.gc()显示调用会触发 Full GC。对整个堆进行整理,包括 Young、Tenured 和 Perm。要尽可能避免。
 
进一步调优总结,可参考 http://unixboy.iteye.com/blog/174173/
相关文章
相关标签/搜索