类加载过程
Loading
类加载过程
双亲委派
-
ExtClassLoader和AppClassLoader中的loadClass()方法都是调用的super.loadClass();即调用的ClassLoader中的loadClass()方法。
-
ExtClassLoader和AppClassLoader都是Launcher的内部类,都继承于URLClassLoader => SecureClassLoader => ClassLoader。
-
自定义类加载器只需要继承ClassLoader,重写findClass()方法,调用父类defineClass将字节流转换为class对象。
-
双亲委派规则是出于安全考虑,如需打破双亲委派,重写loadClass()方法即可。
ClassLoader
/**
* Loads the class with the specified <a href="#name">binary name</a>. The
* default implementation of this method searches for classes in the
* following order:
*
* <ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
* on the parent class loader. If the parent is <tt>null</tt> the class
* loader built-in to the virtual machine is used, instead. </p></li>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <p> If the class was found using the above steps, and the
* <tt>resolve</tt> flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
*
* <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 先查一下缓存中有没有加载过该Class
Class<?> c = findLoadedClass(name);
// 缓存中没有则调用父加载器进行加载
if (c == null) {
try {
if (parent != null) {
// 调用父加载器进行加载,App=>Ext=>Bootstrap
c = parent.loadClass(name, false);
} else {
// 调用Bootstrap加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
// 在父加载器无法加载的时候
// 调用子类findClass去加载,Bootstrap=>Ext=>App
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
Launcher
public Launcher() {
Launcher.ExtClassLoader var1;
try {
// 初始化ExtClassLoader
// 使用双重检查单例初始化ExtClassLoader,父类为NULL即BootstrapClassLoader
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 初始化AppClassLoader
// 指定了AppClassLoader的父加载器为ExtClassLoader,即var1=ExtClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
...
// ExtClassLoader
static class ExtClassLoader extends URLClassLoader {
private static volatile Launcher.ExtClassLoader instance;
// 单例模式-双重检查
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
if (instance == null) {
Class var0 = Launcher.ExtClassLoader.class;
synchronized(Launcher.ExtClassLoader.class) {
if (instance == null) {
instance = createExtClassLoader();
}
}
}
return instance;
}
// 初始化ExtClassLoader
private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
// ExtClassLoader加载的路径地址
File[] var1 = Launcher.ExtClassLoader.getExtDirs();
...
// 初始化操作
return new Launcher.ExtClassLoader(var1);
}
});
} catch (PrivilegedActionException var1) {
throw (IOException)var1.getException();
}
}
// ExtClassLoader加载的路径地址
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
...
}
// 调用父类构造方法进行初始化,传参指定父加载器,这里Ext指定的null
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
...
}
}
// AppClassLoader
static class AppClassLoader extends URLClassLoader {
...
// 初始化AppClassLoader,var0=ExtClassLoader
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
// AppClassLoader加载的路径地址
final String var1 = System.getProperty("java.class.path");
...
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
...
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
// 调用父类构造方法进行初始化,传参指定父加载器,这里App指定的Ext
// 即var2=ExtClassLoader
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
...
}
}
}
Linking
Verification
验证文件是否符合JVM规定
Preparation
静态成员变量赋默认值(系统初始化的值)
Resolution
将类、方法、属性等符号引用解析为直接引用
Initializing
开始执行字节码,调用类初始化代码
JMM内存模型
JMM(Java Memory Model)
,是Java虚拟机规范中所定义的一种内存模型。在此内存模型中,规定了,所有的共享变量都是存储于主内存中,每个线程都是将主内存中的共享变量拷贝一份副本,到本线程的本地内存中,然后操作此共享变量副本,修改后,再同步更新到主内存中,因此高并发下就会出现变量修改的问题了。
总结:JMM内存模型是一种规范,用来保证并发编程中共享内存的原子性,可见性,有序性。
JMM内存模型定义了以下八种操作来完成工作内存与主内存之间的信息同步:
-
lock
作用于主内存的变量,它把一个变量标识为一条线程独占的状态
-
unlock
作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
-
read
作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
-
load
作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
-
use
作用于工作内存的变量,它把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
-
assign
作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
-
store
作用于工作内存的变量,它把工作内存中的一个变量的值传送到主内存中,以便随后的write操作使用
-
write
作用于主内存的变量,它把store操作从工作内存得到的变量的值放入主内存的变量中
JVM内存模型
JVM(Java Virtual Machine)
,是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
总结:JVM内存模型是一种规范,屏蔽了与具体操作系统平台相关的信息,使字节码可以运行在不同的平台。
JVM运行时数据区的划分:
-
程序计数器
线程私有
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
-
虚拟机栈
线程私有
Java虚拟机栈(Java Virtual Machine Stack)描述的是Java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、返回地址等信息。每一个方法从调用直至执行完成的过程,都对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
-
本地方法栈
线程私有
本地方法栈(Native Method Stack) 与虚拟机栈所发挥的作用非常相似,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
-
方法区
线程共享
方法区(Method Area)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量(Runtime Constant Pool)是方法区的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
-
堆
线程共享
Java堆(Java Heap)用来存放对象实例。是Java虚拟机所管理的内存中的最大的一块。
Garbage Collection
定位垃圾
-
引用计数(ReferenceCount)
记录对象是否被其他对象引用,当对象没有被其他对象引用就说明这个对象已经可以作为垃圾进行回收了。问题是无法解决循环引用。
-
可达性分析算法(RootSearching)
通过一系列“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象不一定会成为可回收对象。进入DEAD状态的线程还可以恢复,GC不会回收它的内存。
垃圾回收算法
-
标记清除算法(Mark-Sweep)
标记清除算法分为两个阶段,标记阶段和清除阶段。标记阶段任务是标记出所有需要回收的对象,清除阶段就是清除被标记对象的空间。
优点:实现简单
缺点:位置不连续,容易产生碎片,效率偏低(两遍扫描)
-
复制清除算法(Copying)
将可用内存划分为大小相等的两块,每次只使用其中的一块。当进行垃圾回收的时候了,把其中存活对象全部复制到另外一块中,然后把已使用的内存空间一次清空掉。
优点:没有碎片
缺点:浪费空间
-
标记整理算法(Mark-Compact)
先标记存活对象,然后把存活对象向一边移动,然后清理掉端边界以外的内存。
优点:没有碎片
缺点:效率偏低(两遍扫描,指针需要调整)
并发标记算法
-
三色标记
白色:未被标记的对象
灰色:自身被标记,成员变量未被标记
黑色:自身和成员变量均已标记完成
未被标记的对象会被垃圾回收器清理
漏标问题:
只有两者同时出现时,漏标问题才会产生
-
Remark过程中,黑色指向了白色,如果不对黑色重新扫描,则会漏标,会把白色D对象当做没有新引用指向从而被回收掉。
-
并发标记过程中,Mutator删除了所有从灰色到白色的引用,会产生漏标,此时白色对象应该被回收。
解决漏标问题的核心算法:
-
Incremental Update
CMS用到,关注引用的增加,把黑色重新标记为灰色,下次重新扫描属性。
-
Snapshot At The Beginning(SATB)
G1用到,关注引用的删除,当灰色->白色消失时,要把这个引用push到GC的堆栈,保证D还能被GC扫描到(由于有RSet存在,不需要扫描整个堆去查找指向白色的引用,效率比较高,SATB配合RSet使用,浑然天成)。
-
-
颜色指针
ZGC使用
垃圾回收器
常用搭配规则:
-
Serial + Serial Old
-
Parallel Scavenge + Parallel Old (jdk1.8 默认)
-
ParNew + CMS
Serial / Serial Old
年轻代:Serial 串行回收 复制清除算法
老年代:Serial Old 串行回收 标记整理算法
Parallel Scavenge / Parallel Old
年轻代:Parallel Scavenge 并行回收 复制清除算法
老年代:Parallel Old 并行回收 标记整理算法
ParNew / CMS
STW:大概意思就是在安全点将工作线程停住,开始回收垃圾(会影响用户体验)
年轻代:ParNew对Parallel Scavenge进行了增强,配合CMS的并行回收 复制清除算法
老年代:CMS并发回收(无法忍受SWT)标记清除算法
语义的区别:
-
PS/PO是用户线程停住,多个GC线程并行回收的概念
-
CMS是用户线程不用停,跟GC线程并发运行的概念
标记过程:
-
初始标记:标记根对象
-
并发标记:边产生垃圾边清理(最浪费时间)
-
重新标记:标记并发标记过程中产生的新垃圾或者取消又被引用的垃圾
-
并发清理:并发清理垃圾,并发清理过程中产生的垃圾叫做浮动垃圾,等下次GC清理
缺点:
-
因为采用标记清除算法,会产生很多碎片
解决办法:对内存空间进行压缩
多少次FGC之后进行压缩
-XX:CMSFullGCsBeforeCompaction
-
会有浮动垃圾
对于内存很大的情况,当内存已经满了的时候,新对象无法分配内存,这个时候CMS会使用Serial Old去清理垃圾,严重影响效率。
解决办法:降低触发CMS的阈值
使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
-XX:CMSInitiatingOccupancyFraction
G1
逻辑上分代(Eden区,Survivor区,Old区,Humongous区-大对象区),物理不分代
YGC
发生在年轻代的GC算法,一般对象(除了大对象)都是在Eden Region中分配内存,当所有Eden Region被耗尽无法申请内存时,就会触发一次YGC,这种触发机制和之前的YGC差不多,执行完一次YGC,活跃对象会被拷贝到Survivor Region或者晋升到Old Region中,空闲的Region会被放入空闲列表中,等待下次被使用。
MixedGC
当越来越多的对象晋升到老年代Old Region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即MixedGC,除了回收整个Eden Region,还会回收一部分的Old Region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些Old Region进行收集,从而可以对垃圾回收的耗时时间进行控制。
MixedGC触发阈值参数,默认45%,当老年代大小占整个堆大小百分比达到该阈值时,会触发一次MixedGc
-XX:InitiatingHeapOccupancyPercent
MixedGC的执行过程有点类似CMS,主要分为以下几个步骤:
-
Initial Mark:初始标记过程,整个过程STW,标记了从GC Root可达的对象。
-
Concurrent Marking: 并发标记过程,整个过程GC Collector线程与工作线程可以并行执行,标记出GC Root可达对象衍生出去的存活对象,并收集各个Region的存活对象信息。
-
Remark: 重新标记过程,整个过程STW,标记出那些在并发标记过程中遗漏的,或者内部引用发生变化的对象。
-
Clean Up: 垃圾清除过程,将存活的对象复制到另外一个Region区域,复制同时进行了压缩,碎片没有CMS多。(标记整理算法)
FGC
如果对象内存分配速度过快,Mixed GC来不及回收,导致老年代被填满,就会触发一次FGC,G1的FGC算法就是单线程执行的Serial Old ,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免FGC。
补充知识:
CSet:
Collection Set:一组可被回收的分区的集合。
在CSet中存活的数据会在GC过程中被移动到另一个可用分区,CSet中的分区可以来自Eden区,Survivor区,Old区。CSet会占用不到整个堆空间的1%的大小。
RSet:
Remembered Set:记录了其他Region中的对象到本Region的引用。
RSet的价值在于使得垃圾回收器不需要扫描整个堆找到谁引用了当前分区中的对象,只需要扫描RSet即可。
Garbage Collection参数
GC常用参数
- -Xmn -Xms -Xmx -Xss (年轻代 最小堆 最大堆 栈空间-指设定每个线程的堆栈大小)
- -XX:+PrintCommandLineFlags (查看使用的GC配置参数)
- -XX:+UseTLAB (使用TLAB,默认打开)
- -XX:+PrintTLAB (打印TLAB的使用情况)
- -XX:TLABSize (设置TLAB大小)
- -XX:+DisableExplictGC (System.gc()不管用 ,FGC)
- -XX:+PrintGC (打印GC日志)
- -XX:+PrintGCDetails (打印GC详细日志)
- -XX:+PrintHeapAtGC
- -XX:+PrintGCTimeStamps
- -XX:+PrintGCApplicationConcurrentTime (打印应用程序时间)
- -XX:+PrintGCApplicationStoppedTime (打印暂停时长)
- -XX:+PrintReferenceGC (记录回收了多少种不同引用类型的引用)
- -XX:+PrintVMOptions
- -XX:+HeapDumpOnOutOfMemoryError (设置OOM时生成堆转储文件)
- -XX:HeapDumpPath=/tmp/heapdump.hprof (指定堆转储文件时的路径或文件名)
- -XX:+PrintFlagsFinal | grep XXX (查看所有(或指定)JVM参数启动的初始值)
- -XX:MaxTenuringThreshold (升代年龄,最大值15)
- -verbose:class (类加载详细过程)
- -Xloggc:/opt/log/gc.log (记录GC日志)
Parallel常用参数
- -XX:SurvivorRatio
- -XX:PreTenureSizeThreshold(大对象到底多大)
- -XX:MaxTenuringThreshold
- -XX:+ParallelGCThreads (并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同)
- -XX:+UseAdaptiveSizePolicy (自动选择各区大小比例)
CMS常用参数
- -XX:+UseConcMarkSweepGC
- -XX:ParallelCMSThreads (CMS线程数量)
- -XX:CMSInitiatingOccupancyFraction (使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收))
- -XX:+UseCMSCompactAtFullCollection (在FGC时进行压缩)
- -XX:CMSFullGCsBeforeCompaction (多少次FGC之后进行压缩)
- -XX:+CMSClassUnloadingEnabled
- -XX:CMSInitiatingPermOccupancyFraction (达到什么比例时进行Perm回收GCTimeRatio 设置GC时间占用程序运行时间的百分比)
- -XX:MaxGCPauseMillis (停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代)
G1常用参数
- -XX:+UseG1GC
- -XX:MaxGCPauseMillis (建议值,G1会尝试调整Young区的块数来达到这个值)
- -XX:GCPauseIntervalMillis (GC的间隔时间)
- -XX:G1HeapRegionSize (分区大小,建议逐渐增大该值,1 2 4 8 16 32 随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 )
- -XX:G1NewSizePercent (新生代最小比例,默认为5%)
- -XX:G1MaxNewSizePercent (新生代最大比例,默认为60%)
- -XX:GCTimeRatio (GC时间建议比例,G1会根据这个值调整堆空间)
- -XX:ConcGCThreads (线程数量)
- -XX:InitiatingHeapOccupancyPercent (启动G1的堆空间占用比例 默认45%)