JVM知识汇总hz
2020-08-24 08:49:31 46 举报
AI智能生成
JVM知识体系,垃圾回收机制,类加载机制,JVM调优
作者其他创作
大纲/内容
JVM(1.7)
JVM内存区域
线程共有
线程私有
方法区
用于存放被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等
堆
收集器使用分代算法
新生代YoungGeneration
Eden
Survivor(From)
Survivor(To)
老年代OldGeneration
存放的对象实例和数组,线程共享,多线程访问需要同步机制
程序计数器(Program Counter Register)
当前线程执行字节码的行号指示器
是一块较小的内存空间,线程私有
记录虚拟机字节码指令的地址
如果为native(底层方法)计数器为空
虚拟机中唯一没有OutOfMemoryErro的区域
虚拟机栈(JVM执行Java方法)
java方法执行的内存模型,每个方法执行都会创建一个栈帧(局部变量表、操作数栈、动态链接、方法出口),每个方法从开始调用到结束都对应一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表所需要的内存空间在编译器完成分配,当进入一个方法时,这个方法再栈中需要分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表大小
可能出现两种类型的异常
线程请求的栈深度大于虚拟机允许的栈深度,抛出StackOverflowError
虚拟机空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常
本地方法栈(JVM执行本地方法)
HotSpot把本地方法栈和虚拟机栈合二为一
本地方法栈为虚拟机使用到native方法服务,可能底层调用的c或者c++
直接内存(Direct Memory)
jdk1.4的NIO,可以使用native函数直接分配堆外内存,受本机总内存和处理器寻址空间的限制
JVM内存溢出
JVM参数详解
-Xms
初始堆大小
默认值:物理内存的1/64(<1GB)
示例:-Xms1g
-Xmx
最大堆大小
默认值:物理内存的1/4(<1GB)
-Xmn
年轻代大小
-XX:New Ratio
年轻代与老年代的比值
-Xss
每个线程的堆栈大小
-XX:MetaspaceSize
初始元数据空间大小
-XX:PermSize=size
永生代最小容量
永生代最大容量
-XX:MaxMetaspaceSize=128m
最大元数据空间大小
OutOfMemoryError:Java heap space
最大堆、最小堆、新生代:-Xms512M -Xmx512M -Xmn128M
虚拟机栈/本地方法栈
StackOverflowError
每个线程可使用内存:-Xss256K
OutOfMemoryError:unable to create new native thread
OutOfMemoryError:PermGen space
JVM设置最小最大:-XX:PermSize=64M -XX:MaxPermSize=128M
直接内存
at sun.misc.Unsafe.allocateMemory(Native Method)
元空间(java1.8)
java.lang.OutOfMemoryError: Metadata space
-XX:MetaspaceSize:128M -XX:MaxMetaspaceSize=128M
子主题 6
GC overhead limit exceeded
垃圾回收
判断对象已死
引用计数算法(Reference Counting)
给对象添加一个引用计数器,每当一个地方引用它时计数器加1;引用失效计数器减1,计数器为0说明不再引用
优点:实现简单,判定效率高
缺点:无法解决对象相互循环引用的问题;增加存储空间的开销
可达性分析算法
当一个对象到GC Roots没有引用链相连(GC Roots到这个对象不可达)时,证明此对象不可用
GC Roots种类
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中的静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中JNI(Native方法)引用的对象
分析
解决了引用计数法无法解决的循环依赖问题
引用
强引用
Object object=new Object();
JVM 抛出OOM也不会释放对象
中断强引用类型:将null的值给对象即可
源码呈现:Vector的clear方法就是将引用赋值为:null
软引用
SoftReference<Object> object=new SoftReference<Object>(new Object());
可用来实现内存敏感信息的高速缓存
使用软引用可用来防止内存泄露,增强程序的健壮性
JVM在抛出OOM之前会回收软引用及对象,优先回收长时间闲置对象
弱引用
WeakReference<Object> object=new WeakReference<Object>(new Object());
span style=\
虚引用
finalize()
对象是否覆盖finalize方法
是:jvm执行finalize()方法
否:JVM回收对象
JVM是否执行过该对象的finalize方法
是:JVM回收对象(每个对象finalize方法只执行一次)
否:JVM执行finalize()方法
垃圾收集算法
回收方式
单线程
并行
并发
标记清除算法
缺点:1、标记和清除效率都不高;2、标记、清除之后会产生大量不连续的内存碎片
复制算法(新生代)
Eden:Survivor:Survivor=8:1:1
分配担保:如果Survivor没有足够空间来存放上次新生代GC存活下来的对象,它们将通过分配担保机制进入老年代
缺点:对象存活率高时,复制效率较低
标记整理算法(老年代)
先执行标记-清除,让所有的存活对象都向一端移动,最后清理掉边界以外的内存
分代搜集算法
垃圾收集器
新生代(Young Generation)
Serial
特点:单线程收集;标记-复制算法
场景:Client模式下默认新生代收集器;单核机器
ParNew
特点:多线程并行收集;标记-复制算法;其他特点与Serial相似
缺点:在单CPU场景效果不突出
场景:用户交互;配合CMS垃圾收集器
Parallel Scavenge
特点:目标在于达到可控吞吐量(吞吐量=用户代码运行时间/(用户代码运行时间+垃圾收集时间));标记-整理
场景:高效利用CPU,后台运算且不需要太多交互
老年代Tenured Generation)
CMS
特点:最短回收停顿时间;标记-清除
步骤
初始标记:标记GC Roots直接关联的对象,速度快
并发标记:GC Roots Tracing过程,耗时长,与用户进程并发工作
重新标记:修正并发标记期间用户进程继续运行而产生变化的标记,耗时比初始标记长,但远小于并发标记
并发清除:清除标记的对象
缺点
对CPU资源敏感
无法回收浮动垃圾
标记-清除算法,会产生内存碎片,可以通过参数开启碎片的合并整理
Serial Old
特点:Serial的老年代版本,单线程;标记-整理
场景:1.5之前与Parallel Scavenge配合使用;作为CMS的后备预案
Parallel Old
特点:标记-整理;多线程
场景:为了替代Serial Old与Parallel Scavenge配合使用
G1
特点:将整个Java堆划分为多个大小相等的独立区域Region,跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
初始标记:标记GC Roots直接关联的对象
并发标记:对堆中对象进行可达性分析,找出存活对象,耗时长,与用户进程并发工作
最终标记:修正并发标记期间用户进程继续运行而产生变化的标记
筛选回收:对各个Region的回收价值排序,然后根据期望的GC停顿时间制定回收计划
垃圾收集器搭配关系
分支主题
垃圾回收过程
大多情况下对象在Eden分配,当Eden没有足够空间时将发起一次Minor GC
当Eden执行Minor GC后还不足以为对象分配空间,大对象直接进入老年代,可以用参数设置大对象直接进入老年代,避免频繁Minor GC
如果对象在Eden出生,发生MinorGC后仍然存活,且能被Survivor容纳,年龄加1,达到一定年龄进入老年代,默认15
占Survivor空间一半以上且年龄相等的对象,大于等于该年龄以上的对象直接进入老年代
发生Minor GC之前会先检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果大于说明MinorGC安全;否则会判断是否允许担保失败,如果允许担保失败,判断老年代最大连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则尝试MinorGC,否则执行FullGC
类加载
类的生命周期(类加载过程)
加载(Loading)
1.通过一个类的全限定名来获取定义此类的二进制字节流
2.将二进制字节流代表的静态存储结构转化为方法区的运行时数据结构
3.在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类各种数据的访问入口
连接(Linking)
验证(Verification)
文件格式验证
元数据验证
字节码验证
符号引用验证
准备(Preparation)
类变量(static)会分配内存并赋初始值,但是实例变量不会,实例变量主要随着对象的实例化一块分配到java堆中,
解析(Resolution)
解析阶段主要是虚拟机将常量池中的符号引用转化为直接引用的过程
符号引用
直接引用
初始化(Initialization)
执行<clinit>(),初始化类变量、静态代码块为类变量指定初始值
JVM初始化步骤
1.假如这个类还没有被加载和连接,则程序先加载并连接该类
2.假如该类的直接父类还没有被初始化,则先初始化其直接父类
3.假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机
只有当对类的主动使用的时候才会导致类的初始化
类的主动使用(加载)
创建类的实例,也就是new的方式
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射(如 Class.forName(“com.xxx.xxx”))
初始化某个类的子类,则其父类也会被初始化
Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类
JDK 7开始提供的动态语言支持
Java类的被动使用
除了主动使用,其他使用Java类的方法都被看做被动使用
使用(Using)
卸载(Unloading)
Java类加载器
启动类加载器(Bootstrap ClassLoader)
最顶层的加载类
主要加载核心类库,也就是我们环境变量下面%JRE_HOME%\\lib下的rt.jar、resources.jar、charsets.jar和class等
可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中
扩展类加载器(Extension ClassLoader)
加载目录%JRE_HOME%\\lib\\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录
应用程序类加载器(Application ClassLoader)
又称SystemAppClass
加载当前应用的classpath的所有类。
自定义类加载器(User ClassLoader)
两种创建方式
遵守双亲委派模型:继承ClassLoader,重写findClass()方法。
实现步骤
1.创建一个类继承ClassLoader抽象类
2.重写findClass()方法
3.在findClass()方法中调用defineClass()
加载顺序
Bootstrap ClassLoader > Extention ClassLoader > Appclass Loader
类加载的三种方式
通过命令行启动应用时由JVM初始化加载含有main()方法的主类。
通过ClassLoader.loadClass()方法动态加载,不会执行初始化块
双亲委派原则
可以避免重复加载,父类已经加载了,子类就不需要再次加载
更加安全,很好的解决了各个类加载器的基础类的统一问题,如果不使用该种方式,那么用户可以随意定义类加载器来加载核心api,会带来相关隐患。
沙箱安全机制(sandbox)
自定义string类,但是在加载自定义string类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar包中java\\lang\\string.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的string类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。
性能优化
目标:使用较小的内存占用来获得较高的吞吐量或者较低的延迟。
常见问题
CPU Load过高导致系统不可用或tps急剧降低
YoungGC次数频繁
FullGC次数频繁
FullGC时间长
PermSpace GC次数频繁
内存泄漏、内存溢出
调优参考数据
系统运行日志
异常堆栈
GC日志
线程快照(threaddump/javacore文件)
堆转储快照(heapdump/hprof文件)
调优工具
jdk命令行工具
jps查看系统内所有HotSpot进程
jinfo显示虚拟机配置信息
jstat收集虚拟机运行数据
jmap生成指定进程堆转储快照(heapdump文件)
jhat分析heapdump文件
jstack显示虚拟机线程快照
可视化工具
jconsole和jvisualvm查看内存回收情况
BTrace跟踪调试方法
jprofiler监控每个类的内存占用
MAT工具分析内存占用
优化方案
JVM配置
-Xms和-Xmx的值设置成相等
新生代尽量设置大一些
实现层面
避免创建过大的对象及数组
避免同时加载大量数据
集合中的对象用完后及时清空
在合适场景使用软引用、弱引用
尽量避免长时间等待外部资源(数据库、网络、设备资源等)
设置合理的线程数
JVM内存模型
volatile
保证不同线程对共享变量操作时的可见性
禁止指令重排序
保证了内存的可见性,每次获取到的值都是最新值
点个赞吧❤
0 条评论
回复 删除
下一页