JVM内存结构

java 虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统
 
 

在jdk8中:

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

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

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

 

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 中的垃圾;
Mix GC: G1回收器出现,回收全部NEW和部分OLD的region
内存溢出通常发生于 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 永久代变化如下图:

1.新生代:Eden+From Survivor+To Survivor

2.老年代:OldGen

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

为什么废弃永久代:由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen

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

 

  
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。此时不会报内存溢出错,却会使系统持续垃圾收集,无法处理其它请求
2. Perm 段溢出
通常由于 Perm 段装载了大量的 Servlet 类而导致溢出,目前的解决办法:
1) 将 PermSize 扩大,一般 256M 能够满足要求
2) 若别无选择,则只能将 servlet 的路径加到 CLASSPATH 中,但一般不建议这么处理
 
 
3.  Heap 溢出
系统对 Heap 没有限制,故  Heap 发生问题时,Java 进程所占内存会持续增长,直到占用所有可用系统内存
 
为什么一些程序频繁发生 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/
下图是容器云的jvm内存划分示例图
 

 

 

 
posted @ 2018-01-05 20:29  daniel456  阅读(278)  评论(0编辑  收藏  举报