Java编译器做的事情很简单,其实就是就是将Java的源文件转换为字节码文件。
1. 源文件存储的是高级语言的命令,JVM只认识"机器码";
2. 因此将源文件转换为字节码文件,即是JVM看得懂的"机器码"文件。
当程序需要用到某个类时,就需要加载对应的 .class 文件,然后在虚拟机中创建对应的class对象,这一个过程就是类加载器做的事情。
类的加载过程包括以下五个步骤:
<1> 加载:通过类的全限定名查找对应的字节码文件,并通过该字节码文件生成对应的类对象;
<2> 验证:验证类文件的二进制字节码文件是否符合虚拟机的规范,或者说有损虚拟机的运行,包括文件格式验证,元数据验证,字节码验证,符号引用验证;
<3> 准备:对一些类变量赋初始值(例如,static int a;=> static int a = 0);
<4> 解析:虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量(直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄);
<5> 初始化:对所有变量进行赋值,包括类变量和成员变量。
细心阅读你会发现:为什么第<2>步验证是验证二进制字节码文件,那为什么不验证后在通过<1>将其字节码文件生成对象呢?
-------------------------------------------------------------------------------------------------------------
其实它们并不是串行的,在<1>将字节码文件加载到内存的时候,此时就会启动<2>对其二进制字节码文件进行验证了!
JVM提供了三种类加载器:
<1> Bootstrap ClassLoader,启动类加载器:加载
<2> Extension ClassLoader,拓展类加载器:加载
<3> Application ClassLoader,引用类加载器:加载 java.class.path 目录下的类文件;
三者配合使用(当然JVM还支持自定义类加载器,为了方便描述,这里就不谈它了),配置机制就是总所周知的双亲委派机制,执行流程如下:
备注:三个类加载器不是继承的关系,但是为了方便,我称在下面的类加载器为子加载器,在上面的加载器为父加载器!
每次需要加载类的时候,类加载器不会直接去加载,而是委托其父加载器去执行,一直委托到Bootstrap ClassLoader,Bootstrap ClassLoader不存在该类才一步步往下退给子加载器。
双亲委派机制的作用:
保护核心API库的作用,假设从网络中传输一个名为 java.lang.Integer的文件过来,如果不通过父类加载器进行验证,在Application ClassLoader就进行加载,那原本的 java.Integer.Integer核心API库就完全被篡改了,这在JVM上是不允许的。
运行时数据区模块可分为五个子模块:
<1> 堆(线程共享):存放new出来的对象(少量对象逃逸分析在栈中);
<2> 方法区(线程共享):存放类的所有信息,即是创建对象所需的原材料(有点Spring创建Bean的原材料那个BeanDefinition的味道),通过反编译命令可以看看到底存的啥:
javap -v -p Order.class > text.txt
常量池其实就是一些字面量和符号引用跟真实值的一些映射,如果直接存放数据,可能内存会直接爆了,所以存个引用地址就可以啦。
<3> 虚拟机栈(线程私有):每一个线程都有独享一个虚拟机栈,它的生命周期与线程相同。每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
<4> 本地方法栈(线程私有):本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
<5> 程序计数器(线程私有):它的作用就是记录当前线程所执行的位置。 这样,当线程重新获得CPU的执行权的时候,就直接从记录的位置开始执行,分支、循环、跳转、异常处理也都依赖这个程序计数器来完成。
垃圾收集器,主要是对不再使用的对象进行回收,而这些不再使用的对象就被JVM成为垃圾,那怎么判断一个对象是否为垃圾呢?
垃圾:GC-Roots所不能到达的对象则称为垃圾。
可作为GC-Root的对象:
1. Java虚拟机栈中被引用的对象,各个线程调用的参数、局部变量、临时变量等;
2. 方法区中类静态属性引用的对象,比如引用类型的静态变量;
3. 方法区中常量引用的对象;
4. 本地方法栈中所引用的对象;
5. Java虚拟机内部的引用,基本数据类型对应的Class对象,一些常驻的异常对象;
6. 被同步锁(synchronized)持有的对象。
存在这三种垃圾回收算法,分别是:标记-清除、标记-复制、标记-整理。
规则:标记垃圾对象,然后清除。
优点:实现简单
缺点:存在内存碎片(碎片:不够创建其他对象的空间,这块空间则称为碎片空间)
规则:分为两块空间,每次只使用一块空间,然后将存活的对象移动另一块空间。
优点:避免内存碎片
缺点:需要多一块空间,则表明浪费了一块空间
规则:标记垃圾对象,然后清除,最后整理以下存活对象的空间位置
优点:避免了内存碎片
缺点:整理对象需要损耗大量的时间
垃圾收集器其实就是垃圾算法的一些工程落地实现。
Serial收集器作用于新生代,采用标记-复制算法,使用单核处理器进行垃圾回收,没有过多线程之间的交互,在单核处理器下表现出很好的性能。
ParNew收集器作用于新生代,采用标记-复制算法,除了GC过程采用多条线程进行垃圾回收之外,其他的与Serial收集器一样。
Parallel Scavenge收集器也是一款新生代收集器,基于标记——复制算法实现,能够并行收集的多线程收集器和 ParNew 非常相似。
Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。如果虚拟机完成某个任务,用户代码加上垃圾收集总共耗费了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
Parallel Scavenge 收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数和直接设置吞吐量大小的-XX:GCTimeRatio参数。
Serial Old与Serial一样的垃圾回收原理,但是采用标记-整理算法,作用于老年代。
Parallel Old与ParNew一样的垃圾回收原理,但是采用标记-整理算法,作用于老年代。
CMS,标记清除算法,作用于老年代。
GC过程主要分为以下四个流程:
<1> 初始标记:标记GC-Root能直接关联的对象,该过程需要发生Stop The World
<2> 并发标记:该过程用户线程与GC线程并发执行,补充GC-Root完整链路
<3> 重新标记:因为并发标记阶段用户线程也在执行,所以可能会出现漏标、错标情况,因此在该阶段发生Stop The World,解决漏标、错标问题
<4> 并发清除:清除垃圾对象
优点:减少了Stop The World的时间
缺点:会产生浮动垃圾,同时标记-清除算法,会存在内存碎片
G1收集器采用“标记-复制”和“标记-整理”。从整体上看是基于“标记-整理”,从局部看,两个region之间是“标记-复制”。
G1收集器的出现,使得JVM不再区分新生代和老年代,统一处理,将堆划分为均等分的Region。
GC过程主要分为以下四个流程:
<1> 初始标记:标记GC-Root能直接关联的对象,该过程需要发生Stop The World
<2> 并发标记:该过程用户线程与GC线程并发执行,补充GC-Root完整链路
<3> 最终标记:因为并发标记阶段用户线程也在执行,所以可能会出现漏标、错标情况,因此在该阶段发生Stop The World,解决漏标、错标问题
<4> 筛选回收:在给定的GC时间内,选择最优价值的垃圾进行回收。
三色标记法:
三色标记法,将对象分为黑、灰、白色。
黑色节点表示经过GC-Root遍历过的节点,灰色节点表示下一次遍历的节点,白色节点表示GC-Root到不了的节点。 等待标记完毕,剩下黑色、白色两种。黑色是使用中的对象,白色为垃圾对象,因此对其白色进行回收即可。
针对新生代的GC,当新生代的垃圾达到回收条件,会触发Young GC。
针对整个堆和方法区的GC,当老年代的垃圾达到回收条件,会触发Full GC。
关于CMS与G1的对比,以及为什么能一边GC一边执行用户线程(三色标记法),Ysming88博主这篇文章讲得挺好的;同时垃圾收集器部分,参考了楼仔文章的解释。
总结:
* 新生代用“复制算法”,老年代基本用“标记-整理”算法(有的也用“标记-清除”算法),(新生代因为有surive区域,所以肯定使用的“复制算法”,老年代不可能划分成2个区域,所以肯定不会使用“复制算法”);
* 单线程垃圾回收器:Serial、Serial Old;
* 多线程垃圾回收器:ParNew、Parallel Old、Pararrel Scavenge和G1;
* 适用新生代的垃圾回收器:Serial、ParNew、Pararrel Scavenge和G1;
* 适用老年代的垃圾回收器:Serial Old、Parallel Old和G1。