JVM内存模型及垃圾收集机制

JVM结构体系

JVM结构
由图可知JVM的大致结构可以分为三块,类装载子系统、字节码执行引擎和运行时数据区
字节码执行引擎中又包含了解释器、JIT编译器和垃圾回收器
运行时数据区右包含了堆、方法区、java方法栈、本地方法栈和程序计数器

运行时数据区:
堆主要存放对象和他们相应的实例变量以及数组等,是所有线程共享的所以不是线程安全的。
方法区主要存放类信息、静态变量、静态方法、常量和成员方法以及经过JIT编译器编译后的代码等数据,也是所有线程共享的,也不是线程安全的。
虚拟机栈主要存放java中的方法信息以及方法调用时的局部变量、对象的引用等,是线程私有的,是线程安全的。
本地方法栈主要存放JVM里用C/C++写的一些方法。
程序计数器主要用来存储指向下一条指令的地址

类装载子系统:

在这里插入图片描述
作用就是将字节码文件加载到JVM的内存中,

类加载过程主要分为加载、链接、初始化三个阶段,其中链接又分验证、准备、解析三步
加载
是装载类的第一个阶段,取得类的二进制字节流,并转换为方法区的数据结构,在堆中生成相应的java对象
验证阶段
的目的在于确保Class文件中的字节流中包含的信息是否符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全,主要包含四种验证方式:文件格式验证、元数据验证、字节码验证和符号引用验证。
准备阶段
是为静态变量分配内存并且设置默认值
解析阶段
是将常量池的符号引用转换为直接引用
最后初始化阶段为静态变量赋值并且执行静态代码块。

类加载顺序
如果这个类没有父类:
1、类的静态属性
2、类的静态代码块
3、类的非静态属性
4、类的非静态代码块
5、构造方法
如果这个类有父类:
1、父类的静态属性
2、父类的静态代码块
3、子类的静态属性
4、子类的静态代码块
5、父类的非静态属性
6、父类的非静态代码块
7、父类的构造方法
8、子类的非静态属性
9、子类的非静态代码块
10、子类的构造方法

双亲委派模型:
当类加载的时候会先由应用类加载器开始,向上委托给扩展类加载器,最后在委托给启动类加载器,如果父类加载器可以加载这个类那么就会由父类进行加载,如果父类不能加载在向下委派给子类加载器加载,这样做的好处有两点一是可以避免类的重复加载,二是可以防止核心API被篡改。

GC:

在这里插入图片描述
是JVM的一种垃圾回收机制,因为程序在运行过程中会申请大量的内存空间,对于一些无用的内存空间如果不及时清理的话就会导致内存溢出。
堆区划分为年轻代和老年代,年轻代又划分为伊甸园区和幸存者区,幸存者区又分为了s0和s1两块区域,当第一次进行垃圾回收的时候会将伊甸园区存活的对象移动到幸存者区的s0,然后清除伊甸园区的不可用对象,第二次垃圾回收的时候会将伊甸园区与s0区存活的对象移动到s1区,第三次垃圾回收的时候再将伊甸园区与s1区存活的对象再移动到s0区,总之s0和s1总有一块是空闲的。当一个对象存活了15代之后会将这个对象移动到老年代。
Young GC / Minor GC:主要是在年轻代空间内进行垃圾回收
Old GC / Major GC:主要针对在年轻代空间存活的对象,并且存在于老年代空间中的对象进行回收
Full GC:会扫描整个java堆内存去查找不再需要的对象并释放其内存,当没有足够的空间分配新对象或JVM检测到堆内存已经满时,就会调用Full GC。

如何判断一个对象是不是垃圾
1、引用计数法:
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加一,引用失效时计数器就减一,只要计数器为0就说明该对象已经不再使用了可以随时被回收。
优点是实现简单,效率高
缺点是需要额外的空间和时间来维护计数器并且无法处理循环引用问题
2、可达性分析法:
在这里插入图片描述
利用GC Roots对象作为起始节点集,从这些节点开始,通过引用关系向下搜寻,如果某个对象到GC Roots 没有任何引用链相连,就说明该对象不可达,可以被回收
垃圾回收算法:
1、标记-清除算法
就是遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活对象并清除所有未标记的对象
缺点是会产生内存碎片
2、标记-复制算法
将内存空间分为多块,每次只使用其中的一块,在进行垃圾回收时,会将可达对象复制到另一块未被使用的内存中,然后清除当前内存块中的所有对象
优点是可以解决内存碎片化问题
缺点是会压缩内存空间,在存活对象较多的情况下(老年代),需要频繁复制效率很差。而且在复制的时候不仅要移动对象还要维护对象的引用地址。
3、标记-整理算法
是对标记-清除算法的改进,在标记了所有的可达对象之后不会直接清除掉不可达对象,而是会让所有存活的对象向一端移动,然后直接清理掉末端边界以外的内存
优点是不会产生内存碎片
缺点是需要修改栈帧中的引用地址,耗费时间多。

JVM中的垃圾回收器
在这里插入图片描述
在分代回收算法的基础上有六种垃圾回收器
Serial(单线程串行回收)和Serial Old,Parallel Scavenge(标记-复制)和Parallel Old(java1.8默认多线程并发回收,标记-整理),ParNew(标记-复制)和CMS(多线程并发回收不会有STW,标记-清除),
另外还有G1(java1.9),ZGC,Shenandoah和Epsilon这四种不分代但会分区
CMS通常与ParNew组合使用,作用于老年代使用标记-清除算法进行多线程并行回收,回收过程中可以减少用户线程的停顿(STW),但会产生浮动垃圾
CMS工作流程
在CMS和G1中使用三色标记法+SATB解决并发回收时对象状态不停变化和漏标的问题。再最后的remark阶段会有STW

JVM性能调优

内存泄漏:对象已经没有实际的使用意义,但仍然被错误的引用,从而无法被垃圾回收器回收,导致内存泄漏
内存溢出:就是内存不够用了。

jvm参数:
-xss:设置线程栈的最大内存空间
-xms:设置堆的起始内存
-xmx:设置堆的最大内存
-xmn:设置新生代大小
-XX:NewRatio: 配置新生代和老年代占比
-XX:ServivorRatio:新生代中eden和servivor区占比
-XX:MaxTenuringThreshold:进入老年代的年龄阈值
-XX:PretenureSizeThreshold:大对象阈值

年轻代如何晋升到老年代:
1、对象大小超过阈值
2、年龄超过阈值
3、空间担保机制
4、动态年龄判断:如果幸存者区相同年龄的所有对象大小的总和大于幸存者区空间的一半,那么年龄大于或等于该年龄的对象可以直接进入老年代

调优经验:
为了减少GC时间,降低Full GC的次数
1、如果访问压力过大,MInorGC频繁,可以适当增加伊甸园区的空间
2、如果每次MinorGC之后存活的对象过多,要保证所有存活对象的大小 < 幸存者区的大小,否则这批对象会直接进入老年代
3、为了应对动态年龄判断可以适当将幸存者区调大
4、提高大对象的阈值,或者从代码层面进行优化,比如将大对象设置为单例模式或者拆分使用,如果必须要放入老年代,可以通过定时脚 本,在业务系统不繁忙的情况下主动触发Full GC。
5、Minor GC和Full GC停顿时间过长导致影响用户体验。其实停顿时间长的问题无非就两种情况:
(1)内存过大导致gc真实回收过程时间长,在这种情况下可以考虑减少堆内存大小
(2)真实回收时间并不长,但是用户态执行时间和核心态执行时间过长,导致从客户角度来看停顿时间过长,对于这种情况要考虑线程 是否及时到达安全点,除了安全点问题也有可能是操作系统本身负载比较高,导致处理速度过慢,线程到达安全点的时间长,因此还需要检测操纵系统自身的运行情况。

调优案例:
宝箱项目自动售卖柜,他需要跟我们的通信服务相连,刚开始的时候我们只有一台通信服务,自动售卖柜的数量也在50到100之间,他发的请求都还能支撑得住,但后来随着设备的增多,他通过socket连接也会占用咱们的一个内存或者是通道,他会占用一些CPU资源,如果连接多了,设备多了,CPU负载就会提高,负载高了系统处理速度就会变慢。本来一个线程执行只需要几毫秒,现在需要几秒,就导致本来有些马上要被回收的对象,因为在GC之前线程还没处理完,而这部分对象幸存者区又放不下,就导致这些对象直接被扔进老年代,这时候就算老年代内存在大也会很快就满了,就会导致频繁的Full GC。

调优工具:
Arthas(阿尔萨斯)
jvisualvm(jvm自带工具)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值