博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
重学Java虚拟机(二)—— JVM内存管理
阅读量:4224 次
发布时间:2019-05-26

本文共 2388 字,大约阅读时间需要 7 分钟。

前言

Java GC机制已经日臻完善,确实降低了开发人员不少编程难度。但是,作为一名java编程人员,学会JVM内存管理和回收机制,这可以帮助我们在日常工作中排查各种内存溢出或泄露问题,解决性能瓶颈,达到更高的并发量,写出更高效的程序。

对象存活判断策略

判断一个对象是否存活的算法有两种:引用计数算法和根搜索算法。

引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。

Java语言中没有使用引用计数算法来管理内存,最主要的原因是它很难解决对象之间的相互循环引用的问题。

根搜索算法

通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象时不可用的。

在Java语言里,可作为GC Roots的对象包括如下几种:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象

  • 方法区中的类静态属性引用的对象

  • 方法区中的常量引用的对象

  • 本地方法中JNI的引用的对象

垃圾回收算法

标记-清除算法

标记-清除算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。

此方法主要的缺点有两个:一个是效率问题,标记和清除过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片

复制算法

复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块内存用完后,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

此法最明显的不足在于将原先可用的内存空间缩小为一半,代价太高。

标记-整理算法

标记-整理算法和标记-清除算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

分代收集算法

当前商业虚拟机的垃圾收集都采用“分代收集”算法,根据各个年代的特点采用最适当的收集算法。

内存回收与分配策略

在JVM所管理的内存中,堆区是最大的一块,堆区也是JavaGC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区用来存储对象实例及数组值,可以认为java中所有通过new创建的对象都在此分配。

为了让内存回收更加高效,从Sun JDK 1.2开始对堆采用了分代管理方式,如下图所示:

这里写图片描述

  • 1,新建对象被优先分配到Eden区;

  • 2,当Eden区满了,会触发一次Minor GC;

  • 3,经过扫描与标记,存活的对象被复制到S0,不存活的对象被回收;

  • 4,在下一次的Minor GC中,Eden区的情况和上面一致,没有引用的对象被回收,存活的对象被复制到survivor区,然而在survivor区,S0的所有的数据都被复制到S1,需要注意的是,在上次minor GC过程中移动到S0中的两个对象在复制到S1后其年龄要加1,此时Eden区S0区被清空,所有存活的数据都复制到了S1区,并且S1区存在着年龄不一样的对象;

  • 5,经过几次Minor GC之后,当存活对象的年龄达到一个阈值之后(可通过参数配置,默认是8),就会被从年轻代Promote到老年代;

  • 6,随着MinorGC一次又一次的进行,不断会有新的对象被promote到老年代,最终,MajorGC将会在老年代发生,老年代的空间将会被清除和压缩。

从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方式分配内存和清理内存的效率都极高,这种垃圾回收的方式就是著名的“停止-复制(Stop-and-copy)”清理法(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中),这不代表着停止复制清理法很高效,其实,它也只在这种情况下(基于大部分对象存活周期很短的事实)高效,如果在老年代采用停止复制,则是非常不合适的。

而老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。一般,老年代用的算法是标记-整理算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。

关于方法区即永久代的回收,永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:

  • 类的所有实例都已经被回收
  • 加载类的ClassLoader已经被回收
  • 类对象的Class对象没有被引用(即没有通过反射引用该类的地方)

永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。

垃圾收集器

这里写图片描述

总结

本文介绍了JVM的垃圾回收机制,文中涉及的一些配置参数使用方法可以参加后续的引用文档信息,希望能对读者理解JVM虚拟机内存分配回收机制有所帮助。

JVM配置参数详解:

你可能感兴趣的文章
Oracle 11g 新特性 -- Database Replay (重演) 说明
查看>>
Oracle 11g 新特性 -- 自动诊断资料档案库(ADR) 说明
查看>>
CSDN博客之星 投票说明
查看>>
Oracle wallet 配置 说明
查看>>
Oracle smon_scn_time 表 说明
查看>>
VBox fdisk 不显示 添加的硬盘 解决方法
查看>>
Secure CRT 自动记录日志 配置 小记
查看>>
RMAN RAC 到 单实例 duplicate 自动分配通道 触发 ORA-19505 错误
查看>>
mysql 随机分页的优化
查看>>
DB2快速创建测试库
查看>>
SD卡驱动分析--基于高通平台
查看>>
[图文] Seata AT 模式分布式事务源码分析
查看>>
pm 源码分析
查看>>
kmsg_dump
查看>>
Getting a Result from an Activity
查看>>
Allowing Other Apps to Start Your Activity
查看>>
dev/mem
查看>>
pfn_valid 源码分析
查看>>
dev/kmem 和dev/mem的区别
查看>>
test-definitions/blob/master/auto-test/bigdata/bigdata.sh
查看>>