在JVM深入虚拟机系列2中学习了一些常见的JVM配置参数以及一些GC算法,对于GC可以通过设置不同的参数来选择使用不同的收集器.
一. GC参数
二.类装载器
1.串行收集器
-XX:+UseSerialGC
新生代、老年代使用串行回收
新生代复制算法
老年代标记-压缩
- 最古老,最稳定
- 效率高
- 可能会产生较长的停顿
2.并行收集器
-
ParNew
-XX:+UseParNewGC
新生代并行
老年代串行
- Serial收集器新生代的并行版本
- 复制算法
- 多线程,需要多核支持
- -XX:ParallelGCThreads 限制线程数量
2. Parallel收集器
-
类似ParNew
-
新生代复制算法
-
老年代 标记-压缩
-
更加关注吞吐量
-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行 -XX:+UseParallelOldGC 使用Parallel收集器+ 并行老年代
-XX:MaxGCPauseMills
-
最大停顿时间,单位毫秒
-
GC尽力保证回收时间不超过设定值
-XX:GCTimeRatio
-
0-100的取值范围
-
垃圾收集时间占总时间的比
-
默认99,即最大允许1%时间做GC
这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优
3.CMS收集器
CMS运行过程比较复杂,着重实现了标记的过程,可分为
- 初始标记
根可以直接关联到的对象
速度快 - 并发标记(和用户线程一起)
主要标记过程,标记全部对象 - 重新标记
由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正 - 并发清除(和用户线程一起)
基于标记结果,直接清理对象
特点
-
尽可能降低停顿
-
会影响系统整体吞吐量和性能
比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半 -
清理不彻底
因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理 -
因为和用户线程一起运行,不能在空间快满时再清理
-XX:CMSInitiatingOccupancyFraction设置触发GC的阈值
如果不幸内存预留空间不够,就会引起concurrent mode failure -
使用串行收集器作为后备
-
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次整理
整理过程是独占的,会引起停顿时间变长 -
-XX:+CMSFullGCsBeforeCompaction
设置进行几次Full GC后,进行一次碎片整理 -
-XX:ParallelCMSThreads
设定CMS的线程数量
GC参数整理
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:SurvivorRatio:设置eden区大小和survivior区大小的比例
-XX:NewRatio:新生代和老年代的比
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParallelGC :新生代使用并行回收收集器
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:ParallelGCThreads:设置用于垃圾回收的线程数
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:设定CMS的线程数量
-XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发
-XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
-XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收
-XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收
4.Tomcat实例演示
二
1.class装载验证流程
- 加载
- 链接
1. 验证
2. 准备
3. 解析
- 初始化
加载
装载类的第一个阶段
取得类的二进制流
转为方法区数据结构
在Java堆中生成对应的java.lang.Class对象
链接 -> 验证
- 目的:保证Class流的格式是正确的
- 文件格式的验证
- 是否以0xCAFEBABE开头
- 版本号是否合理
- 元数据验证
- 是否有父类
- 继承了final类?
- 非抽象类实现了所有的抽象方法
- 字节码验证 (很复杂,通过字节码检查不一定没有问题,没有通过字节码检查一定有问题)
- 运行检查
- 栈数据类型和操作码数据参数吻合
- 跳转指令指定到合理的位置
- 符号引用验证
- 常量池中描述类是否存在
- 访问的方法或字段是否存在且有足够的权限
- 文件格式的验证
链接 -> 准备
- 分配内存,并为类设置初始值 (方法区中)
- public static int v=1;
- 在准备阶段中,v会被设置为0
- 在初始化的中才会被设置为1
- 对于static final类型,在准备阶段就会被赋上正确的值
- public static final int v=1;
链接 -> 解析
- 符号引用(字符串引用对象不一定被加载)替换为直接引用(指针或者地址偏移量引用对象一定在内存)
初始化
- 执行类构造器
static变量 赋值语句
static{}语句 - 子类的调用前保证父类的被调用
- 是线程安全的
2.什么是类装载器ClassLoader
- ClassLoader是一个抽象类
- ClassLoader的实例将读入Java字节码将类装载到JVM中
- ClassLoader可以定制,满足不同的字节码流获取方式
- ClassLoader负责类装载过程中的加载阶段
3.JDK中ClassLoader默认设计模式ClassLoader
ClassLoader的重要方法
- public Class<?> loadClass(String name) throws ClassNotFoundException
载入并返回一个Class - protected final Class<?> defineClass(byte[] b, int off, int len)
定义一个类,不公开调用 - protected Class<?> findClass(String name) throws ClassNotFoundException
loadClass回调该方法,自定义ClassLoader的推荐做法 - protected final Class<?> findLoadedClass(String name)
寻找已经加载的类
分类
-
BootStrap ClassLoader (启动ClassLoader)
-
Extension ClassLoader (扩展ClassLoader)
-
App ClassLoader (应用ClassLoader/系统ClassLoader)
-
Custom ClassLoader(自定义ClassLoader)
-
每个ClassLoader都有一个Parent作为父亲
协同工作
loadClass方法的实现
在尝试加载一个类的时候,先去找这个类是否已经被加载,如果被加载了就返回这个类,如果找不到 ,就去请求父类去加载.
- 直接运行以上代码:
- I am in apploader
- 加上参数 -Xbootclasspath/a:D:/tmp/clz
- I am in bootloader
- 此时AppLoader中不会加载HelloLoader
- I am in apploader 在classpath中却没有加载
- 说明类加载是从上往下的
4.打破常规模式
问题:
解决:
Thread. setContextClassLoader()
- 上下文加载器
- 是一个角色
- 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
- 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
双亲模式的破坏
- 双亲模式是默认的模式,但不是必须这么做
- Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent
- OSGi的ClassLoader形成网状结构,根据需要自由加载Class
5.热替换ClassLoader
含义:
- 当一个class被替换后,系统无需重启,替换的类立即生效
例子:
geym.jvm.ch6.hot.CVersionA
public class CVersionA {
public void sayHello() {
System.out.println("hello world! (version A)");
}
}
- DoopRun 不停调用CVersionA . sayHello()方法,因此有输出:
- hello world! (version A)
- 在DoopRun 的运行过程中,替换CVersionA 为:
public class CVersionA {
public void sayHello() {
System.out.println("hello world! (version B)");
}
}
- 替换后, DoopRun 的输出变为
- hello world! (version B)