java 内存模型 持久代_【JVM】第1篇:JVM内存模型

JVM内存区域

37b4fd50fd40b66a0234ba9f112a6c2e.png

(1)程序计数器

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

(2)虚拟机栈

虚拟机栈的生命周期和线程同步,虚拟机栈中的局部变量表用于存储各种基本数据类型、对象引用类型;long和double会占用两个局部变量表的空间,局部变量表在编译器完成空间分配;

StackOverflowError:线程请求过多

OutOfMemoryError:如果虚拟机栈扩展时无法申请到足够内存;

(3)本地方法栈

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

(4)Java堆

存放实例对象的以及数组的;

OutOfMemoryError异常;

堆内存划分:

4800b51e870a72b18082c7309e4609a2.png

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 没有任何引用链时证明对象不可用了;

55316ce30a0418798335df5cf70a5f78.png

Java语言中可作为GC roots的对象有:

虚拟机栈引用的对象,方法区中类的静态属性、常量引用的对象,本地方法区中Native方法引用的对象;

垃圾收集算法

(1)标记-清除算法:

首先标记处所有需要回收的对象,标记完成后统一回收;执行下图所示:

fecdb3b58c5ed54efa8e269bd375c0a6.png

主要有两个缺点:一是执行效率不高,另一个是会产生大量不连续的内存碎片,导致再次分配较大对象时,无法得到连续的内存空间而再一次触发垃圾回收机制;

(2)复制算法:

为了解决标记-清除算法的效率问题,可以使用复制算法;它将内存划分为大小相等的两块,每次使用其中一块,当一块内存使用完了,就将存活的对象复制到另一块,然后回收已使用过的内存;

120c68e960cb8ebd389f3717501ac87f.png

该算法在对象存活较多时,效率底下,同时内存空间浪费;适合于新生代内存;

(3)标记-整理算法:

标记所有可回收对象,它不是直接对可回收对象进行清理;而是让所有的存活对象都向一端移动,然后直接清理掉端边界以外的内存;

c68e6ba1f94e275cdfaf6b1a7ae76fc6.png

(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参数来指定新生代内存最小和最大值,如果两个参数值一样,那么就相当于固定了新生代的大小。

7112e3955d1f549ca9851e8b2af41045.png

(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"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值