【JVM】第1篇:JVM内存模型

JVM内存区域

【JVM】第1篇:JVM内存模型

(1)程序计数器
看做当前线程所执行的字节码行号显示器;任意时刻,一个CPU都会执行一条线程中的指令,为了线程切换后能回到正确位置,每个线程都需要一个独立的线程计数器;执行native方法时,计数器值为空;此区域没有任何OutOfMemoryError的区域;

(2)虚拟机栈
虚拟机栈的生命周期和线程同步,虚拟机栈中的局部变量表用于存储各种基本数据类型、对象引用类型;long和double会占用两个局部变量表的空间,局部变量表在编译器完成空间分配;
StackOverflowError:线程请求过多
OutOfMemoryError:如果虚拟机栈扩展时无法申请到足够内存;

(3)本地方法栈
与虚拟机栈发挥的作用非常相似,区别是:虚拟机栈为Java方法服务的;本地方法栈是为native方法服务的;

(4)Java堆
存放实例对象的以及数组的;
OutOfMemoryError异常;

堆内存划分:
【JVM】第1篇:JVM内存模型

Young Generation:新生代
所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区:一个Eden ['i:dən]区,两个 Survivor[sə'vaɪvə]区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor[sə'vaɪvə]区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区。

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

Perm Area:永久带
用于存放静态文件,如今Java类、方法等。

(5)方法区
虚拟机加载的类信息,常量,静态变量等;
运行时常量池也是方法区的一部分:存放编译器生成的各种字面量和符号引用;
OutOfMemoryError异常;

Java对象创建以及内存布局

(1)虚拟机执行new指令 ----> 检查指令参数是否能在常量池中定位到类的符号引用 ----> 如果没有检查到引用代表的类,则先执行类的加载过程 ----> 类加载通过后,为新生对象分配内存 ----> 分配到的内存空间初始化为零 ----> 虚拟机对对象进行设置 ----> 执行init方法,完成对象创建
(2)对象在内存中存储布局可分为3块区域:
对象头:一部分存储对象自身运行时数据,如hashCode,线程持有的锁等;一部分是类型指针,确定对象是哪个类的实例;
实例数据:就是程序代码中定义的各种类型字段内容;
对齐填充:没有特殊含义,仅仅起到占位符的作用;

垃圾回收器和内存分配策略

如何判断对象已经死亡,既内存需要回收?

  • 引用计数器法:给对象添加一个引用计数器,每当一个地方引用它时,计数器值加一;引用失效,计数器值一,值为零时对象就不再使用了;缺点时无法解决对象之间的循环引用;
  • 可达性分析算法:当一个对象到 GC root 没有任何引用链时证明对象不可用了;

【JVM】第1篇:JVM内存模型
Java语言中可作为GC roots的对象有:
虚拟机栈引用的对象,方法区中类的静态属性、常量引用的对象,本地方法区中Native方法引用的对象;

垃圾收集算法

(1)标记-清除算法:
首先标记处所有需要回收的对象,标记完成后统一回收;执行下图所示:
【JVM】第1篇:JVM内存模型
主要有两个缺点:一是执行效率不高,另一个是会产生大量不连续的内存碎片,导致再次分配较大对象时,无法得到连续的内存空间而再一次触发垃圾回收机制;

(2)复制算法:
为了解决标记-清除算法的效率问题,可以使用复制算法;它将内存划分为大小相等的两块,每次使用其中一块,当一块内存使用完了,就将存活的对象复制到另一块,然后回收已使用过的内存;
【JVM】第1篇:JVM内存模型
该算法在对象存活较多时,效率底下,同时内存空间浪费;适合于新生代内存;

(3)标记-整理算法:
标记所有可回收对象,它不是直接对可回收对象进行清理;而是让所有的存活对象都向一端移动,然后直接清理掉端边界以外的内存;
【JVM】第1篇:JVM内存模型

(4)分代收集算法:
目前商业虚拟机都采用分代收集算法。

Java中垃圾回收器类型

垃圾回收器种类

  • Serial ['sɪərɪəl]收集器
    新生代收集器,使用复制算法,使用一个线程进行GC,串行,其它用户工作线程暂停。该收集器简单高效。
  • ParNew [pɑː][njuː]收集器
    新生代收集器,使用复制算法,Serial收集器的多线程版,用多个线程进行GC,并行,其它工作线程暂停。使用-XX:+UseParNewGC开关来控制使用ParNew+Serial Old收集器组合收集内存;使用-XX:ParallelGCThreads来设置执行内存回收的线程数。
  • Parallel Scavenge 收集器
    吞吐量优先的垃圾回收器,作用在新生代,使用复制算法,关注CPU吞吐量,即运行用户代码的时间/总时间。使用-XX:+UseParallelGC开关控制使用Parallel Scavenge+Serial Old收集器组合回收垃圾。
  • Serial Old收集器
    老年代收集器,单线程收集器,串行,使用标记整理算法,使用单线程进行GC,其它工作线程暂停。
  • Parallel Old ['pærəlel]收集器
    吞吐量优先的垃圾回收器,作用在老年代,多线程,并行,多线程机制与Parallel Scavenge差不错,使用标记整理算法,在Parallel Old执行时,仍然需要暂停其它线程。
  • CMS(Concurrent Mark Sweep)收集器
    老年代收集器,致力于获取最短回收停顿时间(即缩短垃圾回收的时间),使用标记清除算法,多线程,优点是并发收集(用户线程可以和GC线程同时工作),停顿小。使用-XX:+UseConcMarkSweepGC进行ParNew+CMS+Serial Old进行内存回收,优先使用ParNew+CMS(原因见Full GC和并发垃圾回收一节),当用户线程内存不足时,采用备用方案Serial Old收集。

如何读懂 GC 日志

其实每个垃圾回收器的日志是不一样的,但是为了方便查看日志它们也有一定的共性,如下所示的一段 GC 日志:

33.125: [GC [DefNew: 3324k->152k(3712k),0.0025925 secs] 3324->152k(11904k),0.003168 secs]

100.667: [Full GG [Tenurend: 0k->210k(1024k),0.00149142 secs]4603k->210k(19456k),
[Perm : 2999k->2999k(21248k)],0.0150007 secs] [Times:user=0.01 sys=0.00,real=0.02 secs]
  • 日志最前面的数字 "33.125:"和"100.667:" 表示GC发生的时间,是从Java虚拟机启动以来经过的时间
  • "[DefNew"、"[Tenured"、"[Perm"表示GC发生的时间,这里显示的区域名称和使用GC收集器密切相关,上面例子中使用的Serial收集器,它的新生代命名为Default New Generation,所以显示"[DefNew"
  • 方括号内部的3324k->152k(3712k)表示"GC前该区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)"
  • 方括号外的3324->152k(11904k)表示"GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)"
  • "0.0025925 secs"表示该内存区域GC所用时间,单位秒

和GC有关的JVM参数

做GC调优需要大量的实践,耐心和对项目的分析。做GC的调优很大程度上依赖于对系统的分析,系统拥有怎样的对象以及他们的平均生命周期。举个例子,如果一个应用大多是短生命周期的对象,那么应该确保Eden区足够大,这样可以减少Minor GC的次数。可以通过-XX:NewRatio来控制新生代和老年代的比例,比如-XX:NewRatio=3代表新生代和老年代的比例为1:3。需要注意的是,扩大新生代的大小会减少老年代的大小,这会导致Major GC执行的更频繁,而Major GC可能会造成用户线程的停顿从而降低系统吞吐量。JVM中可以用NewSize和MaxNewSize参数来指定新生代内存最小和最大值,如果两个参数值一样,那么就相当于固定了新生代的大小。
【JVM】第1篇:JVM内存模型
(1)Minor ['maɪnə] GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC;
(2)Major['meɪdʒə] GC:是清理老年代;
(3)Full GC:是清理堆空间,包括年轻代和老年代;
参考文章:Minor GC,Major GC,Full Gc

GC参数了解

内存相关常用参数

(1) -Xms : 堆内存的初始大小,默认是物理内存的1/64
(2) -Xmx : 堆内存的最大值,默认不超过物理内存
(3) -Xmn : 年轻代堆内存大小
(4) -Xss : 栈内存大小设置
(5) -XX:PermSize : 内存永久区的初始大小
(6) -XX:MaxPermSize : 内存永久区的大小
(7) -XX:SurvivorRatio : Eden区与Survivor区的大小比值;设置成8,则两个Survivor区与一个Eden区的比值是2:8
(8) -XX:+UseAdaptiveSizePolicy : 动态调整 Java 堆内存中各个区域的大小以及即进入老年代的年龄

收集器使用相关参数

(1) -XX:UseParallelGC : 使用 Parallel Scavenge(年轻代并行的多线程收集器) + Serial Old(老年代单线程收集器) 收集器
(2) -XX:UseParNewGC : 使用 ParNew + Serial Old 收集器
(3) -XX:ParallelGCThreads : 并行收集的线程数
(4) -XX:UseParallelOldGC : 使用 Parallel Scavenge(年轻代并行的多线程收集器) + Parallel Old 收集器
(5) -XX:MaxGCPauseMillis : 设置GC的最大停顿时间,仅在使用 Parallel Scavenge 收集器时生效
(6) -XX:+UseConcMarkSweepGC : 使用 ParNew + CMS + Serial Old 组合收集器
(7) -XX:GCTimeRatio : GC时间占总时间的比例,默认99,即允许 1% 的GC时间。仅在使用 Parallel Scavenge 收集器时生效

调试参数:

(1) -XX:+DisableExplicitGC : 忽略来自程序中System.gc()方法触发的垃圾回收
(2) -XX:+PrintGCDetails : 打印GC的相信信息
(3) -XX:+PrintHeapAtGC :
(4) -XX:+PrintTenuringDistribution :
(5) -XX:+PrintGCTimeStamps : 打印GC停顿耗时
(6) -XX:+PrintGCDateStamps :
(7) -XX:+HeapDumpOnOutOfMemoryError :
(8) -XX:ErrorFile :
(9) -XX:HeapDumpPath :

-XX:CMSFullGCsBeforeCompaction=0 : cms 收集器每次进入Full GC都进行碎片整理
-XX:+UseCMSCompactAtFullCollection
-XX:CMSInitiatingOccupancyFraction=80 : cms 收集器触发比例(当空间使用了80%后就触发回收)
-XX:ReservedCodeCacheSize=128m :
-XX:InitialCodeCacheSize=128m

jd线上机器jvm参数设置:

/export/servers/jdk1.6.0_25/bin/java -server

-Xms128M -Xmx256M -Xss256K
-XX:PermSize=32M
-XX:MaxPermSize=32M
-XX:+UseAdaptiveSizePolicy
-XX:+UseParallelGC
-XX:+UseParallelOldGC
-XX:GCTimeRatio=39
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps -Xloggc:/export/home/tomcat/logs/loghub.360buy.com/jcollector/gc.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:ErrorFile=/export/home/tomcat/logs/loghub.360buy.com/jcollector/hs_err.log
-XX:HeapDumpPath=/export/home/tomcat/logs/loghub.360buy.com/jcollector/heap_dump.hprof -classpath

mt线上机器jvm参数设置:

JVM_ARGS="-server -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Djava.io.tmpdir=/tmp -Djava.net.preferIPv6Addresses=false"
JVM_GC="

-XX:+DisableExplicitGC 
-XX:+PrintGCDetails 
-XX:+PrintHeapAtGC 
-XX:+PrintTenuringDistribution 
-XX:+UseConcMarkSweepGC 
-XX:+PrintGCTimeStamps 
-XX:+PrintGCDateStamps

"
JVM_GC=$JVM_GC"

-XX:CMSFullGCsBeforeCompaction=0 
        -XX:+UseCMSCompactAtFullCollection 
        -XX:CMSInitiatingOccupancyFraction=80
    "

JVM_HEAP="

-XX:SurvivorRatio=8 
    -XX:PermSize=256m 
    -XX:MaxPermSize=256m 
    -XX:+HeapDumpOnOutOfMemoryError 
    -XX:ReservedCodeCacheSize=128m 
    -XX:InitialCodeCacheSize=128m
"

JVM_SIZE="-Xmx4g -Xms4g -Xmn1g"

参考文章
JDK 1.8内存模型
全面理解Java内存模型
年轻代、年老点和持久代

相关推荐