JVM内存模型和调优

一、JVM运行内存模型

JVM内存模型主要包含五部分的内容:堆、栈、本地方法栈、方法区(元空间)、程序计数器。

堆分为了新生代(年轻代)和老年代,新生代中又分为了Eden区和Survior区,Survior区分为S0和S1。

堆:JVM管理的最大一块内存空间,它是所有线程所共享的一块区域。在虚拟机启动的时候创建,该区域的唯一目的就是为了存放对象实例,几乎所有通过new创建的实例对象都会被分配在该区域。

栈(虚拟机栈):也可以称为虚拟机线程栈,它是JVM中每个线程所私有的一块空间,每个线程都会有这么一块空间。它的生命周期是与线程的生命周期是绑定的。虚拟机栈描述了Java中方法执行时的内存模型,即每个方法被执行的时候,线程都会在自己的线程栈中同步创建一个栈帧(Stack Frame),用于存放局部变量表、操作数栈、动态连接和方法出口等信息,每个方法从调用到完成的过程,就对应着一个栈帧在线程栈中从入栈到出栈的过程。

本地方法栈:本地方法栈与虚拟机栈的作用是相似,本地方法栈为JVM调用的本地方法服务。一般都是由C语言实现,用native修饰。

程序计数器:只需要占用一小块的内存空间,每个线程都会有自己独立的程序计数器,主要功能就是记录当前线程执行到哪一行指令了,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、线程恢复等都依赖于计数器。

方法区(元空间):在JDK 8之前,方法区也称之为永久代,这部分区域与堆一样,是所有线程所共享的,它主要用于存放被虚拟机加载的类型信息、常量、静态变量以及即时编译器编译后的代码缓存等数据。对于一个Class文件,除了版本、字段、方法、接口等描述信息外,还有常量池表,主要用于编译器生成的各种字面量和符号引用,而这部分内容在Class文件加载后是存放在方法区的运行时常量池中。这个运行时常量池自然还包括了字符串常量池,但需要注意的是,在JDK 7以后的版本中,字符串常量池和静态变量等被移至到了Java堆区,而到了JDK 8,抛弃了之前永久代的概念,通过在本地内存中实现了元空间(Meta-space)来代替永久代,并把JDK 7中永久代剩余内容(主要是类型信息)全部移至到了元空间。

小结:堆和方法区是全局共享的;栈是线程私有的,每个线程在运行的过程中new出来的对象都放到堆上面,在栈帧中通过地址引用的方式找到堆中的对象。

        栈执行完了,栈帧上的局部变量会释放,但是堆的对象还未释放,这时候些对象在这时候就是无用的对象了,是一种垃圾对象,需要释放掉,如果不释放久而久之会导致堆内存溢出。因此jvm的垃圾回收机制主要就是对堆中的垃圾进行回收,我们在工作中常说的jvm调优,也就是如何配置堆内存的大小,减少GC频率和Full GC次数。

二、GC过程分析

        新new对象的时候,最开始是存放在Eden区,当Eden区快满的时候会触发GC,这时的GC只会对新生代进行GC,称为youngGC(也称MinorGC)。youngGC采用的是复制算法,把Eden区根据GCRoot可达性分析算法找到存活的对象,标记的是存活对象,然后清除阶段清除的是未标记的对象,把未清除的数据移动到S0区;到下一次Eden区快满的时候,会把Eden区和S0区未清除的数据移动到S1区,到下一次Eden区快满的时候,会把Eden区和S1区未清除的数据移动到S0区。如此往复,直到某些数据在被标记达到了一定次数的时候,就会把数据从S0和S1直接放到老年代。在youngGC的过程中如果把S0和Eden区的对象复制到S1区存放不下的时候会直接放到老年区,这里面有个阈值,超过阈值后会直接放到老年区。 老年代还会存放一些大对象,比如大数组等,避免在新生代复制到老年代效率低的问题。老年代内存快满的时候会触发Old GC,在Old GC的过程中往往还会触发young GC, 这时候把这个过程称为Full GC。Full GC主要用标记清理或标记整理的算法对垃圾进行回收。

三、堆内存设置不当可能出现的问题:

1)新生代设置过小

    一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入老年代,占据了老年代剩余空间,诱发Full GC

2)新生代设置过大

    一是新生代设置过大会导致老年代过小(堆总量一定),从而诱发Full GC;二是新生代GC耗时大幅度增加

    一般说来新生代占整个堆1/3比较合适

3)Survivor设置过小

    导致对象从eden直接到达老年代,降低了在新生代的存活时间

4)Survivor设置过大

    导致eden过小,增加了GC频率

    另外,通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收。

四、JVM性能调优思路

对JVM内存的调优主要通过设置堆内存的大小和垃圾回收器来减少GC的频率和Full GC的次数。

1.针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值;

2.新生代和老年代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率NewRadio来调整二者之间的大小。

比如新生代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止新生代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。

3.新生代和老年代设置多大才算合理

        1)更大的新生代必然导致更小的老年代,大的新生代会延长普通GC的周期,但会增加每次GC的时间;小的老年代会导致更频繁的Full GC

        2)更小的新生代必然导致更大老年代,小的新生代会导致普通GC很频繁,但每次的GC时间会更短;大的老年代会减少Full GC的频率

        如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的新生代;如果存在相对较多的持久对象,老年代应该适当增大。但很多应用都没有这样明显的特性。一般说来新生代占整个堆1/3(也就是新生代与老年代的默认比例1:2)比较合适,具体情况要根据项目来调整,尽量不要出现新生代比老年代的内存大的情况。

小结:

(1)本着Full GC尽量少的原则,让老年代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 。

(2)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给老年代至少预留1/3的增长空间。

4.在配置较好的机器上(比如多核、大内存),可以为老年代选择并行收集算法: -XX:+UseParallelOldGC 。

5.线程栈的设置:每个线程默认会开启1M的栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。-Xss256k

  

五、常见的JVM配置

1、堆设置

-Xms:初始堆大小

-Xmx:最大堆大小

-XX:NewSize=n:设置新生代最小大小

-XX:MaxNewSize=n:设置新生代最大大小

-Xmn256m 这个表示将-XX:NewSize和-XX:MaxNewSize都设置为256m

-XX:NewRatio=n:设置新生代和老年代的比值。如:为3,表示新生代与老年代比值为1:3,新生代占整个堆的1/4

-XX:SurvivorRatio=n:新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。例如:-XX:SurvivorRatio=8,表示Eden与Survivor的比是8:2,一个Survivor区占整个新生代的1/10。

-XX:PermSize=n: 永久代最小大小(jdk8之后没有永久代,而是元空间,这个参数不可用)

-XX:MaxPermSize=n:设置持久代最大大小(jdk8之后没有永久代,而是元空间,这个参数不可用)
-XX:MetaspaceSize,初始元空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不> 超过MaxMetaspaceSize时,适当提高该值。(jdk8后可用)

-XX:MaxMetaspaceSize,最大元空间,默认是没有限制的。(jdk8后可用)

2、收集器设置

-XX:+UseSerialGC:设置串行收集器

-XX:+UseParallelGC:设置并行收集器

-XX:+UseParalledlOldGC:设置并行年老代收集器

-XX:+UseConcMarkSweepGC:设置并发收集器

这些收集器在jdk8之后可以使用G1垃圾回收器替换,G1垃圾回收器效率更好。

3、垃圾回收统计信息

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

4、并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。

-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

5、并发收集器设置

-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。

-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

参考:JVM原理讲解和调优 - IsaacZhang - 博客园

参考:JVM系列(一)之内存模型 - 逐梦北京 - 博客园

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
视频目录 第1节说在前面的话 [免费观看] 00:05:07分钟 | 第2节整个部分要讲的内容说明 [免费观看] 00:06:58分钟 | 第3节环境搭建以及jdk,jre,jvm的关系 [免费观看] 00:20:48分钟 | 第4节jvm初体验-内存溢出问题的分析与解决 [免费观看] 00:17:59分钟 | 第5节jvm再体验-jvm可视化监控工具 [免费观看] 00:21:17分钟 | 第6节杂谈 [免费观看] 00:12:37分钟 | 第7节Java的发展历史00:27:24分钟 | 第8节Java的发展历史续00:02:27分钟 | 第9节Java技术体系00:08:46分钟 | 第10节jdk8的新特性00:07:31分钟 | 第11节lanmbda表达式简介00:07:02分钟 | 第12节Java虚拟机-classic vm00:06:06分钟 | 第13节Java虚拟机-ExactVM00:03:35分钟 | 第14节Java虚拟机-HotSpotVM00:04:23分钟 | 第15节Java虚拟机-kvm00:03:04分钟 | 第16节Java虚拟机-JRockit00:04:12分钟 | 第17节Java虚拟机-j900:04:23分钟 | 第18节Java虚拟机-dalvik00:02:20分钟 | 第19节Java虚拟机-MicrosoftJVM00:03:57分钟 | 第20节Java虚拟机-高性能Java虚拟机00:02:58分钟 | 第21节Java虚拟机-TaobaoVM00:03:06分钟 | 第22节Java内存区域-简介00:07:56分钟 | 第23节Java内存区域-Java虚拟机栈00:12:04分钟 | 第24节Java内存区域-程序计数器00:12:54分钟 | 第25节Java内存区域-本地方法栈00:02:39分钟 | 第26节Java内存区域-堆内存00:05:08分钟 | 第27节Java内存区域-方法区00:06:32分钟 | 第28节Java内存区域-直接内存和运行时常量池00:15:53分钟 | 第29节对象在内存中的布局-对象的创建00:21:19分钟 | 第30节探究对象的结构00:13:47分钟 | 第31节深入理解对象的访问定位00:08:01分钟 | 第32节垃圾回收-概述00:06:20分钟 | 第33节垃圾回收-判断对象是否存活算法-引用计数法详解00:14:08分钟 | 第34节垃圾回收-判断对象是否存活算法-可达性分析法详解00:07:09分钟 | 第35节垃圾回收算法-标记清除算法00:04:36分钟 | 第36节垃圾回收算法-复制算法00:14:35分钟 | 第37节垃圾回收算法-标记整理算法和分代收集算法00:05:24分钟 | 第38节垃圾收集器-serial收集器详解00:09:45分钟 | 第39节垃圾收集器-parnew收集器详解00:04:53分钟 | 第40节垃圾收集器-parallel收集器详解00:11:02分钟 | 第41节垃圾收集器-cms收集器详解00:14:58分钟 | 第42节最牛的垃圾收集器-g1收集器详解00:18:04分钟 | 第43节内存分配-概述00:04:23分钟 | 第44节内存分配-Eden区域00:22:51分钟 | 第45节内存分配-大对象直接进老年代00:06:42分钟 | 第46节内存分配-长期存活的对象进入老年代00:03:40分钟 | 第47节内存分配-空间分配担保00:04:54分钟 | 第48节内存分配-逃逸分析与栈上分配00:10:32分钟 | 第49节虚拟机工具介绍00:10:27分钟 | 第50节虚拟机工具-jps详解00:11:20分钟 | 第51节虚拟机工具-jstat详解00:09:20分钟 | 第52节虚拟机工具-jinfo详解00:05:03分钟 | 第53节虚拟机工具-jmap详解00:08:48分钟 | 第54节虚拟机工具-jhat详解00:08:10分钟 | 第55节虚拟机工具-jstack详解00:10:19分钟 | 第56节可视化虚拟机工具-Jconsole内存监控00:07:09分钟 | 第57节可视化虚拟机工具-Jconsole线程监控00:12:18分钟 | 第58节死锁原理以及可视化虚拟机工具-Jconsole线程死锁监控00:10:38分钟 | 第59节VisualVM使用详解00:08:03分钟 | 第60节性能调优概述00:11:22分钟 | 第61节性能调优-案例100:23:28分钟 | 第62节性能调优-案例200:10:05分钟 | 第63节性能调优-案例300:12:41分钟 | 第64节前半部分内容整体回顾00:15:41分钟 | 第65节Class文件简介和发展历史 [免费观看] 00:11:26分钟 | 第66节Class文件结构概述 [免费观看] 00:16:50分钟 | 第67节Class文件设计理念以及意义 [免费观看] 00:13:41分钟 | 第68节文件结构-魔数 [免费观看] 00:09:49分钟 | 第69节文件结构-常量池 [免费观看] 00:23:44分钟 | 第70节文件结构-访问标志 [免费观看] 00:11:36分钟 | 第71节文件结构-类索引00:11:26分钟 | 第72节文件结构-字段表集合00:13:21分钟 | 第73节文件结构-方法表集合00:10:06分钟 | 第74节文件结构-属性表集合00:18:23分钟 | 第75节字节码指令简介00:09:18分钟 | 第76节字节码与数据类型00:09:34分钟 | 第77节加载指令00:09:33分钟 | 第78节运算指令00:10:24分钟 | 第79节类型转换指令00:13:42分钟 | 第80节对象创建与访问指令00:09:38分钟 | 第81节操作树栈指令00:03:27分钟 | 第82节控制转移指令00:11:58分钟 | 第83节方法调用和返回指令00:06:37分钟 | 第84节异常处理指令00:09:44分钟 | 第85节同步指令00:07:34分钟 | 第86节类加载机制概述00:07:26分钟 | 第87节类加载时机00:13:15分钟 | 第88节类加载的过程-加载00:15:15分钟 | 第89节类加载的过程-验证00:10:24分钟 | 第90节类加载的过程-准备00:05:40分钟 | 第91节类加载的过程-解析00:14:04分钟 | 第92节类加载的过程-初始化00:19:41分钟 | 第93节类加载器00:22:41分钟 | 第94节双亲委派模型00:17:03分钟 | 第95节运行时栈帧结构00:08:46分钟 | 第96节局部变量表00:20:48分钟 | 第97节操作数栈00:08:36分钟 | 第98节动态连接00:02:56分钟 | 第99节方法返回地址和附加信息00:03:24分钟 | 第100节方法调用-解析调用00:09:49分钟 | 第101节方法调用-静态分派调用00:16:21分钟 | 第102节方法调用-动态分派调用00:09:02分钟 | 第103节动态类型语言支持00:09:27分钟 | 第104节字节码执行引擎小结00:03:38分钟 | 第105节总结与回顾00:10:55分钟

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值