JVM技术体系实战
2020-12-18 14:35:02 0 举报
AI智能生成
JVM体系
作者其他创作
大纲/内容
实战案例
内存溢出有哪些情况
G1回收算法
parnew+cms痛点
stw(Stop The World) 导致系统停止工作
消耗cpu
系统的停顿影响时间是不可控的
所有的堆被分成很多的region区域
最多可以分配2048个region
年轻代和老年代都只是逻辑概念
老年代的region如果超过40%大概800个大小就会触发mixed GC 混合GC
可以手动指定回收的时间 -XX:MaxGCPauseMills单位毫秒
追踪每个对象的大小以及回收时间,尽量把系统的影响控制在我们设置的时间内
region不属于固定的年轻代和老年代,这个是G1回收器自动控制和调控
如果想使用G1回收器 加上参数-XX:+UseG1GC
region的大小是2的倍数也可以用 -XX:G1HeapRegionSize指定大小,最好不用设置,保持默认
G1中大对象的判断是超过了region的一半就认为是大对象
大对象是不会进入老年代的,还是用年轻代的承担
大对象是不会进入老年代的,还是用年轻代的承担
年轻代
初始化Eden区域是5%的大小 大概100region
也有eden区和survive区概念默认还是8:1:1
大概Eden占用1000个2个S200个左右
大概Eden占用1000个2个S200个左右
如果超过60%的region就会触发minorGC
采用复制算法
老年代
标记对象 系统处于停滞 还是非常快的
并发标记所有的GC ROOTS比较耗时,要追踪所有的存活对象,系统可以继续运行创建对象
混合回收,这个时候就会把停顿回收的时间控制在我们设定的时间之内,如果不能回收完成就继续让系统运行一段时间
在进行回收,至到回收完成,就是循环的进行回收又恢复运行,达到新生代的region在5%就停止了,默认是进行8次
在进行回收,至到回收完成,就是循环的进行回收又恢复运行,达到新生代的region在5%就停止了,默认是进行8次
-XX:MaxGCPauseMills
要根据线上系统合理的调节这个值,因为G1的垃圾回收是很智能的
他会尽量的根据你这个值来进行垃圾回收,而不是一定要到了新生代
的对象达到60%才回收,本身我们的系统就是要分配一个超大的内存
给应用程序,而我们设置的回收时间又特别久,这样回收也非常的慢
对系统的影响卡顿也非常的严重,导致系统卡顿很长时间,如果对系统
延迟有严格要求的就会非常的不好,所以我在设置这个值的时候要合理
评估系统的新生对象大小,多久可以到系统的60%,对系统的延迟可以
容忍到多久,尽量的设置一个比较优的值来作为回收卡顿时间,混合回收
也是会按照这个时间来进行回收。
他会尽量的根据你这个值来进行垃圾回收,而不是一定要到了新生代
的对象达到60%才回收,本身我们的系统就是要分配一个超大的内存
给应用程序,而我们设置的回收时间又特别久,这样回收也非常的慢
对系统的影响卡顿也非常的严重,导致系统卡顿很长时间,如果对系统
延迟有严格要求的就会非常的不好,所以我在设置这个值的时候要合理
评估系统的新生对象大小,多久可以到系统的60%,对系统的延迟可以
容忍到多久,尽量的设置一个比较优的值来作为回收卡顿时间,混合回收
也是会按照这个时间来进行回收。
其实G1调优的核心关键
还是要考虑我们存活的对象是否让S区域能够放下,不能直接进入老年代
合理的调整S区域的大小避免年龄动态规则让对象进入老年代
尽量让系统不要过多的进行系统GC,快速的进行回收
减小混合GC,因为混合回收还是比较慢的
减小混合GC,因为混合回收还是比较慢的
region回收规则
默认是低于自身85%就才可以回收
回收成本就不会太高
回收成本就不会太高
-XX:G1MixedGCLiveThresholdPercent参数设置
默认是85
默认是85
G1回收日志分析
0.904: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0046285 secs]
[Parallel Time: 3.0 ms, GC Workers: 4]
[GC Worker Start (ms): Min: 905.0, Avg: 905.1, Max: 905.3, Diff: 0.3]
[Ext Root Scanning (ms): Min: 1.1, Avg: 1.4, Max: 2.0, Diff: 0.9, Sum: 5.5]
[Update RS (ms): Min: 0.0, Avg: 0.4, Max: 1.7, Diff: 1.7, Sum: 1.7]
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.0, Avg: 0.9, Max: 1.6, Diff: 1.6, Sum: 3.7]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.2]
[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[GC Worker Total (ms): Min: 2.6, Avg: 2.8, Max: 2.9, Diff: 0.3, Sum: 11.2]
[GC Worker End (ms): Min: 907.9, Avg: 907.9, Max: 907.9, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
[Other: 1.6 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.1 ms]
[Humongous Reclaim: 0.8 ms]
[Free CSet: 0.0 ms]
[Eden: 3072.0K(51.0M)->0.0B(50.0M) Survivors: 0.0B->1024.0K Heap: 232.5M(1024.0M)->1800.1K(1024.0M)]
[Times: user=0.00 sys=0.00, real=0.01 secs]
分析
0.904: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0046285 secs]
0.904秒开始进行一次youngGC CPU耗时0.0046285秒
[Parallel Time: 3.0 ms, GC Workers: 4]
[GC Worker Start (ms): Min: 905.0, Avg: 905.1, Max: 905.3, Diff: 0.3]
[Ext Root Scanning (ms): Min: 1.1, Avg: 1.4, Max: 2.0, Diff: 0.9, Sum: 5.5]
[Update RS (ms): Min: 0.0, Avg: 0.4, Max: 1.7, Diff: 1.7, Sum: 1.7]
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Object Copy (ms): Min: 0.0, Avg: 0.9, Max: 1.6, Diff: 1.6, Sum: 3.7]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.2]
[Termination Attempts: Min: 1, Avg: 1.0, Max: 1, Diff: 0, Sum: 4]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
[GC Worker Total (ms): Min: 2.6, Avg: 2.8, Max: 2.9, Diff: 0.3, Sum: 11.2]
[GC Worker End (ms): Min: 907.9, Avg: 907.9, Max: 907.9, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
[Other: 1.6 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.0 ms]
[Humongous Register: 0.1 ms]
[Humongous Reclaim: 0.8 ms]
[Free CSet: 0.0 ms]
[Eden: 3072.0K(51.0M)->0.0B(50.0M) Survivors: 0.0B->1024.0K Heap: 232.5M(1024.0M)->1800.1K(1024.0M)]
[Times: user=0.00 sys=0.00, real=0.01 secs]
分析
0.904: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0046285 secs]
0.904秒开始进行一次youngGC CPU耗时0.0046285秒
ParNew+CMS GC日志怎么看
Java HotSpot(TM) 64-Bit Server VM (25.161-b12) for windows-amd64 JRE (1.8.0_161-b12), built on Dec 19 2017 17:52:25 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 8281908k(2603052k free), swap 11799044k(3966832k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=4194304 -XX:NewSize=4194304 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
这个是JVM启动的参数配置 初始化大小 新生代的大小 大对象的设置 是否大于GC时间 是否记录log日志
0.306: [GC (Allocation Failure) 0.306: [ParNew: 3115K->384K(3712K), 0.0037058 secs] 3115K->1767K(9856K), 0.0039580 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.306开始进行GC回收,原因是无法再新生代进行创建对象
[ParNew: 3115K->384K(3712K), 0.0037058 secs] 新生代可用大小 3.5M左右 存活的对象还有384k 回收时间大概3毫秒
3115K->1767K(9856K), 0.0039580 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 整个堆还有9M多,存活的一共1767k数据
0.310: [GC (Allocation Failure) 0.310: [ParNew: 2494K->0K(3712K), 0.0017355 secs] 3878K->1763K(9856K), 0.0018016 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 3712K, used 2114K [0x00000000ff600000, 0x00000000ffa00000, 0x00000000ffa00000)
eden space 3328K, 63% used [0x00000000ff600000, 0x00000000ff810bc0, 0x00000000ff940000)
from space 384K, 0% used [0x00000000ff940000, 0x00000000ff940000, 0x00000000ff9a0000)
to space 384K, 0% used [0x00000000ff9a0000, 0x00000000ff9a0000, 0x00000000ffa00000)
concurrent mark-sweep generation total 6144K, used 1763K [0x00000000ffa00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3447K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
Memory: 4k page, physical 8281908k(2603052k free), swap 11799044k(3966832k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=4194304 -XX:NewSize=4194304 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
这个是JVM启动的参数配置 初始化大小 新生代的大小 大对象的设置 是否大于GC时间 是否记录log日志
0.306: [GC (Allocation Failure) 0.306: [ParNew: 3115K->384K(3712K), 0.0037058 secs] 3115K->1767K(9856K), 0.0039580 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.306开始进行GC回收,原因是无法再新生代进行创建对象
[ParNew: 3115K->384K(3712K), 0.0037058 secs] 新生代可用大小 3.5M左右 存活的对象还有384k 回收时间大概3毫秒
3115K->1767K(9856K), 0.0039580 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 整个堆还有9M多,存活的一共1767k数据
0.310: [GC (Allocation Failure) 0.310: [ParNew: 2494K->0K(3712K), 0.0017355 secs] 3878K->1763K(9856K), 0.0018016 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 3712K, used 2114K [0x00000000ff600000, 0x00000000ffa00000, 0x00000000ffa00000)
eden space 3328K, 63% used [0x00000000ff600000, 0x00000000ff810bc0, 0x00000000ff940000)
from space 384K, 0% used [0x00000000ff940000, 0x00000000ff940000, 0x00000000ff9a0000)
to space 384K, 0% used [0x00000000ff9a0000, 0x00000000ff9a0000, 0x00000000ffa00000)
concurrent mark-sweep generation total 6144K, used 1763K [0x00000000ffa00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3447K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
JVM总结
指令计数器和虚拟机栈分别存放指令和数据的设计
方法执行完后, 栈帧立马被出栈, 那该栈帧中的变量等数据是立马就被回收
双亲委派模型设计的出发点很重要,对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。
也就是说,判断2个类是否“相等”,只有在这2个类是由同一个类加载器加载的前提下才有意义,否则即使这2个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,这2个类必定不相等。
基于双亲委派模型设计,那么Java中基础的类,Object类似Object类重复多次的问题就不会存在了,因为经过层层传递,加载请求最终都会被Bootstrap ClassLoader所响应。加载的Object类也会只有一个,否则如果用户自己编写了一个java.lang.Object类,并把它放到了ClassPath中,会出现很多个Object类,这样Java类型体系中最最基础的行为都无法保证,应用程序也将一片混乱。
也就是说,判断2个类是否“相等”,只有在这2个类是由同一个类加载器加载的前提下才有意义,否则即使这2个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,这2个类必定不相等。
基于双亲委派模型设计,那么Java中基础的类,Object类似Object类重复多次的问题就不会存在了,因为经过层层传递,加载请求最终都会被Bootstrap ClassLoader所响应。加载的Object类也会只有一个,否则如果用户自己编写了一个java.lang.Object类,并把它放到了ClassPath中,会出现很多个Object类,这样Java类型体系中最最基础的行为都无法保证,应用程序也将一片混乱。
回收算法
新生代的回收算法
标记算法
需要标记每个对象的存活
对象之间存在碎片
空间利用率不高
稍微不足的复制算法
新生代分成2个区域
交替复制整理对象
减少对象的内存碎片
空间利用率不大
改进版复制算法
Eden区域+2个survive
新生的对象存放在Eden区域,回收的存活的对象放入到其中一个survive中
这样可用的内存就是Eden+一个survive
JVM调优面临的问题
市面上的书籍过于理论
而且大部分的参数都是大概,根本没有参考价值
面试不会问一点简单的原理,需要有实际的调优经验
高级工程师必须要迈过的坎
一个程序员必须要的核心技能
java代码运行原理
由java文件编译成class
执行入口main,类加载器加载class到JVM
字节码执行器执行一行一行的指令
机器只认识0和1
类加载
JVM加载器有哪些
根加载器(BootStrap ClassLoader)
扩展加载器(Extend ClassLoader)
类加载器(ClassPath ClassLoader)
自定义加载器
双亲委派
什么是双亲委派
委托自己的父类去加载,如果加载不了,自己加载,如果还是不能加载,子类加载
双亲委派的目的是什么
为了安全考虑,如果我们自己写了一个基础类覆盖了,这样就会有比较大的风险
避免重复加载 + 避免核心类篡改
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java
API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java
API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
加载一个类会做什么
验证阶段
类是否被篡改
验证类是否符合规范
准备阶段
根据类的进行合理的分配一下内存
初始阶段
对静态变量赋值
成员属性赋值
静态初始化
构造函数
tomcat的类加载器
打破双亲委派
tomcat会部署很多不同的应用程序,而且相互之间会用不同版本的jar,相互之间要隔离
部署在同一个web容器中相同的类库相同的版本可以共享。
web容器也有自己依赖的类库,不能于应用程序的类库混淆
web容器需要支持 jsp 修改后不用重启
加载器有哪些
common加载器
tomcat最基本的类加载器,加载路径中的class可以被tomcat和各个webapp访问
catalina加载器
tomcat私有的类加载器,webapp不能访问其加载路径下的class,即对webapp不可见
shared加载器
各个webapp共享的类加载器,对tomcat不可见
webapp加载器
webapp私有的类加载器,只对当前webapp可见
jsp加载器
每个jasper类加载器加载一个jsp文件
JVM内存区域
方法区
存放class的区域,或者叫做metaspace,元数据空间
程序计数器
每个线程执行的命令都会被记录
java虚拟机栈
当线程执行到某个方法的时候,如果这个方法有局部变量,
那么就需要一块区域来存放局部变量的数据信息。这个区域就叫做java虚拟机栈。
那么就需要一块区域来存放局部变量的数据信息。这个区域就叫做java虚拟机栈。
java堆
实例化的所有对象都是存放在这个内存中
JVM垃圾回收
垃圾回收器是一个后台线程
当我们创建的对象再堆中无法存储的时候就会自动回收没有GC ROOTS引用的对象
我们创建的对象优先分配在新生代对象中
当新生代的空间快满的时候就触发垃圾回收机制
回收时通过可达性算法追踪是否有引用,如果没有就进行回收
JVM规范中认为方法的局部变量,静态变量引用的对象都是不可回收的这些都可以称为GC ROOTS
引用类型
强引用
就是普通的声明局部变量,静态变量
软引用
利用SoftReference<T>包装的对象都是软引用
一般新生代充足的情况是不会回收的,
但是如果空间不足的时候就会强制回收软引用对象
不管是否有引用
但是如果空间不足的时候就会强制回收软引用对象
不管是否有引用
弱引用
利用WeakReference<T>引用
只要系统发生垃圾回收就会销毁对象
虚引用
这个基本上不会用到不用了解
finalize()
重写Object的finalize()方法 利用静态变量引用自己也可以强制不用回收
这种的使用基本上很少,大概知道可以让没有GC ROOTS的引用也可以不用回收
根据JVM规范,方法局部变量和静态变量被称为GC ROOTS,如果有GC ROOTS是不可以回收的
JVM分代模型
年轻代
存放新生对象的
复制算法
有2个S区域 默认比例是8:1:1
什么情况下回触发垃圾回收
对象无法创建的时候
触发minor GC会做什么
整个系统停止工作
检查老年代的可用内存大于Eden区域的对象大小
大于就触发minor GC
小于就看有没有设置担保机制 默认应该是打开的
如果回收后的对象大小大于S区的大小 小于老年代的可用大小直接转入到老年代
如果回收后的对象大小小于S区就直接放入
如果回收后的对象大小大于S区,大于老年代可用大小触发FULL GC完了还要触发minorGC
如果老年代可以存放就放入,不能放入就发生OOM
如果老年代可以存放就放入,不能放入就发生OOM
动态年龄判断,如果新生代回收的对象,大于S区域年龄总和的50%就会把,最大年龄的对象放入到老年代
如果新生代对象回收超过15次还没有回收掉就会进入老年代
如果设置了大对象就会按照我们设置的大小直接进入老年代
老年代
存放无法回收的对象
默认回收是达到内存可用的92%大小
CMS算法
初始标记
无法创建对象,系统停止工作,标记哪些对象是存活的
并发标记
多核并发标记,允许系统创建对象,速度不是很快,要吃系统的CPU,如果对于CPU类型的应用会有影响
默认的CPU配置 (4+3)/4核进行并行处理
重新标记
无法创建对象,系统停止工作
这个时候需要把并发阶段的对象标记出来
默认的CPU配置 (4+3)/4核进行并行处理
并发清理
系统可以创建对象,和系统一起处理垃圾对象,这个时候也是多核的方式进行回收
concurrence mode failure
在并发标记和并发整理的时候是运行系统运行和创建对象的,但是如果此时系统突出进入到的对象超过了目前的可用空间的时候
就会导致并发回收失败,变成单线程回收算法,这个很慢的,有可能会超过非常大的时间,系统完全停止几十秒都有可能
就会导致并发回收失败,变成单线程回收算法,这个很慢的,有可能会超过非常大的时间,系统完全停止几十秒都有可能
会产生浮动垃圾
因为在比方清理的时候系统是可以进行创建新的对象,所以会有一部分的对象在进行并发回收的时候还有一些浮动的对象在老年代无法进行回收
FULL GC
系统停止工作
初始化标记系统停止工作
重新标记系统停止工作
永久代
存放class类信息的地方
OOM
栈内存溢出
递归调用没有停止,很容易导致
代码处在逻辑问题,导致死循环
堆内存溢出
常见于并发比较高,老年代无法回收有可能会导致OOM
代码存在内存溢出
metaspace溢出
产生大量的class,有可能工程很大,但是没有配置好元数据的空间会导致
用动态代理写出很多的代理class导致
JVM核心参数
0 条评论
下一页