admin管理员组文章数量:1648916
JVM参数调优利器 —— XXFox HeapDump - Java虚拟机参数分析
自动获取本地进程分析优化-XPocket PerfMa 笨马网络 网站性能优化,高并发模拟,影子库,常用的生产环境,全链路,接口,服务器性能测试/监测-网络性能分析
1. Java 类加载机制
https://www.processon/view/link/5e69db12e4b055496ae4a673#map 儒猿群分享JVM知识点
jvm | ProcessOn免费在线作图,在线流程图,在线思维导图 | JVM图
类加载器 加载顺序
破坏双亲委派机制:
(1) tomcat (2) SPI (3) OSGi 以上3个会是破坏双亲委派机制的
2. JVM 内存模型
Method Area(方法区)
- 方法区是各个线程共享的内存区域,在虚拟机启动时创建 . 别名叫做Non-Heap(非
堆),目的是与Java堆区分开来
- 用于存储已被虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据
- 当方法区无法满足内存分配需求时,将抛出 OutOfMemoryError 异常
【常量池】主要存储两方面内容:字面量(Literal)和符号应用(Symbolic References)
- 字面量 : 文本字符串, final 修饰等
- 符号引用:类和接口的全限定名、字段名称和描述符、方法名称和描述符
javap -v -p Person.class 进行反编译,查看字节码信息和指令等信息
JDK1.8之后,这块区域叫 Metaspace ,可以认为是 “元数据空间” 的意思 。方法区与堆、原空间关系:方法区中的 静态变量和字符串常量池是放在 堆中的
程序计数器
那么在执行字节码指令的时候,JVM里就需要一个特殊的内存区域了,那就是“程序计数器” 。
这个程序计数器就是用来 记录当前执行的字节码指令的位置的 ,也就是记录目前执行到了哪一条字节码指令。 JVM是支持多个线程的。所以就会有多个线程来并发的执行不同的代码指令 。 因此每个线程都会有自己的一个程序计数器,专门记录当前这个线程目前执行到了哪一条字节码指令了。如下图Heap 堆
- 堆是存储Java对象的地方。被所有线程共享 ,存储Java对象实例以及数组。
- 堆也是java垃圾收集器管理的主要区域
- 堆内存结构还可以分块成: 老年代(old),年轻代(Young) . Young:Old 为 1:2
ReplicaManager replicManager = new ReplicManager()
局部变量表里的 “replicaManager” 指向了Java堆内存里的 ReplicaManager 对象
静态变量 在方法区 ,其实例也是在Java堆内存中的public class Test {
private static User user = new User();
}
静态变量 user 引用了 User对象,这是长期驻留在内存里的。
但是哪怕是这种对象,其实刚开始你通过“new User()”代码来实例化一个对象时,他也是分配在新生代里的
年轻代(Young)区
分为:有Eden区、两个 Survivor区;(From Survivor空间(s0)、To Survivor空间(s1)),比例:8:1:1
- 所有新创建的对象都在Eden区,当Eden区满后,出发minorGc将Eden区仍然存活的对象复制到其中一个Survivor区,另一个Survivor区中存活的对象也复制到这个Survivor区中,以保证始终有一个Survivor区是空的(两个Survivor 是交替的 一会from 一会 to)
- 使用标记整理算法。
老年代(old) 与 年轻代 比例:
Java Virtual Machine Stacks(虚拟机栈)
- 虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个 Java 线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创 建
- 每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。
- 调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。
图解栈和栈帧
出栈和入栈顺序如下 : a 调用 b , b 调用 cvoid a(){
b();
}
void b(){
c();
}
void c()
{ }
出栈和入栈顺序 : 先进后出,后进先出
随着一些方法执行完毕(栈帧出栈了),大部分新生代里的对象就没有人引用了,就成了垃圾对象.
栈帧
栈帧 : 每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。每个栈帧中包括
- 局部变量表(Local Variables)、
- 方法中定义的局部变量以及方法的参数存放在这张表中
局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。
- 方法中定义的局部变量以及方法的参数存放在这张表中
- 操作数栈(Operand Stack)、
- 操作数栈:以压栈和出栈的方式存储操作数的
- 指向运行时常量池的引用(A reference to the run-time constant pool)、
- 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调 用过程中的动态连接 (Dynamic Linking) 。
- 方法返回地址(Return Address) 和 附加信息。
- 方法返回地址:当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇 见异常,并且这个异常没有在方法体内得到处理
3. 一个对象创建流程
第一步 :栈上分配 如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上,这样,对象所占用的内存空间就可以随栈帧出栈而销毁。如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以减小垃圾收集器的负载
栈上分配的技术基础:
一是逃逸分析:逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体
二是标量替换:允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
第二步 :TLAB分配 (Thread Local Allocation Buffer)即线程本地分配缓存区,这是一个线程专用的内存分配区域。TLAB是在堆上 Eden区的 。名字上看是一个线程专用的内存分配区域,是为了加速对象分配而生的,每一个线程都会产生一个TLAB区域,该线程独享工作区域,Java虚拟机使用这种TLAB区域来避免多线程冲突问题,提高了对象分配的效率。TLAB空间一般不会太大,当大对象无法再TLAB分配时,则会直接分配到堆上 。Tlab 分配独享,使用共享(堆是线程共享的)
-XX:+TLABSize 设置TLAB 大小 TLAB区域大小是Eden区域的1%
-XX:+TLABRefillWasteFraction 设置维护进入TLAB空间的单个对象大小,它是一个比例值,默认为64。即如果对象大于整个空间的1/64,则在堆创建对象
第三步 :是否满足进入老年代
-XX:PretenureSizeThreshold=1000 单位是字节。1000byte 设置新生代中的对象大小超过指定大小后,直接晋升老年代。
第四步 :新生代分配
4. JVM内存参数
在JVM内存分配中,有几个参数是比较核心的,如下所示。
-
-Xms:Java堆内存的大小
-
-Xmx:Java堆内存的最大大小
-
-Xmn:Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了
-
-XX:PermSize:永久代大小
-
-XX:MaxPermSize:永久代最大大小
-
-Xss:每个线程的栈内存大小
-Xms和-Xmx,分别用于设置Java堆内存的刚开始的大小,以及允许扩张到的最大大小。通常来说,都会设置为完全一样的大小
-XX:PermSize和-XX:MaxPermSize,分别限定了永久代大小和永久代的最大大小。通常这两个数值也是设置为一样的
JDK 1.8以后的版本,那么这俩参数被替换为了-XX:MetaspaceSize和-XX:MaxMetaspaceSize
5. 垃圾收集器
1. serial
单线程收集器, 简单高效, 收集过程会有 STW, 用于新生代。 使用复制算法
2. serial Old
serial 的老年代版本: 单线程收集器, 简单高效, 收集过程会有 STW, 用于老生代。 使用标记-整理算法
3. ParNew
多线程版本, 多核, 收集过程会有 STW,但是停顿时间减少,吞吐量更大 。
-XX:+UseParNewGC 新生代使用 ParNew
业务代码时间 / (业务代码时间+垃圾收集时间) = 吞吐率 99s / 99s + 1s(垃圾收集时间) = 99%
4. Parallel Scavenge
新生代垃圾收集器 使用复制算法
5. Parallel Old
Parallel Scavenge的老年代版本: 更加关注吞吐率。 使用标记整理算法
-- 能否实现 业务代码线程与垃圾收集线程 同时进行呢 ? > 分治算法
分治算法, 把垃圾收集线程分为多步骤。细化每一个步骤 >
5.1 CMS 垃圾收集器
CMS是基于“标记-清除”算法实现的,整个过程分为4个步骤:
1、初始标记(CMS initial mark)。
2、并发标记(CMS concurrent mark)。
3、重新标记(CMS remark)。
4、并发清除(CMS concurrent sweep)。
CMS 垃圾收集器收集完整步骤:
- Phase1 :Initial Mark【初始标记】
- Phase2 : Concurrent Mark 【并发标记】
- Phase3 : Concurrent Preclean【并发预先清除】
- Phase4 : Concurrent Abortable Preclean【并发可能失败的预先清除】
- Phase5 : Final Remark【最终重新标记】
- Phase6 : Concurrent Sweep【并发清除】
- Phase7 : Concurrent Reset【并发重置】
-XX:+UseConcMarkSweepGC
, 这个参数表示对于老年代的回收采用CMS
CMS 垃圾收优点:
1.并发收集(concurrent)
2.低停顿
CMS是基于“标记-清除” 那么就会有空间碎片
CMS 垃圾收集器 :
缺点:1. 并发标记过程,抢占CPU资源。不是4核以上,不如 serial Old。因为会抢Cpu,
2. 基于“标记-清除” 那么就会有空间碎片
3. 并发模式失败。 并发收集器在老年代填满之前,完成对象回收, 或者 空闲内存不能满足一个内存的分配要求, 会强行 STw 。 并发模式失败 ,会 serial Old 作为备选方案
5.2 G1 收集器
让CMS停顿时间更短, 短到开发者自己设置, 某种程度上可以解决空间碎片 , 4核8G,最低配置要求使用G1
G1 先回收优先级高的,剩下来不及回收的,下次再来回收。
G1 把内存划分为大小相等的 Region 。把大对象(超过 Regiony一般大小)放在 Humongous 中
JDK11出现的垃圾收集器 ZGC 10ms内的停顿时间。
串行收集器: serial 、 serial Old
并行收集器: ParNew 、Parallel Scavenge 、Parallel Old
并发收集器(垃圾收集线程和业务线程同时运行):CMS 、 G1
G1收集器使用要求
1. 堆内存6GB是最小要求,会卡
2. 50%以上堆内存都被存活对象占用
3. 对象的分配, 垃圾收集频率高,时间长(0.5-1s)
6 JDK 参数
标准参数:不随JDK版本变化而变化的参数
非标准参数:
- -X : 随JDK版本变化而变化的参数(用处不是特别大)
- -XX :
- boolean 类型 -XX:[ +/- ]value 。比如 -XX:UserG1GC -XX:+UserSerialGC value前面的+代表启用,- 就是禁用,不使用。 启用:-XX:+UserSerialGC 禁用 :-XX:-UserSerialGC
- K-V 类型 name = value 。比如 -XX:initialHeapSize=100M 初始化堆内存100M , -XX:MaxHeapSize = 100M
其它参数:
比如 -Xms100M 其实等价于 -XX:initialHeapSize=100M 这是简写
-Xmx100M 等价于 -XX:MaxHeapSize = 100M
-Xxx100 等价于 -XX:ThreadStackSize = 100M 栈深度
7 IDEA 查看 Java 的默认参数
IDEA 查看 Java 的默认参数 java -XX:+PrintFlagsFinal -version
输出到文件夹: java -XX:+PrintFlagsFinal -version > 1.txt
在IDEA 中设置启动是打印JVM参数:参数单位是 byte 字节
打印如下:
' = ' 前面的冒号 ,代表JVM启动的时候修改过,最右边一列,{manageable} 代表可以 JVM启动的时候实时修改参数值。{product} 代表不可以实时修改JVM参数 。
JVM 调试
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom(这个目录自己写一个) 输出 dump文件
JVM 垃圾收集器:
版权声明:本文标题:JVM 内存模型 、对象分配流程、 垃圾收集器 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1729507520a1203716.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论