java代码时如何运行起来

  • 1 把我们的代码编译打包 jar
  • 2 java -jar 运行我们得代码
  • 3 java采用类加载器把编译好得那些class 字节码文件给加载到JVM中,然后供后续代码运行来使用
  • 4 jvm就会基于自己得字节码执行引擎,来执行加载到内存里得类
    (比如你的代码有一个 main 方法那么JVM就会有这个 main 方法开始执行里面得代码,它需要哪个类,就会使用类加载器来加载对应,反正对应得类就在.class文件中)

java代码时如何运行起来

JVM在什么情况下会加载一个类

其实类加载过程非常的琐碎复杂,但是对于我们平时从工作中实用的角度来说,主要是把握他的核心工作原理就可以。

一个类从加载到使用,一般会经历下面的这个过程:
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载

所以首先要搞明白的第一个问题,就是JVM在执行我们写好的代码的过程中,一般在什么情况下会去加载一个类呢?

也就是说,啥时候会从“.class”字节码文件中加载这个类到JVM内存里来。

其实答案非常简单,就是在你的代码中用到这个类的时候。

  • 加载
  • 验证
    校验你加载进来JVM规范
  • 准备
    类变量分配一定的内存空间
  • 解析
    符号引用替换为直接引用的过程
    此过程很复杂,涉及到JVM的底层
  • 核心阶段:初始化
    类初始化代码
    规则:初始化一个类的时候,发现他的父亲还未初始化,那么必须先初始化父类?
  • 类加载器+双亲委派机制
    • 启动类加载器 Bootstrap ClassLoader,他主要是负责加载我们在机器上安装的Java目录下的核心类的,java安装目录lib
    • 扩展类加载器 Extension ClassLoader,这个类加载器其实也是类似的,就是你的Java安装目录下,有一个“lib\ext”目录
    • 应用程序类加载Application ClassLoader
    • 自定义类加载器
      自定义类加载器,去根据你自己的需求加载你的类。

双亲委派机制

 双亲委派机制

就是假设你的应用程序类加载器需要加载一个类,他首先会委派给自己的父类加载器去加载,最终传导到顶层的类加载器去加载
但是如果父类加载器在自己负责加载的范围内,没找到这个类,那么就会下推加载权利给自己的子类加载器。

这就是所谓的双亲委派模型:先找父亲去加载,不行的话再由儿子来加载。
这样的话,可以避免多层级的加载器结构重复加载某些类。

双亲委派机制1

Tomcat的类加载机制

首先Tomcat的 类加载器体系如下图所示,他是自定义了很多类加载器的。

Tomcat的类加载机制
Tomcat自定义了Common、Catalina、Shared等类加载器,其实就是用来加载Tomcat自己的一些核心基础类库的。然后Tomcat为每个部署在里面的Web应用都有一个对应的WebApp类加载器,负责加载我们部署的这个Web应用的类
至于Jsp类加载器,则是给每个JSP都准备了一个Jsp类加载器。
而且大家一定要记得,Tomcat是打破了双亲委派机制的
每个WebApp负责加载自己对应的那个Web应用的class文件,也就是我们写好的某个系统打包好的war包中的所有class文件,不会传导给上层类加载器去加载。
tomcat打破了双亲委派的原则,实际是在应用类加载器中打破了双亲委派,其他类加载器还是遵循双亲委派的。

java内存区域划分

  • 存在类的方法区
    主要是放从“.class”文件里加载进来的类,还会有一些类似常量池的东西放在这个区域里。

    • 1.8前:方法区
    • 1.8后:Metaspace
  • 执行代码指令用的程序计数器

我们写好的Java代码会被翻译成字节码,对应各种字节码指令

就是用来记录当前执行的字节码指令的位置的,也就是记录目前执行到了哪一条字节码指令

  • java虚拟机栈

JVM必须有一块区域是来保存每个方法内的局部变量等数据的,这个区域就是Java虚拟机栈
每个线程都有自己的Java虚拟机栈

  • java 堆内存
    这里就是存放我们在代码中创建的各种对象的

  • 本地方法栈

在调用这种native方法的时候,就会有线程对应的本地方法栈
是跟Java虚拟机栈类似的,也是存放各种native方
法的局部变量表之类的信息。

java 内存区域

java 垃圾回收

我们在Java堆内存里创建的对象,都是占用内存资源的,而且内存资源有限。

你只要启动一个JVM进程,他就会自带这么一个垃圾回收的后台线程。
这个线程会在后台不断检查JVM堆内存中的各个实例对象,这些不再被人指向的对象实例,即JVM中的“垃圾”,就会定期的被后台垃圾回收线程清理掉,不断释放内存资源

java 垃圾回收

jvm 分代模型

年轻代,老年代,永久代

  • 大部分对象都是存活周期短的
  • 少数对象时长期活的

年轻代

  • 很快就要被回收的对象
  • 大部分的正常对照都是优先再新生代分配内存
  • 新生代的内存垃圾回收叫Minor GC 或者叫Young GC
  • 新生代如果对象满了,会触发Minor GC 回收掉没有人引用的垃圾对象
  • 大部分的正常对象,都是优先在新生代分配内存的

老年代

  • 长期存在的对象
  • 长期存活的对象躲过多次垃圾回收 就会进入老年代,15(动态对象年龄判断机制)次
  • 如果老年代也满了,那么也会触发垃圾回收,把老年代里没人引用的垃圾对象清理掉

永久代

我们之前说的方法区,可以放一些类信息

一般永久代刚开始上线一个系统,没太多可以参考的规范,但是一般你设置个几百MB

参数:

  • -Xms:
    Java堆内存的刚开始的大小
  • -Xmx:
    Java堆内存的最大大小
    -Xms喝-Xmx通常会设置完全一样的大小
  • -Xmn:
    Java堆内存中的新生代大小,扣除新生代剩下的就是老年代的内存大小了
  • -XX:PermSize:
    永久代大小
  • -XX:MaxPermSize:
    永久代最大大小

JDK 1.8以后的版本,那么这俩参数被替换为了-XX:MetaspaceSize和-XX:MaxMetaspaceSize,

  • -Xss:每个线程的栈内存大小

,每个线程都有一个自己的虚拟机栈,然后每次执行一个方法,就会将方法的栈帧压入线程的栈里,方法执行完
毕,那么栈帧就会从线程的栈里出栈
一般也不会特别的去预估和设置的,一般默认就是比如512KB到1MB
jvm 分代模型

  • 下面如何设置java程序 jvm参数
    1
    java -Xms512M -Xmx512M -Xmn256M -Xss1M -XX:PermSize=128M -XX:MaxPermSize=128M -jar App.jar
  • Tomcat、Spring Boot部署启动系统的时候,JVM参数如何设置?
    tomcat jvm参数:
    1
    JAVA_OPTS="-Xms512M -Xmx512M -Xmn256M -Xss1M -XX:PermSize=128M -XX:MaxPermSize=128M"
    Spring Boot jvm参数:
    1
    java -Xms512M -Xmx512M -Xmn256M -Xss1M -XX:PermSize=128M -XX:MaxPermSize=128M -jar App.jar

到底什么情况下会触发新生代的垃圾回收?

jvm 问题

  • 我们在Java堆内存中分配的那些对象,到底会占用多少内存?一般怎么来计算和估算我们的系统创建的对象对内存占用的一个压力呢?
    答:
    一个对象对内存空间的占用,大致分为两块:
    一个是对象自己本身的一些信息
    一个是对象的实例变量作为数据占用的空间
    比如对象头,如果在64位的linux操作系统上,会占用16字节,然后如果你的实例对象内部有个int类型的实例变量,他
    会占用4个字节,如果是long类型的实例变量,会占用8个字节。如果是数组、Map之类的,那么就会占用更多的内存
    了。

如何合理设置JVM内存大小

计算了这个系统在日百万交易的压力下,部署3台机器的场景下,每秒钟每台机器需要处理多少笔订单,每笔订单要耗时多久处理,每秒钟会对JVM占据多大内存空间,根据这个横向扩展预估整个系统每
秒需要占据多大内存空间。
接着根据上述数据模型推算出,在不同的机器配置之下,你的新生代大致会有多大的内存空间,然后在不同的新生代大小之
下,多久会触发一次Minor GC
为了避免频繁的GC,那么应该选用什么样的机器配置,部署多少台机器,给JVM堆内存多大的内存空间,新生代多大的内存
空间。
根据这套配置,就可以推算出来整个系统的运行模型了,每秒钟创建多少对象在新生代,然后1秒之后成为垃圾,大概系统运
行多久,新生代会触发一次GC,频率有多高 。

如何合理设置永久代大小?

话说回来,如何合理设置永久代大小呢?
其实一般永久代刚开始上线一个系统,没太多可以参考的规范,但是一般你设置个几百MB,大体上都是够用的
因为里面主要就是存放一些类的信息

如何合理设置栈内存大小

其实这个栈内存大小设置,一般也不会特别的去预估和设置的,一般默认就是比如512KB到1MB,就差不多够了。
这就是每个线程自己的栈内存空间,用来存放线程执行方法期间的各种布局变量的。后面也会用专门的案例演示,栈内存什么
时候会发生内存溢出。

目标

后建立起来一个全面的工程素养,每个合格的工程师,都应该在上线系统的时候,对系统压力做出预估,
然后对JVM内存、磁盘空间大小、网络带宽、数据库压力做出预估,然后各方面都给出合理的配置。

什么时候进行垃圾回收

只要你的对象被方法的局部变量、类的静态变量给引用了,就不会回收他们。

  • 强引用

强引用
一个变量引用一个对象,只要是强引用的类型,那么垃圾回收的时候绝对不会去回收这个对象的。

  • 软引用
    软引用
    正常情况下垃圾回收是不会回收软引用对象的,但是如果你进行垃圾回收之后,发现内存空间还是不够存放新的对象,内存都快溢出了
    此时就会把这些软引用对象给回收掉,哪怕他被变量引用了,但是因为他是软引用,所以还是要回收。

  • 弱引用
    ,你这个弱引用就跟没引用是类似的,如果发生垃圾回收,就会把这个对象回收掉。
    弱引用

  • 虚引用
    因为很少用。

  • finalize()方法的作用

垃圾回收算法

复制算法

把新生代内存划分为两块内存区域,然后只使用其中一块内存
待那块内存快满的时候,就把里面的存活对象一次性转移到另外一块内存区域,保证没有内存碎片
接着一次性回收原来那块内存区域的垃圾对象,再次空出来一块内存区域。两块内存区域就这么重复着循环使用。
缺点:对内存的使用效率太低了。

Eden区和Survivor区

真正的复制算法会做出如下优化,把新生代内存区域划分为三块:
1个Eden区,2个Survivor区,其中Eden区占80%内存空间,每一块Survivor区各占10%内存空间,比如说Eden区有800MB内存,每一块Survivor区就100MB内存,一块Survivor区就100MB内存,一块Survivor区就100MB内存,
新生代
好处:就是只有10%的内存空间是被闲置的,90%的内存都被使用上了无论是垃圾回收的性能,内存碎片的控制,还是说内存使用的效率,都非常的好。

新生代垃圾回收的各种万一:

  • 躲过15次GC之后进入老年代
    “-XX:MaxTenuringThreshold”来设置,默认是15岁
  • 一批对象的总大小大于了这块Survivor区域的内存大小的50%,那么此时大
    于等于这批对象年龄的对象,就可以直接进入老年代了。(年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区
    域的50%,此时就会把年龄n以上的对象都放入老年代)
  • 大对象直接进入老年代:“-XX:PretenureSizeThreshold”,

Minor GC后的对象太多无法放入Survivor区怎么办?

  • Minor GC后的对象太多无法放入Survivor区 就会把这些对象转移到老年代
  • 老年代空间分配担保规则
    • 老年代空间也不够放这些对象
      • 老年代的可用内存已经小于了新生代的全部对象大小了,就会看一个“-XX:-HandlePromotionFailure”的参数是否设置了(是“-XX:-HandlePromotionFailure”参数没设置,此时就会直接触发一次“FullGC”,就是对老年代进行垃圾回收,尽量腾出来一些内存空间,然后再执行Minor GC。) 是看看老年代的内存大小,是否大于之前每一次Minor GC后进入老年代的对象的平均大小
      • 第一种可能,Minor GC过后,剩余的存活对象的大小,是小于Survivor区的大小的,那么此时存活对象进入Survivor区域即可。
      • 第二种可能,Minor GC过后,剩余的存活对象的大小,是大于 Survivor区域的大小,但是是小于老年代可用内存大小的,此时就直接进入老年代即可。
      • 很不幸,Minor GC过后,剩余的存活对象的大小,大于了Survivor区域的大小,也大于了老年代可用内
        存的大小。此时老年代都放不下这些存活对象了,就会发生“Handle Promotion Failure”的情况,这个时候就会触
        发一次“Full GC”。
      • Full GC就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。
        因为这个时候必须得把老年代里的没人引用的对象给回收掉,然后才可能让Minor GC过后剩余的存活对象进入老年代
        里面。如果要是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致所谓的
        “OOM”内存溢出了

Minor GC后的对象太多无法放入Survivor区

老年代垃圾回收算法

标记整理算法

  • 首先标记出来老年代当前存活的对象,这些对象可能是东一个西一个的。
  • 接着会让这些存活对象在内存里进行移动,把存活对象尽量都挪动到一边去,让存活对象紧凑的靠在一起,避免垃圾回收过后出现过多的内存碎片然后再一次性把垃圾对象都回收掉

标记整理算法

  • 这个老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍。
    如果系统频繁出现老年代的Full GC垃圾回收,会导致系统性能被严重影响,出现频繁卡顿的情况。

常见垃圾回收器

  • Serial和Serial Old垃圾回收器:分别用来回收新生代和老年代的垃圾对象–单线程
  • ParNew和CMS垃圾回收器:ParNew现在一般都是用在新生代的垃圾回收器,CMS是用在老年代的垃圾回收器,他们都是多线程并发的机制,性能更好,现在一般是线上生产系统的标配组合

ParNew和CMS垃圾回收器

  • G1垃圾回收器:统一收集新生代 和老年代,采用了更加优秀的算法和设计机制,

stop the world

  • 然后让垃圾回收线程可以专心致志的进行垃圾回收的工作使用JVM最大的痛点,其实就是在垃圾回收的这个过程
    因为在垃圾回收的时候,尽可能要让垃圾回收器专心致志的干工作,不能随便让我们写的Java系统继续对象了,所以此时JVM会在后台
    直接进入“Stop the World”状态。
    也就是说,他会直接停止我们写的Java系统的所有工作线程,让我们写的代码不再运行!然后让垃圾回收线程可以专心致志的进行垃圾回收的工作

stop the world
就可以让我们的系统暂停运行,然后不再创建新的对象,同时让垃圾回收线程尽快完成垃圾回收的工作,就是标记和转移
Eden以及Survivor2的存活对象到Survivor1中去,然后尽快一次性回收掉Eden和Survivor2中的垃圾对象,

现在大家就很清晰“Stop the World”会对系统造成的影响了, 假设我们的Minor GC要运行100ms,那么可能就会导致我们的系统
直接停顿100ms不能处理任何请求
在这100ms期间用户发起的所有请求都会出现短暂的卡顿,因为系统的工作线程不在运行,不能处理请求。

ParNew 是如何工作

  • 新生代垃圾回收器
  • 使用“-XX:+UseParNewGC”选项,只要加入这个选项,JVM启动之后对新生代进行垃圾回收的,就是ParNew垃圾回收器了。
  • ParNew垃圾回收器默认情况下的线程数量:跟CPU核数一样的线程数量或使用“-XX:ParallelGCThreads”参数设置线程数(一般不要随意动这个参数)

ParNew

  • 新生代的ParNew垃圾回收器主打的就是多线程垃圾回收机制,另外一种Serial垃圾回收器主打的是单线程垃圾回收,他们俩都是回收新生代的,唯一的区别就是单线程和多线程的区别,但是垃圾回收算法是完全一样的。

  • 算法:标记-复制算法

  • 如果“-XX:SurvivorRatio”参数默认值为8,那么此时新生代里Eden区大概占据了1.2GB内存,每个Survivor区是150MB的内存

  • 参数
    ParNew垃圾回收器的核心参数,其实就是配套的新生代内存大小、Eden和Survivor的比例,只要你设置合理,避免
    Minor GC后对象放不下Survivor进入老年代,或者是动态年龄判定之后进入老年代,给新生代里的Survivor充足的空
    间,那么Minor GC一般就没什么问题

    1
    2
    3
    “-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -
    XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -
    XX:+UseConcMarkSweepGC”

对象什么时候进入老年代

  • 首先第一种情况,那绝对就是“-XX:MaxTenuringThreshold=5”这个参数会让在一两分钟内连续躲过5次Minor GC的对象迅速进入老年代中。
    *(1)没有打开“ -XX:HandlePromotionFailure”选项,结果老年代可用内存最多也就1G,新生代对象总大小最多可
    以有1.8G
    那么会导致每次Minor GC前一检查,都发现“老年代可用内存” < “新生代总对象大小”,这会导致每次Minor GC
    前都触发Full GC。
    当然,上篇文章提到过了,现在JDK 1.6以后的版本废弃了这个参数,其实只要满足下面第二个条件就可以直接触发
    Minor GC,不需要触发Full GC。
    *(2)每次Minor GC之前,都检查一下“老年代可用内存空间” < “历次Minor GC后升入老年代的平均对象大小”
    其实按照我们目前设定的背景,要很多次Minor GC之后才可能有一两次碰巧会有200MB对象升入老年代,所以这个
    “历次Minor GC后升入老年代的平均对象大小”,基本是很小的。
    *(3)可能某次Minor GC后要升入老年代的对象有几百MB,但是老年代可用空间不足了
    *(4)设置了“-XX:CMSInitiatingOccupancyFaction”参数,比如设定值为92%,那么此时可能前面几个条件都没满
    足,但是刚好发现这个条件满足了,比如就是老年代空间使用超过92%了,此时就会自行触发Full GC

  • 参数
    Full GC优化的前提是Minor GC的优化,Minor GC的优化的前提是合理分配内存空间,合理分
    配内存空间的前提是对系统运行期间的内存使用模型进行预估。

    1
    2
    3
    4
    “-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -
    XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -
    XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection
    -XX:CMSFullGCsBeforeCompaction=0

CMS 垃圾回收器

  • 算法:标记清理算法

  • 初始标记
    stop the world状态(系统的工作线程全部停止)
    影响不大,速度很快

  • 并发标记
    让系统可以随意创建各种对象,继续运行
    对老年代所有所有对象进行Gc Roots追踪,其实最耗时
    ,跟系统程序并发运行,对系统运行造成影响
    他需要追踪所有对象是否从根源上被GC Roots引用了,但是这个最耗时的阶段,是跟系统程序并发运行的,所以其实这个阶段不会对
    系统运行造成影响的。

  • 重新标记
    stop the world状态(系统的工作线程全部停止)
    是速度很快的,他其实就是对在第二阶段中被系统程序运行变动过的少数对象进行标记,所以运行速度很快

  • 并发清理
    是让系统程序随意运行,然后他来清理掉之前标记为垃圾的对象即可。
    因为需要进行对象的清理,但是他也是跟系统程序并发运行的,所以其实也不影响系统程序的执行

cms

  • 总结 :最耗时的,其实就是对老年代全部对相关进行GC Roots追踪,标记出来到底哪些可以回收,然后就是对各种垃圾对象从内存里清理掉,这是最耗时的。第二阶段和第四阶段,都是和系统程序并发执行的,基本这两个最耗时的阶段对性能影响不大。
    第一个阶段和第三个阶段是需要“Stop the World”的,但是这两个阶段都是简单的标记而已,速度非常的快,所以基本上对系
    统运行响应也不大。

  • “-XX:MaxTenuringThreshold”参数的默认值15次来说

  • -XX:PretenureSizeThreshold=1M 大对象进入老年代

cms 性能分析

CMS垃圾回收器有一个最大的问题,虽然能在垃圾回收的同时让系统同时工作,但是大家发现没有,在并发标记和并发清理两个最耗时
的阶段,垃圾回收线程和系统工作线程同时工作,会导致有限的CPU资源被垃圾回收线程占用了一部分
CMS垃圾回收器有一个最大的问题,虽然能在垃圾回收的同时让系统同时工作,但是大家发现没有,在并发标记和并发清理两个最耗时
的阶段,垃圾回收线程和系统工作线程同时工作,会导致有限的CPU资源被垃圾回收线程占用了一部分。
CMS的垃圾回收线程是比较耗费CPU资源的。CMS默认启动的垃圾回收线程的数量是(CPU核数 + 3)/ 4。

“-XX:CMSInitiatingOccupancyFaction”参数可以用来设置老年代占用多少比例的时候触发CMS垃圾回收,JDK 1.6里面默认的值是
92%。

如果内存碎片太多,会导致后续对象进入老年代找不到可用的连续内存空间了,然后触发Full GC。
所以CMS不是完全就仅仅用“标记-清理”算法的,因为太多的内存碎片实际上会导致更加频繁的Full GC。

  • CMS有一个参数是“-XX:+UseCMSCompactAtFullCollection”,默认就打开了是在Full GC之后要再次进行“Stop the World”,停止工作线程,然后进行碎片整理,就是把存活对象挪到一起,空出来大片
    连续内存空间,避免内存碎片。

  • -XX:CMSFullGCsBeforeCompaction”,这个意思是执行多少次FullGC之后再执行一次内存碎片整理的工作,默认是0,意思就是每次FullGC之后都会进行一次内存整理。

  • -XX:+UseParNewGC -
    XX:+UseConcMarkSweepGC” 指定新生代回收器,指定老年代回收器

G1 垃圾回收器

  • ParNew + CMS带给我们的痛点是什么?
    stop the world

之后对垃圾回收器的优化,都是朝着减少“Stop the World”的目标去做的。

G1垃圾回收器是可以同时回收新生代和老年代的对象的

他最大的一个特点,就是把Java堆内存拆分为多个大小相等的Region 后G1也会有新生代和老年代的概念,但是只不过是逻辑上的概念
也就是说,新生代可能包含了某些Region,老年代可能包含了某些Reigon,
G1垃圾回收器

  • 可以让我们设置一个垃圾回收的预期停顿时间

希望G1在垃圾回收的时候,可以保证,在1小时内由G1垃圾回收导致的“Stop the World”时间,
也就是系统停顿的时间,不能超过1分钟。

  • 其实我们对内存合理分配,优化一些参数,就是为
    了尽可能减少Minor GC和Full GC,尽量减少GC带来的系统停顿,避免影响系统处理请求。

G1是如何做到对垃圾回收导致的系统停顿可控的?

G1可以做到让你来设定垃圾回收对系统的影响,他自己通过把内存拆分为大量小Region,以及追踪每个Region中可以
回收的对象大小和预估时间,最后在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时间内尽量回收尽可能多的垃圾对象。

参数

  • 用“-XX:+UseG1GC”来指定使用G1垃圾回收器,
    因为JVM最多可以有2048个Region,然后Region的大小必须是2的倍数,比如说1MB、2MB、4MB之类的。
    比如说堆大小是4G,那么就是4096MB,此时除以2048个Region,每个Region的大小就是2MB。大概就是这样子来决定Region的数
    量和大小的,大家一般保持默认的计算方式就可以
    如果通过手动方式来指定,则是“-XX:G1HeapRegionSize”,

  • 刚开始的时候,默认新生代对堆内存的占比是5%,也就是占据200MB左右的内存,对应大概是100个Region,这个是可以通过“-
    XX:G1NewSizePercent”来设置新生代初始占比的,其实维持这个默认值即可。

  • 因为在系统运行中,JVM其实会不停的给新生代增加更多的Region,但是最多新生代的占比不会超过60%,可以通过“-
    XX:G1MaxNewSizePercent”。

  • 新生代还有Eden和Survivor的概念吗?
    “-XX:SurvivorRatio=8”,所以这里还是可以区分出来属于新生代的Region里哪些属于Eden,哪些哪些属于Survivor。
    ,有100个Region,那么可能80个Region就是Eden,两个Survivor各自占10个Region,如下
    图。

  • 因为G1是可以设定目标GC停顿时间的,也就是G1执行GC的时候最多可以让系统停顿多长时间,可
    以通过“-XX:MaxGCPauseMills”参数来设定,默认值是200ms。

对象什么时候进入老年代

(1)对象在新生代躲过了很多次的垃圾回收,达到了一定的年龄了,“-XX:MaxTenuringThreshold”参数可以设置这个年龄,他就会进入老年代
(2)动态年龄判定规则,如果一旦发现某次新生代GC过后,存活对象超过了Survivor的50%
此时就会判断一下,比如年龄为1岁,2岁,3岁,4岁的对象的大小总和超过了Survivor的50%,此时4岁以上的对象全部会进入老年
代,这就是动态年龄判定规则

  • 大对象Region
    G1提供了专门的Region来存放大对象
    在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,比如按照上面算的,每个Region是2MB,只要一个大对象超过了1MB,就会被放入大对象专门的Region中
    而且一个大对象如果太大,可能会横跨多个Region来存放。如下图。
    G1回收器
    其实新生代、老年代在回收的时候,会顺带带着大对象Region一起回收,所以这就是在G1内存模型下对大对象的分配和回
    收的策略。

  • 什么时候触发新生代+老年代的混合垃圾回收?

    • G1有一个参数,是“-XX:InitiatingHeapOccupancyPercent”,他的默认值是45%

    • “-XX:G1MixedGCCountTarget”参数,就是在一次混合回收的过程中,最后一个阶段执行几次混合

回收,默认值是8次
意味着最后一个阶段,先停止系统运行,混合回收一些Region,再恢复系统运行,接着再次禁止系统运行,混合回收一些Region,反
复8次因为你停止系统一会儿,回收掉一些Region,再让系统运行一会儿,然后再次停止系统一会儿,再次回收掉一些Region,这样可以尽可能让系统不要停顿时间过长,可以在多次回收的间隙,也运行一下。

* “-XX:G1HeapWastePercent”,默认值是5%

在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他
Region,然后这个Region中的垃圾对象全部清理掉
* “-XX:G1MixedGCLiveThresholdPercent”,他的默认值是85%,意思就是确定要回收的Region的时候,必须是存
活对象低于85%的Region才可以进行回收

G1 回收过程

  • 初始标记
    首先会触发一个“初始标记”的操作,这个过程是需要进入“Stop the World”的,仅仅只是标记一下GC Roots直接能引用的对象,
    这个过程速度是很快

  • 并发标记
    允许系统程序的运行,同时进行GC Roots追踪,从GC Roots开始追踪所有的存活对象,

  • 最终标记
    会进入“Stop the World”,系统程序是禁止运行的,但是会根据并发标记 阶段记录的
    那些对象修改,最终标记一下有哪些存活对象,有哪些是垃圾对象

  • 混合回收
    “混合回收“阶段,这个阶段会计算老年代中每个Region中的存活对象数量,存活对象的占比,还有执行垃圾回
    收的预期性能和效率。会停止系统程序,然后全力以赴尽快进行垃圾回收,此时会选择部分Region进行回收,因为必须让垃圾回收的停顿时间控制在我们指定的范围内。
    因为我们设定了对GC停顿时间的目标,所以说他会从新生代、老年代、大对象里各自挑选一些Region,保证用指定的时间(比如200ms)回收尽可能多的垃圾,这就是所谓的混合回收,

  • 如果在进行Mixed回收的时候,无论是年轻代还是老年代都基于复制算法进行回收,都要把各个Region的存活对象拷贝到别的Region里去此时万一出现拷贝的过程中发现没有空闲Region可以承载自己的存活对象了,就会触发 一次失败。
    一旦失败,立马就会切换为停止系统程序,然后采用单线程进行标记、清理和压缩整理,空闲出来一批Region,这个过程是极慢极慢
    的。

参数设置

1
2
3
4
5
“-Xms4096M -Xmx4096M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseG1GC
“-XX:G1NewSizePercent”参数是用来设置新生代初始占比的,不用设置,维持默认值为5%即可。
“-XX:G1MaxNewSizePercent”参数是用来设置新生代最大占比的,也不用设置,维持默认值为60%即可。
就是“* -XX:MaxGCPauseMills”,他的默认值是200
毫秒
  • 合理设置-XX:MaxGCPauseMills
  • 是mixed gc的优化了
    里核心还是在于调节“-XX:MaxGCPauseMills”这个参数的值,在保证他的新生代gc别太频繁的同时,还得考
    虑每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc。

基于JVM运行的系统最怕什么?