JVM内存组成结构:

(1)堆

所有通过new创建的对象都是在堆中分配内存,其大小可以通过-Xmx和-Xms来控制,堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区。Survivor被划分为from space 和 to space组成,结构图如下:

(2)栈

每个线程 执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包含局部变量区和操作数栈。用于存放此次方法调用过程中的临时变量,参数和中间结果

(3)本地方法栈

用于支持native方法的执行。存储了每个native方法调用的状态

(4)方法区

存放了要加载的类信息,静态变量,final类型的常量,属性和方法信息。JVM用持久代(permanet generation)来存放方法区,可通过-XX:PermSize和 -XX:MaxPermSize来指定最小值和最大值。

(5)程序计数器

每个线程私有,当前线程执行的字节码的行数。

JAVA堆内存分配机制

java内存分配和回收概括地说:就是分代分配,分代回收。对象将根据存货的时间被分为:young generation, old generation,permanent generation。

yong generation:对象被创建时,内存的分配首先发生在年轻代(大对象可以直接创建在old generation),大部分的对象在创建后很快不再使用,因此很快变得不可达,被young generation 的GC机制清理掉(IBM的研究表示,98%的对象都是很快消亡的),这个GC机制被称为Minor GC或者 Young GC;Minor GC并不代表内存不足。

young generation分为 3个区域, eden区,两个 survivor区(from survivor, to survivor),内存分配过程如下所示:

1.绝大多数对象刚创建被分配在 eden区,其中的大多数对象很快就会消亡,eden区域是连续的内存空间,在其上分配内存极快。

2.最初一次,当eden区满的时候,执行 minor GC,将消亡的对象清理掉,并将eden,survivor 1剩余的对象复制到到一个存活区 Survivor 0(此时Survivor 1是空白的,两个Survivor总有一个是空白的)

3.下次eden满了,在执行一次 minor GC,将消亡的对象清理掉,存活的对象复制到survivor1中,清空eden区。将survivor 0 中消亡的对象清理掉,将其中可以晋级的对象晋级到old区,将存活的对象也复制到survivor 1中,清空survivor 0

4.当被两个存活期 来回复制了几次之后,(用-XX:maxTenuringThreshold 控制,大于该值进入old generation,但是这只是个最大值,并不代表一定是这个值,因为:为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。)仍然存活的对象,将被复制到old generation。

old generation:对象如果在young generation 存活了足够长的时间而没有被清理掉,则会被复制到old generation,old generation 的空间一般比young generation大得多,发生的GC次数也比年轻代少,当年老代内存不足时,将执行 Major GC,也叫 Full GC;

可以使用-XX:+UseAdaptiveSizePolicy开关来控制是否采用动态控制策略,如果动态控制,则动态调整Java堆中各个区域的大小以及进入老年代的年龄。

如果对象比较大,young generation空间不足,则大对象会直接分配到old generation(大对象可能提前触发GC,应尽少使用大对象,更少用短命的大对象)。用-XX:PretenureSizeThreshold来控制直接升入老年代的对象大小,大于这个值的对象会直接分配在老年代上。

可能存在年老代对象引用新生代对象的情况,如果需要执行Young GC,则可能需要查询整个老年代以确定是否可以清理回收,这显然是低效的。解决的方法是,年老代中维护一个512 byte的块——”card table“,所有老年代对象引用新生代对象的记录都记录在这里。Young GC时,只要查这里即可,不用再去查全部老年代,因此性能大大提高。

JAVA 不同代 GC 机制

young generation:发生的GC是minor GC,使用停止-复制算法进行清理,将新生代内存分为:较大的eden,和两个相等survivor,每次进行清理时,把eden 和一个survivor中存活的对象 复制到另一个survivor中,如果存活的对象超过了survivor内存,则需要通过空间分配担保机制将一部分对象复制到old generation,然后清理掉eden,和刚才的survivor。可以通过 -XX:SurvivorRation参数来调整eden和survivor区的内存容量比值。默认是8,eden:survivor:survivor = 8:1:1。

old generation:发生major GC。存储的对象比young generation多得多,且存在很多大对象,对old generation进行内存清理,如果使用 停止-复制算法,相当低效,一般使用 标记-整理算法,标记出仍然存活的对象(存在引用),将所有存活的对象向一端移动,以保证内存的连续。

在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。

old generation GC之 标记-清除法:标记出所有需要回收的对象(可达性分析),标记完成后统一清理掉所有被标记的对象。

该算法有两个问题:

(1)下频率问题:标记和清除过程效率都不高

(2)空间问题:标记清除后会产生大量的不连续的内存碎片,空间碎片太多会导致在运行过程需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集。

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

permanent generation:永久代的垃圾收集分为两类:废弃的常量和不再被使用的类。

废弃常量:比如说我们在常量池中用intern()添个字符串常量“a”,但是现在的系统没有任何一个String对象叫“a”,所以这个常量就是废弃了。

不再被使用的类:①该类的所有的实例都已经被回收,Java堆中不存在该类的任何实例。

②加载类的ClassLoader已经被回收

③没有该类的java.lang.Class对象被引用,即不能通过反射访问该类信息。

满足了上述三个条件只是满足了类回收的基本条件,是否回收不用的类需要看设置的-Xnoclassgc参数进行控制,还可以使用-verbose:class及-XX:+TraceClassLoading、 -XX:+TraceClassUnLoading查看类的加载和卸载信息。

在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。

算法分析:

空间分配担保:

在执行Minor GC前, VM会首先检查老年代是否有足够的空间存放新生代尚存活对象, 由于新生代使用复制收集算法, 为了提升内存利用率, 只使用了其中一个Survivor作为轮换备份, 因此当出现大量对象在Minor GC后仍然存活的情况时, 就需要老年代进行分配担保, 让Survivor无法容纳的对象直接进入老年代, 但前提是老年代需要有足够的空间容纳这些存活对象. 但存活对象的大小在实际完成GC前是无法明确知道的, 因此Minor GC前, VM会先首先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小, 如果条件成立, 则进行Minor GC, 否则进行Full GC(让老年代腾出更多空间).然而取历次晋升的对象的平均大小也是有一定风险的, 如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然可能导致担保失败(Handle Promotion Failure, 老年代也无法存放这些对象了), 此时就只好在失败后重新发起一次Full GC(让老年代腾出更多空间).

GC回收对象确立:

    引用计数:如果有引用这个对象的,对象计数器+1,引用失效,计数器-1,绝大多数情况下,这个算法高效简单,但是不能解决对象之间的循环引用的关系,所以没有被主流语言采用。

可达性算法:通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的, 如下图: Object5、6、7 虽然互有关联, 但它们到GC Roots是不可达的, 因此也会被判定为可回收的对象:

在Java, 可作为GC Roots的对象包括:

  1. 方法区: 类静态属性引用的对象;
  2. 方法区: 常量引用的对象;
  3. 虚拟机栈(本地变量表)中引用的对象.
  4. 本地方法栈JNI(Native方法)中引用的对象。

可达性算法如果对象到GC Roots不可达也不是说这个对象会立即被回收,是需要经过一个两次标记过程的,第一次:是在可达性分析后发现没有与GC Roots相连接的引用链。第二次是判断是否需要执行finalize(),这个方法也是对象唯一能够进行自我救赎的机会了,但是不推荐使用,因为这个方法运行代价高,不确定性大,不能保证不同对象的执行顺序。如果不需要执行该方法,直接就进行回收,如果需要执行该方法,那么该对象会被放在一个F-Queue里。虽然会被执行,但是不一定保证能够执行成功,因为有可能会在这个方法执行过程中出现死循环等意外情况,所以虚拟机并不一定会等待这个方法执行结束才进行回收。如果在第二次标记的时候,该对象没有成功的自我拯救,那么就真的被回收了。

常用JVM配置参数

-XX:CMSInitiatingPermOccupancyFraction当永久区占用率达到这一百分比时,启动CMS回收
-XX:CMSInitiatingOccupancyFraction设置CMS收集器在老年代空间被使用多少后触发
-XX:+CMSClassUnloadingEnabled允许对类元数据进行回收
-XX:CMSFullGCsBeforeCompaction设定进行多少次CMS垃圾回收后,进行一次内存压缩
-XX:NewRatio:新生代和老年代的比
-XX:ParallelCMSThreads设定CMS的线程数量
-XX:ParallelGCThreads设置用于垃圾回收的线程数
-XX:SurvivorRatio设置eden区大小和survivior区大小的比例
-XX:+UseParNewGC在新生代使用并行收集器
-XX:+UseParallelGC 新生代使用并行回收收集器
-XX:+UseParallelOldGC老年代使用并行回收收集器
-XX:+UseSerialGC在新生代和老年代使用串行收集器
-XX:+UseConcMarkSweepGC新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:+UseCMSCompactAtFullCollection设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
-XX:+UseCMSInitiatingOccupancyOnly表示只在到达阀值的时候,才进行CMS回收

(+不能省)
-Xms:设置堆的最小空间大小。
-Xmx:设置堆的最大空间大小。
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss:设置每个线程的堆栈大小

java之JVM学习--简单了解GC算法的更多相关文章

  1. java之JVM学习--简单理解编译和运行的过程之概览

    java代码编译流程图: java字节码执行由JVM执行引擎完成 Java代码编译和执行的整个过程包含了以下三个重要的机制: Java源码编译机制 类加载机制 类执行机制 Java源码编译机制 Jav ...

  2. java之jvm学习笔记十三(jvm基本结构)

    java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...

  3. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  4. java之jvm学习笔记三(Class文件检验器)

    java之jvm学习笔记三(Class文件检验器) 前面的学习我们知道了class文件被类装载器所装载,但是在装载class文件之前或之后,class文件实际上还需要被校验,这就是今天的学习主题,cl ...

  5. java之jvm学习笔记四(安全管理器)

    java之jvm学习笔记四(安全管理器) 前面已经简述了java的安全模型的两个组成部分(类装载器,class文件校验器),接下来学习的是java安全模型的另外一个重要组成部分安全管理器. 安全管理器 ...

  6. java之jvm学习笔记二(类装载器的体系结构)

    java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...

  7. Java虚拟机JVM学习07 类的卸载机制

    Java虚拟机JVM学习07 类的卸载机制 类的生命周期 当Sample类被加载.连接和初始化后,它的生命周期就开始了. 当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就 ...

  8. Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...

  9. Java虚拟机JVM学习05 类加载器的父委托机制

    Java虚拟机JVM学习05 类加载器的父委托机制 类加载器 类加载器用来把类加载到Java虚拟机中. 类加载器的类型 有两种类型的类加载器: 1.JVM自带的加载器: 根类加载器(Bootstrap ...

随机推荐

  1. css简单学习属性2---背景图片

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. ORA-03114: not connected to ORACLE

    PlSql Developer出现这个问题的时候,只要重新连接一些数据库就行了!

  3. Linux学习笔记之系统中的分区和文件系统

    转自 http://blog.csdn.net/hanxuehen/article/details/8229472

  4. redis管理工具rdm安装;另一个管理工具medis

    安装: brew cask install rdm 开源安装 http://docs.redisdesktop.com/en/latest/install/ 说明 直接从官网下载也可以,不过是收费的 ...

  5. python 操作mysql数据库(mac)包括如何处理安装MySQL-python

    一.数据库的安装,https://www.jianshu.com/p/fd3aae701db9      https://jingyan.baidu.com/article/fa4125ac0e3c2 ...

  6. Docker使用pipework配置本地网络

    需求 在使用Docker的过程中,有时候我们会有将Docker容器配置到和主机同一网段的需求.要实现这个需求,我们只要将Docker容器和主机的网卡桥接起来,再给Docker容器配上IP就可以了. 下 ...

  7. Cocos Creator Android打包 apk

    这一篇讲的是用 Cocos Creator 编译器打包 Android APP 的时候遇到的一些问题,虽然说打包的过程不是很复杂,但是在其中还是会遇到各式各样的坑. 我们将项目用CCC(Cocos C ...

  8. exchange 2010入门到精通

    exchange 2010入门到精通 Exchange产品介绍和功能演示 Exchange是什么 目前最受欢迎企业级邮件服务器产品 市场占有率70%(2011数据) 微软消息协作平台中核心产品 Exc ...

  9. cpp调用c的动态库

    目录 cpp调用c的动态库 title: cpp调用c的动态库 date: 2019/11/22 20:34:29 toc: true --- cpp调用c的动态库 CPP文件里这么引用头文件即可 e ...

  10. ZUI(BootStrap)使用vue动态插入HTMl所创建的data-toggle事件初始化方法

    用ZUI的图片浏览:lightbox 写静态html的时候是有预览效果的,使用了vue动态加载就没有效果了, 网上的说法是动态生成的没有激活事件:ZUI(BootStrap)动态插入HTMl所创建的d ...