JVM
2021-05-27 00:59:53 0 举报
AI智能生成
吐血整理jvm相关知识
作者其他创作
大纲/内容
jvm整体结构详解
jvm内存分配机制
堆
主要存储对象,垃圾回收主要需要回收的区域
栈
方法栈,一种先进后出的数据结构,每调用一个方法就会创建一个线程并在方法栈开辟一块栈帧
每一块栈帧中,有四块,局部变量表,操作数栈,动态链接,方法出口
本地方法栈
与栈内存结构相似,只不过这里存储的是本地方法
方法区
1.7之前,完整的方法区包括常量池,字面量(直接赋值的String),静态变量,动态链接,类元信息
1.7开始,将除类元信息以外的东西移到其他地方,譬如,字面量常量池静态变量移到堆区,动态链接移到本地方法栈
元空间
使用jvm内存,分配多大就吃多大内存,并且有上限,有oom风险,现在直接使用的是机器内存,理论上可以无上限,减少oom风险
一次元空间溢出问题
一次测试服元空间溢出,重启服务就好了,当时认为是common包太大,所有类信息都加载到元空间中,导致元空间溢出。事后回想,元空间依赖于机器内存,启动时给初始值(默认大约20m),用多大就有多大,动态扩容。当时机器本身内存就不多,可能是动态扩容,机器已经没有内存可用,然后类还要硬加载进来,导致溢出。重启机器,内存重组,短时间内不会出现问题
程序计数器
记录线程的执行到哪了,当线程被挂起然后重新唤醒之后继续执行的位置
jvm主要分为4块,类加载子系统,字节码执行引擎,本地方法库,运行时数据区
类加载子系统
负责动态加载类
字节码执行引擎
将class文件翻译成二进制文件给操作系统执行
本地方法库
存储了操作系统的本地方法,java代码中可以调用这些本地方法完成一些操作
运行时数据区
jvm运行时数据存储区域,堆,栈,本地方法栈,元空间,程序计数器位于此
垃圾收集
常用的垃圾收集器
serial串行垃圾收集器(年轻代)
子主题
serialOld串行垃圾收集器(老年代)
parallel并行垃圾收集器(年轻代)
parallelOld并行垃圾收集器(老年代)
parNew新并行垃圾收集器(年轻代)
CMS(concurrent mark sweep老年代)
G1(物理上不分代,逻辑上还是分代)
ZGC(源于商用的C4,暂时不分代)
垃圾收集算法
生产环境调优实战
JVM
jvm类加载机制
类加载器classLoader
BootStrapLoader/引导类加载器
主要负责加载jdk核心类库(/jre/lib目录下的jar文件)
ExtClassLoader/扩展类加载器
主要负责加载/jre/lib/ext目录下的jar文件
AppClassLoader/应用程序类加载器
负责加载classPath下的文件
? extends ClassLoader/自定义类加载器
可指定加载路径,实现用自己的类加载器加载某个class或目录
手写自己的类加载器实现加载
类加载时机
类加载过程
加载
验证
验证是否是一个java类,class文件的合法性,字节码cafebaby开头
准备
为静态变量赋初始值(0值)
解析
将符号引用替换为直接引用,此时类信息都已经加载进来,方法也有相应的句柄和地址,替换为真正的地址
初始化
执行静态代码块,静态方法,为静态变量赋值(此时才是真正赋值)
类加载机制
双亲委派机制
源ClassLoader先判断该Class是否已加载,如果已加载,则返回Class对象;如果没有则委托给父类加载器。父类加载器判断是否加载过该Class,如果已加载,则返回Class对象;如果没有则委托给祖父类加载器。依此类推,直到始祖类加载器(引用类加载器)。始祖类加载器判断是否加载过该Class,如果已加载,则返回Class对象;如果没有则尝试从其对应的类路径下寻找class字节码文件并载入。如果载入成功,则返回Class对象;如果载入失败,则委托给始祖类加载器的子类加载器。始祖类加载器的子类加载器尝试从其对应的类路径下寻找class字节码文件并载入。如果载入成功,则返回Class对象;如果载入失败,则委托给始祖类加载器的孙类加载器。依此类推,直到源ClassLoader。源ClassLoader尝试从其对应的类路径下寻找class字节码文件并载入。如果载入成功,则返回Class对象;如果载入失败,源ClassLoader不会再委托其子类加载器,而是抛出异常。————————————————版权声明:本文为CSDN博主「zhangzeyuaaa」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/zhangzeyuaaa/article/details/42499839
全盘负责委托机制
当一个类加载器加载了一个类,那么这个类所依赖或者引用的其他类也有这个加载器加载
沙箱安全机制
双亲委派模型保证了一个类只能被加载一次,并且不会出现同名的类被重复加载,如果我们自己写一个java.lang.String类,那么这个类是不能被加载的,保证了jvm环境不被破坏
jvm内存分配理论基础
对象的创建过程
1,类加载检查,在虚拟机常量池中查找是否有该类的符号引用,并检查该类是否已经被加载过,如果没有首先加载该类
2,分配内存
分配内存的方法
指针碰撞:适用于堆内存绝对规整的情况下,jvm底层维护一个指针,左侧是已经分配内存的空间,右侧是未分配的空间,当需要分配内存时,指针向后移动,偏移量就是分配内存大小,根据需要多大分配多大
空闲列表:适用于内存不规整的情况下,jvm底层维护一个空闲列表(Free List),该列表中记录了未被分配的空间,当需要分配是,取出空闲列表中的空间用于分配内存
JVM默认使用的是指针碰撞进行内存分配
并发时内存分配问题解决
CAS(解决指针碰撞):虚拟机采用失败重试的方法解决并发问题
TLAB(解决空闲列表):虚拟机采用本地线程缓冲解决并发问题,每一个线程会有自己的一块独立的空闲用于该线程所需要的内存 -XX:+UseTLAB开启本地线程缓冲分配,XX:TLABSize设置每个线程用于分配内存大小
分配内存的位置
栈上分配
依赖于线程的逃逸分析和标量替换
逃逸分析:当一个对象只存在于栈内,没有逃出栈,对象会在当前线程栈中分配空间
标量替换:类似int,boolean这种不可再被分割的成员变量,称为标量,反之则是聚合量,聚合量是由标量组成,当栈内空间没有足够连续空间以分配对象,会把聚合量分割为若干个标量,为每一个标量分配空间(标量所占空间远小于聚合量)
堆中分配
年轻代与老年代,年轻代:老年代=1:2,年轻代中eden:Survivor1:Survivor2=8:1:1
新建对象默认会被分配到年轻代的eden区,每做一次YoungGC/minorGC会将eden和Survivor2存活对象移到Survivor1中或者Survivor1移到Survivor2
大对象直接进入老年代:如果一个对象太大,放在eden区占用空间比较大,会频繁触发YoungGC,那么会考虑直接将他放到老年代,-XX:PretenureSizeThreshold=1000000 (单位是字节)用于设置直接进入老年代对象阈值,只有serial和parNew有效
长期存活的对象直接进入老年代,长期存活的对象考虑到一定分代年龄之后直接进入老年代,而不是一直在年轻代占用空间触发GC,默认分代年龄超过15会进入老年代,-XX:MaxTenuringThreshold:10用于设置进入老年代年龄阈值
对象年龄动态判断,当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的 50%(-XX:TargetSurvivorRatio可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了, 例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会 把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年 龄判断机制一般是在minor gc之后触发的。
老年代内存担保机制,年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间 如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象) 就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了 如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。 如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾, 如果回收完还是没有足够空间存放新的对象就会发生\"OOM\" 当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”
3,初始化,执行静态代码块,静态方法,执行init方法
4,设置对象头,每个实例对象都是由对象头,实例数据,对象填充组成,并且占用字节数是8的整数倍(不足的话会触发对象填充)
MarkWord标记字段(32位虚拟机中4字节32位):1-30()
KlassPointer类型指针,开启指针压缩占4字节,关闭指针压缩占8字节,指向元空间中的类的元数据信息
如果是数组对象还会有数组长度占4字节,数组对象特有的,其他对象没有
关于指针压缩
什么是指针压缩?
在jdk1.6开始,64位虚拟机会默认开启指针压缩,就是压缩KlassPointer所占内存大小,64位指针默认占8字节,而32位指针占4字节
为什么要开启指针压缩?
对象头的大小一定程度上影响了对象的大小,如果对象太大,对象的传输会带来网传传输压力,占用较大带宽,一定程度上节约了64位的内存压力和网络传输压力。
如何开启指针压缩?
当堆内存小于4G时,默认就是开启的。当堆内存>32G时,指针压缩强制关闭。堆内存4-32G可选择开启或者关闭指针压缩,默认开启,禁止指针压缩:XX:UseCompressedOops
5,执行init方法,为成员变量赋初始值,执行构造方法
内存回收理论
引用计数法
可达性分析算法
从gcRoots往下找,寻找被gcRoots引用的对象,每个被引用的对象都是非垃圾对象,jvm会维护一个gcRootsSet,一般都是线程本地变量,静态变量,本地方法栈变量
对象的finalize()方法
当对象要被回收时,会判断该对象是否重写了finalize()方法,如果没有,直接标记垃圾对象,如果有,会执行finalize()方法,只需要在该方法中将该对象被其他非垃圾对象引用即可,没啥用
0 条评论
回复 删除
下一页