`
lhc1986
  • 浏览: 160214 次
  • 性别: Icon_minigender_1
  • 来自: 帝都
社区版块
存档分类
最新评论

(转载)jvm内存模型

    博客分类:
  • Jvm
阅读更多

一 JVM体系结构



 

 

方法区

  • 一个JVM只有一个方法区,是所有线程共享的
  • 存放Class的线性二进制流
  • 类信息,该类型的常量池,字段信息,方法的字节码,操作数栈和该方法的栈帧中的局部变量区的大小,异常表,到类ClassLoader的引用,到Class类的应用
  • 方法区大小不固定,可以动态调整
  • 方法区也可以被GC

  • 一个JVM只有一个堆,所有线程共享
  • 存放所有类实例和数组

PC寄存器

  • JVM会为每一个创建的线程分配一个PC寄存器
  • 大小为一个字节
  • 内容是下一条将执行指令的地址

java方法栈

  • 线程启动时,JVM会为其分配一个Java栈
  • JVM对java方法栈只有“压栈”,“出栈”的操作,操作的单位是栈帧
  • 栈帧由三部分组成“局部变量区”,“操作数栈“,“栈帧数据区”

 

二 JVM内存结构

 

 

 

堆分为:新生代:Eden,S0,S1;年老代,其中s0,s1是完全对等的,在GC时数据互相拷贝。

例如:下面的列表中各代的比例。

S0     S1      E       O       P        YGC    YGCT   FGC  FGCT     GCT
0.00   0.00   5.83  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.47  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.47  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.48  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.48  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.50  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.50  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.51  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.51  30.71  34.49     36    4.177   223  308.158  312.335
0.00   0.00   7.51  30.71  34.49     36    4.177   223  308.158  312.335

 

 

 

三 内存分配参数:




 

注意:图上的参数是正确的,但是图中说PermSize最大为64M是不对的。

 

 

 

四,JVM 内存参数分析实例

环境:OS:Linux version 2.6.9-79.custome.ELxenU  cpu: 4  * Intel(R) Xeon(R) CPU           E5410  @ 2.33GHz (双核)   memory:4G

-server -Xmx2g -Xms2g -Xmn256m -XX:PermSize=192m -Xss256k -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=70

  • -Xmx2g 最大堆内存2G
  • -Xms2g 最小内存2G
  • -Xmn256m 新生代内存256m  整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般是固定大小的(例如64m、96m),所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
  • -XX:PermSize=192m 持久代 192m
  • -Xss256k 指定线程桟大小256K
  • -XX:LargePageSizeInBytes=128m  指定Java heap的分页页面大小为128M
  • -server 可以使得新生代采用并行GC,年老代采用串行
  • -XX:+DisableExplicitGC
  • -XX:+UseConcMarkSweepGC 指定在Old Generation使用concurrent gc ,启用CMS低停顿垃圾收集器。GC线程和应用线程并行
  • -XX:+CMSParallelRemarkEnabled
  • -XX:+UseCMSCompactAtFullCollection
  • -XX:+UseFastAccessorMethods
  • -XX:+UseCMSInitiatingOccupancyOnly
  • -XX:CMSInitiatingOccupancyFraction=70

五. 内存回收

GC:垃圾回收。回收的是堆和方法区的内存。

基本原理:找到不被使用的对象,然后回收内存。使用收集器的方式实现GC。

A)怎么找到?从根集合出发,找出无引用的对象。

根集合对象: 当前运行线程栈上引用的对象,常量及静态变量,传到本地方法且没有被本地方法释放的对象引用。

B)收集器

按回收算法为两种: 引用计数收集器,跟踪收集器。

引用计数采用算法:原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。

跟踪收集器采用算法:复制,标记-清除,标记-压缩。

按分区对待的方式分: 增量收集器(jdk5开始废弃),分代收集器。

增量收集器:就是通过一定的回收算法,把一个长时间的中断,划分为很多个小的中断,通过这种方式减少GC对用户程序的影响。

分代收集:对象存活的时间有长短,基于此将堆分为多个代,不同的代采用不同的GC方式。

按吞吐量和响应时间(暂停时间)分为: 串行收集器,并行收集器,并发收集器。

C)评估垃圾回收策略的两个重要度量

  • 吞吐量:JVM花费在垃圾回收上的时间越长,则吞吐量越低
  • 暂停时间:JVM垃圾回收过程当中有一个暂停期,在暂停期间,应用程序不能运行

串行收集器:单线程(单CPU)进行垃圾回收的工作。
–适用情况:数据量比较小;单处理器下并且对响应时间无要求的应用。
–缺点:只能用于小型应用
并行收集器:多个线程同时进行垃圾回收的工作。
–适用情况:”对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:科学计算。
–缺点:应用响应时间可能较长
并发收集器:传说中的CMS。垃圾回收器的一些工作与应用程序同时进行。
–适用情况:”对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器。

D)GC类型

GC有两种类型:Minor GC(Scavenge GC)和Full GC。

Minor GC:对新生代内存进行GC。

Full GC:对新生代,旧生代,持久代都进行GC。

Full GC可能的原因

a)老年代或持久代空间满。

b)老年代采用CMS GC,GC日志出现prmotion failed和concurrent mode failure时可能触发。

prmotion failed:Minor GC是,S0(S1)放不下,放入旧生代时,仍然放不下造成的。

concurrent mode failure:CMS GC的过程中,有对象放入旧生代,此时旧生代空间不够。

c)统计得到Minor GC后存活对象放入旧生代的平均大小大于旧生代剩余空间。

d)System.gc(),只是”建议”JVM回收内存,不是强制。

各种GC 参数:http://www.imwanghai.com/blog/?p=265

六. 为何内存溢出:

既然都有GC,为什么还有内存被用尽(当然除了突然申请大空间)。这里更想说的是新生代和老年代被耗尽。

这是因为jvm有四种引用类型,不同的引用,GC的条件是不一样的。

A)四种引用

软引用:SoftReference,弱引用:WeakReference ,虚引用:PhantomReference。

软引用:内存不足,或软引用不经常使用时会被回收。适用于做缓存。

弱引用:使用弱引用创建的对象本身没有强引用,GC时一定会被回收。

虚引用:虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

除此之外都是强引用,我们一般创建一个对象时的引用就是强引用。对象被强引用,是不会不垃圾回收的。

B)内存溢出(泄露)

两种理解,

一是需要使用的对象在不断增加,直到需要分配的jvm内存超出了无法满足,于是产生溢出。

二是无用的对象在不断增加,但又无法回收,于是产生泄露。

泄露的对象有两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。这些对象不会被GC所回收,然而它却占用内存。

 

 

七. GC调整:

 

调整目标
  随着 Java 的演化,当初 C vs. Java 大战渐趋缓和:
    Java:     老兄! malloc / free 就是一名艺术?
    C:         好钢用到刀刃上,总感觉 GC 忙个不停一直占着资源在清理“垃圾”
    …


梦幻 GC 应该是这样子:
    1.    低开销 (low overhead)
    2.    低停顿时间 (low pause time)
    3.    有效的空间利用率 (space efficient)

但通常,我不得不从以上中来个”三选二”,因为不同的程序对GC有不同的需求:
    1.    吞吐量 (Throughput) - 花费在非GC的工作时间,也就是说GC时间是反吞吐量的 (这就是上述 C 老兄嘲笑的原因) 
        - -XX:GCTimeRatio =ratio (粗调,只是个 hint,能否达到取决于系统状态)
        - GC 时间对应用时间的比率: 1 / (1 + n) 
        - 例如: -XX:GCTimeRatio=19 设定 5% (1/(1+19)) GC 时间; -XX:GCTimeRatio=99 设定 1% (1/(1+99)) GC 时间
    2.    开销 (Overhead) - 即反吞吐量,即 GC 所占的时间
    3.    停顿时间 (Pause) - 由于GC 正在运行,程序看起来没响应的时长
        - -XX:MaxGCPauseMillis=n (粗调,只是个 hint,能否达到取决于系统状态)
        - GC 将尽最大努力调整相应参数来满足这一要求,也许会以降低吞吐量为代价
    4.    GC频率 (GC Frequency) - 相对应用执行时间,GC执行的频率
    5.    空间利用率 (Footprint, 这个单词找不到准确的中文) - 完成GC功能需要的空间。 因为硬件的 cache line, memory, page 总是稀缺的,GC占去越多意味着真正用来干活的就越少
    6.    敏捷性 (Promptness) – 当对象变成垃圾了,多长时间它才被清理掉。也就是多长时间一个垃圾对象所占的内存被收回。包括分配式垃圾收集(Distributed Garbage Collection -DGC),如在RMI 场景

 


调整策略
1. 对所有GC适用
    1.    来个超大号的总没错!
        - 但是,最大值 <= 1/2 系统内存(除非你跑着玩,因为另一半留给操作系统其它看家进程和 mmap)
        - 且JVM能访问到的最大值因操作系统而异 (java -Xmx)
    2.    平衡
        - 堆空间,通常是越大越好,无论对 young 还是对 Old Generation 来说
        - 大的空间意味着GC不那么频繁,因此留有时间给对象成为“垃圾”
        - 对于 Young Generation  因为频繁的GC,本该成为垃圾的对象,会被过早地搬到 Survivor Space,进到那里的对象,如果存活,会占去来回在 S0 <-> S1 中拷贝的消耗。
        - 对于 Old Generation  我们希望,在一次GC过程中尽量多清除些垃圾,但频繁的GC活动,有时会显得过早(因为,也许过不了多久,又有一大批对象消亡)
        - 但小的空间或许意味着GC每一趟可以更快地结束 (但并不总是这样,如CMS)
    3.    Young Generation 大小
    因为 minor GC 频繁地运行,minor GC 算法的目标是快速。因此参数的选择不要违背这一宗旨

        - Young 整体空间大小调整
            - -XX: newSize=<n> : 初始 young generation 大小
            - -XX:MaxNewSize=<n> : 最大 young generation 大小
            - -Xmn n 更可取,因为它综合了 -XX:NewSize=<n> 和 -XX:MaxNewSize=<n>
            - 如果使用上述任一种方案指定 young generation 大小,那么 -XX:NewRatio=<ratio > 不再需要: young generation 与 old generation 的比率,如 ratio =3 表示 young/old = 1/3
            - CPU 核心增加了,适量增加 young generation 大小以充分利用并行,如果 heap size 固定了,那么增加 young generation 则意味着 old generation 减少,确保 old generation 足够大以容纳任何时候所有 live 对象 + 20% 空闲
        - Eden空间调整
            - 该大小意味着 minor GC 的频率,越小越容易填满,因此频率越高(不利于对象成为垃圾)
            - 该大小还意味着有多少比例的对象可以被夭折在伊甸园中(age=0),而不必花力气将他们搬到Survivor Space (age >0),进而送往 Old Generation,最后在 Major collection 中耗费力气 
            - 增加 Eden 大小并不总是减小 minor collection 的时间,因为 minor collection 时间花费在 拷贝Eden 中的 age=0的对象到 survivor space,以及处理 survivor space 中的 age>0 的对象
            - -XX:SurvivorRatio=<ratio> (见下面)
        - Survivor 调整
            - -XX:SurvivorRatio=<ratio>  单个 Survivor Space 与 Eden 的比例,例如,ratio=6 表示 每个 Survivor / Eden = 1/6 (即,每个 Survivor 是整个 Young Generation 的 1/8,因为有两个 Survivor Space)
            - -XX:TargetSurvivorRatio=<percent> 在每个(因为有两个) Survivor Space 到达这一比率时,将被提升到 old generation 中
            - -XX:InitialTenuringThreshold=<threshold> 因为是自适应(adaptive)GC, 初始值,在被提升到 old generation 之前,对象可在 young generation 存活的次数
            - -XX:MaxTenuringThreshold=<threshold>   因为是自适应(adaptive)GC, 最大值,在被提升到 old generation 之前,对象可在 young generation 存活的次数
            - -XX:+AlwaysTenure 从不将对象保留在 Survivor Space, 即每次 minor GC 直接将对象送往 Old Generation。对于应用包括很多 mid/long time live 对象适用,因为 可以避免在 Survivor Space 中来回拷贝的开销 
            - 权衡
                - 对于 mid/long live time 对象的比例小的应用
                - 尽可能多地将对象呆在 survivor space, 因为这样可以将他们在 young generation 就被回收
                - 更少提升到 old generation
                - 低频繁 major GC
            - 对于mid/long live time 对象的比例大的应用
            - 尽量避免来回在 survivor space 中拷贝,因为他们最终是进到 old generation
            - 但是,很难预言对象的生命周期长还是短,一般来说,在 survivor space 中拷贝比无谓地提升到 old generation 还是要好点
            - -XX:+PrintTenuringDistribution 用于监视 survivor size 的行为分配,并给出合适的 survivor 空间大小建议 
    4.    Old Generation 大小
        - Major collection 最主要目标是更好的空间利用率,而不是快速
        - 尽可能减少 major collection 的频率
        - 最好能容纳应用“稳态”时的活对象总大小 (说起来容易,做起来难,能容易地预测 WEB 服务器的高峰与空闲状态负载的回归线么?能很容易地预测分布式存储系统的 read write 比么?)。并适用加上20% 空闲
        - -Xms == -Xmx
            - 当 -Xms != -Xmx,堆的扩张或收缩需要 Full GC (什么是 Full GC?在大多数情况下,其等同于 major GC, 但在 i-cms 增量并发标记清扫 GC 模式下,有些不一样。)
            - -Xmx<n> 最大堆大小 (young generation + old generation)
            - -Xms<n> 初始堆大小 (young generation + old generation)
            - -Xmn<n> young generation 大小
            - 因此,-XX:MinHeapFreeRatio=<minimum> -XX:MaxHeapFreeRatio=<maximum> 不再需要
        - -Xms != -Xmx 也许适用于应用在整个生命周期中稳态的活对象总大小为 -Xms 赋的大小,但也许会在极小的情况下峰值负载(如 WEB 服务器)或数据集(存储系统)将达到 -Xmx赋的值。但是,是以增长堆空间大小的开销为代价的(Full GC)。因为,此时,GC 别无选择,在 OOME 导致 crash 之前,尽最大努力做一次 Full GC是值得的。
    5.    Permanent Generation大小调整
        - -XX:PermSize == -XX:MaxPermSize, 同样,因为 permanent 堆扩张或收缩需要 Full GC 
        - -XX:PermSize=<n>  permanent generation 初始大小
        - -XX:MaxPermSize=<n>   permanent generation 最大值
        - 通常,我们不幸地很难拿捏这个值
    6.    禁止自适用策略

        - 采用固定大小策略,还意味着 -XX:YoungGenerationSizeIncrement=<m>  -XX:TenuredGenerationSizeIncrement=<n>  -XX:AdaptiveSizeDecrementScaleFactor=<r> 都不需要
    7.    尽可能多地在对象还在 Young Generation 时将其回收
        - 如果对象不是 long-live 对象,最好在 minor collection 时就将其回收。否则,
        - 转入 Old Generation,major collection 发生的频率低,会增加 Footprint
        - 越多的 old generation垃圾,是终导致 major collection 频率升高和GC时间更长
    8.    Footprint 不应该超过系统可用的物理内存
        - 如果 Swap 开启(对于服务器应用,并不是好主意),swap in/out 或许可能缓解一会儿,但是程序将遭受 内存与I/O 访问的时间的差异 + swap 时间。
        - 如果 Swap 没开启,哈哈,OOME! 
        - 对于个别 GC, 即使 swap 开启,如果大部分时间花在 swap in/out 蚂蚁搬家的话,也一样 OOME
        - 通常,目前的操作系统都有 mmap 用于 I/O,所以,不要贪心地将所有系统物理内存让 JVM 独吞。这样的话,你将失去来自操作系统的,免费而超优化的缓存功能!


2. 对Parallel GC / Parallel Old GC
    1.    默认情况下,该GC 认为它承载的系统只有它一个JVM
    2.    XX:ParallelGCThreads=<n>  指定并行的线程数
        - 考虑系统是否有多个JVM
        - 系统的处理器核心个数
        - 处理器是否支持一个核心多个硬件线程 (比如:优秀的UltraSPARC T1/T2)
    3.    –XX:MaxGCPauseMillis=n 指定最大 pause time 
    4.    –XX:GCTimeRatio=n 指定GC 花费时间与总体时间的比率:1/(1+n) 
    5.    因为 Parallel GC 为当今主流硬件开发并采用 ergonomics,因此默认情况下它自动调整过了,除非这种默认遇到严重问题
    6.    首先按照前述调整 young generation
    7.    降低 major GC 的频率
    8.    用于 low-pause 环境
        - 通过最大化 heap 空间
        - 通过避免或最小化 Survivor promotion 来避免 Full GC 


3. 给 CMS
    1.    首先按照前述调整 young generation
        - 别错误地认为,因为 CMS 并发能力发生在 old generation,因此忽略 young generation 的调整。
    2.    更加小心地避免 过早的Survivor Space promotion
        - CMS 因为采用 free lists 的原因,提升开销很大
        - 越频繁的 Survivor Space promotion 越有可能造成堆碎片(fragmentation )
    3.    只调整 minor GC 同样可以利用 CMS
        - CMS 只作为最后一道防线,在应用负载超过以往的最大值时
        - 将 Full GC 安排在非关键时间段以减少堆碎片(fragmentation )
    4.    无法避免堆碎片(fragmentation )
        - 最终导致找不到足够大的空间来完成分配请求,即使 free 空间总计大小大于分配请求大小
        - 分配器为了效率,采用近似策略,这近似值最终浪费不少空闲空间
        - 不同大小的“大对象”是元凶
    5.    -XX:ParallelCMSThreads=<n>  并行的 CMS 线程数
    6.    –XX:+CMSIncrementalMode  启用增量模式
    7.    –XX:+CMSIncrementalPacing 控制增量模式下,每次的工作量
    8.    卸载 permanent generation 中的类
        - 默认情况下,CMS 不卸载 permanent generation 中的类
        - -XX:+CMSClassUnloadingEnabled  -XX: +PermGenSweepingEnabled 激活卸载 
    9.    CMS 启动时机
        - 前面的算法描述部分介绍了CMS GC 需要提前启动以避免回退到 Serial Mark-Sweep-Compact 模式
        - 启动太早,将导致频繁的 GC 周期以及高并发的开销
        - 启动太晚,最终回退到Serial Mark-Sweep-Compact 模式 Full GC。没有利用到 CMS 的优势
        - -XX:+UseCMSInitiatingOccupancyOnly - 默认情况下,CMS 会自动统计来找到最佳启动时机(即 ergonomic 机制),但是为了采用下面的控制参数,需要使该ergonomic 机制失效
        - -XX:CMSInitiatingOccupancyFraction=<percent> - 当 old generation 占用率达到这一百分比时,启动 CMS GC
        - -XX:CMSInitiatingOccupancyFraction=<percent> - 由这个配置算出的结果(占有大小)应该比应用“稳态”下的 所有live 对象大小大得多,否则,CMS 将不停地发生。因为一达到这个百分比就会触发
        - -XX:CMSInitiatingPermOccupancyFraction=<percent>  当 permanent generation 占用率达到这一百分比时,启动 CMS GC (前提是 -XX:+CMSClassUnloadingEnabled 激活了)
        - 对于 mid/long live time 对象的比例小的应用,可以晚些启动,因为:
        - 很少 Survivor Space promotion发生
        - old generation 增长缓慢
        - 低频率地 major GC (即 CMS GC)
        - 对于 mid/long live time 对象的比例大的应用,相对早些启动,因为:
        - 很多 Survivor Space promotion发生
        - old generation 增长很快
        - 高频率地 major GC (即 CMS GC)
    10.    使 System.gc() 明确地使用 CMS
        - System.gc() 总是进行一次 Full GC, 即使没有必要,所以 -XX:+DisableExplicitGC 可以用来忽略所有的 System.gc() 调用
        - -XX:+ExplicitGCInvokesConcurrent / -XX:+ExplicitGCInvokesConcurrentAndUnloadClasses 指定 System.gc()采用 CMS 算法
        - 对于应用信赖于 Sofe, Weak, Phantom references / finalizers 时有用

 

(Serial GC 就不单独调整了,因为除非是 Embedded JAVA, 否则,它会慢慢地消失!)

 


八. 几个小工具


1.visualgc

这个工具很好很强大,使用起来也很简单,只要将你要监视的JVM的进程号作为命令行参数传递给它,它就可以生成一个花花绿绿的Dashboard,实时的显示出各个内存区的使用和GC情况,而且速度很快。最大的优势在于,它不需要停止应用,不需要修改JVM启动参数,想监视谁,就监视谁。

visualgc使用:      http://haoweishow.iteye.com/blog/1119117


2.gcviewer

使用参数-Xloggc:file和-XX:+PrintGCDetails打印gclog,然后使用gcviewer对gclog进行查看,它的优势在于可以生成统计数据,吞吐量,最大的和最小的停顿时间,Full GC时间占整个GC时间的百分比等,都可以使用这个工具查看。

3.jconsole

这个工具是Java5自带的,在目标JVM的启动参数里加入-Dcom.sun.management.jmxremote,然后启动jconsole,选择你要监视的JVM的进程号,即可,这个工具很强大,统计信息很全面,可是开销也很大,速度比较慢。

4.jstat

这个工具也是Java5自带的, 是个命令行工具,不喜欢看图的人一定喜欢它,它提供一堆options。每个option可以输出一组数据,数据的意义可以参看

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics