首页 > 系统相关 >《深入理解Java虚拟机第3版》垃圾收集器与内存分配策略、虚拟机性能监控故障处理工具

《深入理解Java虚拟机第3版》垃圾收集器与内存分配策略、虚拟机性能监控故障处理工具

时间:2023-06-28 10:33:34浏览次数:48  
标签:Java 收集器 对象 虚拟机 标记 算法 引用 内存


目录


  • 往期博客:Java课堂篇3_初识JMM、常量池简单理解(字符串常量池、静态常量池、大整型常量池)
  1. 为什么要了解垃圾收集和内存分配?
  2. 如何判断对象已死?
  • 引用计数算法
  • 可达性分析算法
  • JDK1.2之后引用的扩充
  1. 回收方法区
  2. 垃圾收集算法分代收集理论
  • 标记清除
  • 标记复制
  • 标记整理
  1. 对象分配
  2. 虚拟机性能监控故障处理工具

1、为什么需要了解垃圾收集和内存分配?

当需要排查各种内存溢出、内存泄露问题时,当垃圾收集成为系统达到高并发量的瓶颈时,我们必须对这些“自动化”的技术实

施必要的监控和调节。

2、如何判断对象已死?

2.1、引用计数法

  • 在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当有一个地方取消引用它时,计数器值减1
  • 虽然额外占用内存空间,但是他的原理简单,判定效率也很高
  • Java领域主流的虚拟机未采用没有选择用,因为这个看似简单的算法有很多例外的情况要考虑,必须配合额外的大量处理才能确保正确工作,如单纯的引用计数很难解决对象之间的互相引用问题。

2.2、可达性分析

  • 通过一系列的GC Roots的根对象作为起始点集,从这些结点开始,根据引用关系向下搜索,搜索走过的路径称为引用链,如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是不可达,证明这个对象不再被使用
  • 固定可以作为GC Roots的对象包括:
  • 虚拟机栈引用的对象(方法参数、局部变量、临时变量)
  • 方法区的静态属性引用的对象
  • 方法区的常量池引用的对象
  • 本地方法栈Native方法引用的对象
  • 虚拟机内部的引用
  • 同步锁synchrionized持有的对象

2.3、JDK1.2之后引用的扩充

四种新扩充的引用

  • 强引用:传统的"引用"定义如Object o = new Object(),只要强引用的关系还在,垃圾收集器就永远不会回收掉被引用的对象
  • 软引用:描述还有用,但是非必须的对象,在系统将要发生内存溢出异常前,会把这些对象列入回收范围之中进行二次回收,如果回收之后还是内存不够,就会抛出异常。JDK1.2版之后提供了SftReference类来实现软引用
  • 弱引用:也是描述那些非必须的对象,但是它的强度比弱引用更弱一些,被弱引用引用的·对象只能生存到下一次垃圾收集发生为止。
  • 虚引用:不对对象存活造成影响,位一各对象设置虚引用的目的只是为了垃圾收集器回收该对象时收到有个系统通知。

3、回收方法区

  • 相比于堆内存的回收,方法区的回收由于苛刻的回收条件,其区域垃圾收集的成果往往很低
  • 方法区的垃圾收集主要涉及两部分的内容:
  • 废弃的常量:字符串常量池里面的常量等
  • 不在使用的类型:类、接口、方法、字段的符号引用等
  • 判断一个类不在使用需要考虑
  • 该类的所有实例都已经被回收
  • 加载该类的类加载器已经被回收
  • 该类对java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
  • 在大量使用反射、动态代理、CGLIB等字节码的框架,动态生成JSP等这类频繁自定义类加载器的场景,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。

4、垃圾收集算法

垃圾收集算法可以划分为

  • 引用计数式垃圾收集(对应前面的 引用计数算法)
  • 追踪式垃圾收集(对用前面的 可达性分析算法)

分代收集设计原则

  • 弱分代假说:任何对象都是朝生夕灭
  • 强分代假说:熬过越多次垃圾收集的对象就越难消亡

因此存在

  • 部分收集Partical GC
  • 新生代收集Young GC
  • 老年代收集Old GC
  • 混合收集Mixed GC
  • 整堆收集Full GC

4.1、标记清除算法

算法分为 标记 和 清除 两个阶段

  • 标记:首先标记出所有要回收的对象(或者存活的对象),在标记完成后
  • 清除:统一回收掉被标记的对象

缺点

  • 执行效率不太稳定:如果Java堆中包含大量对象,而且其中大部分都是需要回收的,这时需要大量标记和清除操作
  • 内存空间碎片化问题:清除之后,产生大量不连续的内存空间,当需要分配大对象是,有可能放不下还需要进行一次GC

4.2、标记复制算法

相比标记清除算法

  • 半区复制:将内存容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就还将存活的对象复制到另外一块,再把使用过的一块内存全部回收清理。
  • 缺点:这种算法产生大量的内存空间复制的开销,可用内存缩小为原来的一半,空间浪费。

半区复制分代策略

  • Appel式回收:虚拟机的新生代使用此种回收算法,将新生代分为 Eden和Survivor区域,比例为8:1:1;每次将Eden的存活对象放到Survivor中
  • 需要老年代内存担保,也就是Survivor需要像老年代传送对象。

4.3、标记整理算法

相比于新生代使用标记复制算法、标记清除算法

  • 老年代不能使用标记复制算法,因为老年代对象存活率高,进行复制会浪费内存空间
  • 针对老年代对象的死亡特征,需要使用标记-整理算法,区别于标记-清除算法本质区别就是 不是直接对可回收对象清理,而是将存活的对象往一段移动,清除边界以外的内存。

特点

  • 移动存活对象的·时候,尤其是老年代,移动对象地址需要全部用户程序才能进行Stop The World
  • 不移动时候采用标记清除算法,内存分配复杂
  • 移动的时候采用标记整理算法,回收时更复杂
  • 有一种和稀泥方式,碎片化程度到达一定程度开启移动

5、对象分配

对象的内存分配从概念上讲都是堆上分配,(实际可能有即时编译后被拆散为标量类型并间接的在栈上分配),在经典分代的设计下,新生对象会直接分布在新生代,一些超过阈值的大对象可以直接分布在老年代

  • 对象优先在Eden分配:没有足够空间会触发YoungGC,存活的对象会进入Survivor
  • 大对象直接进入老年代:为了避免大对象内存复制的开销,直接将大对象分配到老年代,通过设置-XX:PretenureSizeThreshold=3145728参数指定阈值
  • 长期存活的对象进入老年代:虚拟机给每个对象定义了一个对象年龄(Age)计数器存储在对象头中,对象通常在Eden单上,如果第一次经理YongGC能够存活下来并且Survivor能够放的下,该对象就会被移动到Survivor并且年龄加1岁,当年龄增加到一定程度(默认15岁)就会被放到年代。可以通过-XXMaxTenuringThreshold设置
  • 动态对象年龄判断:除了对象年龄增长进入老年代,如果Survivor空间相同年龄的对象综合大于Survivor空间的一半,年龄大于等于该年龄的对象可以直接进入老年。
  • 空间分配担保:在发生YoungGC的时候,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,
  • 如果这个条件成立,那么这一次GC是确保安全的
  • 如果这个条件不成立,则虚拟机会检查参数-XX:HandlePromotionFailure参数是否允许担保失败,如果允许,则会检查老年代可用连续空间是否大于历届上升到老年代对象年龄的平均大小
  • 如果大于,进行一次YoungGC
  • 如果小于,或者是-XX:handlePromotionFailure设置不允许冒险,这时候就需要进行一次FullGC

6、虚拟机性能监控故障处理工具

6.1基础故障处理工具

  • jsp:虚拟机进程状况工具
    虚拟机进程查看定位工具
  • 《深入理解Java虚拟机第3版》垃圾收集器与内存分配策略、虚拟机性能监控故障处理工具_深入理解JVM虚拟机

  • jstat:虚拟机统计信息监视工具
    显示类加载、内存、垃圾收集器、即时编译等运行时数据,定位虚拟机性能问题
  • jinfo:Java配置信息工具
  • jmap:Java内存映像工具
    用于生成堆转储快照
  • jhat:虚拟机堆转储快照分析工具
  • jstack:Java堆栈跟踪工具
  • jcmd:Java7开始提供的虚拟机诊断命令工具

基本Java工具

  • javadoc:API文档生成器
  • javap:字节码分析工具

6.2可视化故障处理工具

  • jhsdb:Java9开始提供的进程调试器,基于服务代理的调试工具
    首先使用jps查看进程号,然后jhsdb hsdb --pid xxx 进行操作
  • JConsole:Java监视与管理控制台
    控制台输入jconsole,本地、远程进行连接
  • VisualVM:多合-故障处理工具


标签:Java,收集器,对象,虚拟机,标记,算法,引用,内存
From: https://blog.51cto.com/u_15790456/6569638

相关文章

  • 编程初学者入门5_键盘输入5个人的身高(米),求他们的平均身高(米)。(C的没问题,试着用Java写
    写在前面此系列博客为牛客网编程初学者入门题目小结,题目很基础不常用的知识容易遗忘,为了边复习c语言和学习Java,后面系列博客将采用c、c++、Java双语言版记录现在位置(72/140),虽然我走的很慢,但我仍在前进~题目描述从键盘输入5个人的身高(米),求他们的平均身高(米)。输入描述:一行,连续输......
  • 编程初学者入门6_简单分支问题+Java在OJ中实现多组输入sc.hasNextInt()函数
    题目KiKi想知道这学期他的学习情况,BoBo老师告诉他这学期挂的科目累计的学分,根据所挂学分,判断KiKi学习情况,10分以上:很危险(Danger++),4~9分:危险(Danger),0~3:Good。输入描述:一行,一个整数(0~30),表示KiKi挂的科目累计的学分。输出描述:一行,根据输入的挂科学分,输出相应学习情况(Danger+......
  • 编程初学者入门7_公务员面试现场打分。有7位考官,从键盘输入若干组成绩,每组7个分数(百分
    题目描述公务员面试现场打分。有7位考官,从键盘输入若干组成绩,每组7个分数(百分制),去掉一个最高分和一个最低分,输出每组的平均成绩。输入描述:一行,输入7个整数(0~100),代表7个成绩,用空格分隔。输出描述:一行,输出去掉最高分和最低分的平均成绩,小数点后保留2位,每行输出后换行。示例1我的......
  • Java线程的WAITING状态和BLOCKED状态之间区别的一点理解
    先说结论:WAITING状态是线程在获取锁对象之后,由于某些原因需要等待一些事件的完成才能继续执行,这时线程调用Object.wait()、Thread.sleep()、Thread.join()等方法进入WAITING状态。而BLOCKED状态则是线程在准备进入某个同步代码块时,发现锁对象已经被其它线程占用了,这时线程就会进入......
  • Java语言学习2
    JavaSE2面向对象(OOP)编程初始面向对象面向过程&面向对象面向过程思想步骤清晰简单,第一步做什么,第二步做什么....面向过程适合处理一些简单的问题面向对象思想物以类聚,分类的思维模式,思考问题是否首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对......
  • Java语言学习1
    JavaSE1机器语言的发展史第一代语言(机器语言)机器语言计算机的基本计算方式都是基于二进制的方式。二进制:0101010010110101001这种代码是直接输入给计算机使用的,不经过任何转换101210311410051016110711181000161000032100000第二代机器语言(......
  • Java之thread常用成员
    setName用于自定义线程的名字,方便我们调试定为问题;@TestpublicvoidsetNameTest(){Runnablerun=()->{System.out.println(Thread.currentThread().getName());};vart1=newThread(run);t1.start();......
  • 【后端面经-Java】Synchronize和ReentrantLock区别
    目录1.概念介绍1.1线程安全锁1.2公平锁1.3响应中断/等待可中断2.区别2.1底层实现2.2锁的用法2.3锁的特点2.4性能比较2.5适用场景3.总结比较参考文献1.概念介绍1.1线程安全锁Synchronize(同步锁)和ReentrantLock(可重入锁)都是Java中的常用锁,都是用来保证线程安全的。......
  • java反序列化与反序列化
    java反序列化漏洞JAVA反序列化漏洞是由于开发者重写了readObject方法,该readObject方法方法调用了别的方法,最终执行到了例如Transfrom方法的危险方法java序列化过程:调用一个函数进行序列化,存放到一个文件内,再将文件反序列化回来,涉及到文件的读写序列化与反序列化序列化:Objec......
  • 【Java】讲讲StreamAPI
     预设场景:从Mybatis调用Mapper得到的用户集合List<UserDTO>userList=newArrayList<>(); 常用的几种API用法示例:Map方法,转换为某一个字段的集合:List<Integer>userIdList=userList.stream()/*map转换成某个类型来处理,比如这个场景是为了快速......