JVM
2021-08-03 14:26:08 71 举报
AI智能生成
JVM内存区域整理
作者其他创作
大纲/内容
内存区域
存放类的方法区(元数据空间)
存放我们自己写的各种类相关的信息
类加载到jvm之后,就会放在这个内存区域
执行代码指令的程序计数器
用来记录当前执行的字节码指令的位置的,也就是记录目前执行到了哪一条字节码指令
线程私有
Java虚拟机栈
保存每个方法内的局部变量等数据
每个线程都有自己的java虚拟机栈
调用执行任何方法时,都会给方法创建栈帧然后入栈
栈帧里面存放这个方法对应的局部变量之类的数据
方法执行完毕之后就出栈
java堆内存
存放我们在代码中创建的各种对象
本地方法栈
存放各种native方法的局部变量表之类的信息
堆外空间
JVM运行原理
Young GC
运行流程
1、采用复制算法,从GC Roots(方法的局部变量、类的静态变量)开始追踪,标记出来存活的对象
2、把存活对象都放入第一个Survivor区域中,也就是S0区域
3、垃圾回收器就会直接回收掉Eden区里剩余的全部垃圾对象,在整个这个垃圾回收的过程中全程会进入Stop the Wold状态,也就是暂停系统工作线程,系统代码全部停止运行,不允许创建新的对象
4、一旦垃圾回收全部完毕之后,也就是存活对象都进入了Survivor区域
5、下一次如果Eden区满了,就会再次触发Young GC,把Eden区和S0区里的存活对象转移到S1区里去,然后直接清空掉Eden区和S0区中的垃圾对象
运行的时候是基于多线程并发执行垃圾回收的
进入老年代的条件
1、一个对象在年轻代里躲过15次垃圾回收,年龄太大了,寿终正寝,进入老年代
2、对象太大了,超过了一定的阈值,直接进入老年代,不走年轻代
3、一次Young GC过后存活对象太多了,导致Survivor区域放不下了,这批对象会进入老年代
4、可能几次Young GC过后,Surviovr区域中的对象占用了超过50%的内存,此时会判断如果年龄1+年龄2+年龄N的对象总和超过了Survivor区域的50%,此时年龄N以及之上的对象都进入老年代,这是动态年龄判定规则
Full GC触发的条件
1、老年代自身可以设置一个阈值,有一个JVM参数可以控制,一旦老年代内存使用达到这个阈值,就会触发Full GC,一般建议调节大一些,比如92%
2、在执行Young GC之前,如果判断发现老年代可用空间小于了历次Young GC后升入老年代的平均对象大小的话,那么就会在Young GC之前触发Full GC,先回收掉老年代一批对象,然后再执行Young GC。
3、如果Young GC过后的存活对象太多,Survivor区域放不下,就要放入老年代,要是此时老年代也放不下,就会触发Full GC,回收老年代一批对象,再把这些年轻代的存活对象放入老年代中
加载机制
流程
加载->验证->准备->解析->初始化->使用->卸载
加载:什么时候加载?就是在代码中用到这个类的时候
验证:校验是否符合jvm规范
准备:
1、分配内存空间
2、给类变量初始值
解析:从符号引用转为直接引用的过程
初始化:执行类的初始化代码
静态代码块的执行
给类变量赋值
先初始化父类,再初始化自己
类加载器
Bootstrap ClassLoader(启动类加载器)
负责加载java目录下的核心类
java安装目录下的“lib”目录中的核心类库
Extension ClassLoader(扩展类加载器)
加载java安装目录下“lib/ext”中的类库
Application ClassLoader(应用程序加载器)
负责去加载“ClassPath”环境变量所指定的路径中的类
可以理解为去加载我们写好的java代码
自定义类加载器
可以根据自己的需求加载我们的类
双亲委派机制
类加载器有亲子层级结构,启动类加载器在第一层,扩展类加载器在第二层,应用程序加载器在第三层,自定义类加载器在第四层
加载一个类时会首先委派自己的父类加载器去加载,最终传导到顶层的类加载器去加载
如果父类加载器在自己负责的范围内,没找到这个类,则下推加载权利给自己的子类加载器
这个机制可以避免多层级的加载器结构重复加载某些类
垃圾回收机制
后台自动运行的线程
分代模型
背景
大部分对象的存活周期都是极短的
少数对象是长期存活的
年轻代
创建之后很快就会回收
大部分正常的对象都优先在新生代分配内存
长期存活的对象会躲过多次垃圾回收
如果一个实例对象在新生代中,成功的在15次垃圾回收之后,还是没有被回收掉,就说明他已经15岁了,就会被认为是会长期存活在内存里的对象
年轻代又分成了Eden和2个Survivor,默认比例是8:1:1
老年代
永久代
对应方法区
核心参数
-Xms
Java堆内存的大小
-Xmx
Java堆内存的最大大小
一般-Xms与-Xmx通常设置完全一样的大小
-Xmn
Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了
-XX:PermSize
永久代大小
JDK1.8之后:-XX:MetaspaceSize
-XX:MaxPermSize
永久代最大大小
JDK1.8之后:-XX:MaxMetaspaceSize
-Xss
每个线程的栈内存大小
-XX:+CMSParallelInitialMarkEnabled表示在初始标记的多线程执行,减少STW;
-XX:+CMSScavengeBeforeRemark:在重新标记之前执行minorGC减少重新标记时间;
-XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,降低STW;
-XX:CMSInitiatingOccupancyFraction=92和-XX:+UseCMSInitiatingOccupancyOnly配套使用,如果不设置后者,jvm第一次会采用92%但是后续jvm会根据运行时采集的数据来进行GC周期,如果设置后者则jvm每次都会在92%的时候进行gc;
-XX:+PrintHeapAtGC:在每次GC前都要GC堆的概况输出
JVM优化
如何预估性的合理设置JVM参数
1、估算一下自己负责的系统每个核心接口每秒多少次请求,每次请求会创建多少个对象,每个对象大概多大,每秒钟会使用多少内存空间?
2、接着就可以估算出来Eden区大概多长时间会占满
3、然后就可以估算出来多长时间会发生一次Young GC,而且可以估算一下发生Young GC的时候,会有多少对象存活下来,会有多少对象升入老年代里,老年代对象增长的速率大概是多少,多久之后会触发一次Full GC。
原则:尽可能让每次Young GC后存活对象远远小于Survivor区域,避免对象频繁进入老年代触发Full GC。
压测之后合理调整JVM参数
1、Eden区的对象增长速率多块?
2、Young GC频率多高?
3、一次Young GC多长耗时?
4、Young GC过后多少对象存活?
5、老年代的对象增长速率多高?
6、Full GC频率多高?
7、一次Full GC耗时?
用jstat等工具去观察JVM的运行内存模型
线上频繁Full GC表现
1、机器CPU负载过高;
2、频繁Full GC报警
3、系统无法处理请求或者处理过慢
频繁Full GC的几种常见原因
1、系统承载高并发请求,或者处理数据量过大,导致Young GC很频繁,而且每次Young GC过后存活对象太多,内存分配不合理,Survivor区域过小,导致对象频繁进入老年代,频繁触发Full GC。
2、系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年代,必然频繁触发Full GC
3、系统发生了内存泄漏,莫名其妙创建大量的对象,始终无法回收,一直占用在老年代里,必然频繁触发Full GC
4、Metaspace(永久代)因为加载类过多触发Full GC
5、误调用System.gc()触发Full GC
建议:统一的JVM参数模板
“-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom”
0 条评论
下一页