文章来源:微信阅读--Offer来了
Java 的类加载过程可以分为 5 个阶段:载入、验证、准备、解析和初始化。
1)Loading(载入)
JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class
文件、也可能是 jar 包,甚至网络)转化为二进制
字节流加载到内存中,并在堆中创建
生成一个代表该类的
java.lang.class 对象。
2)Verification(验证)
JVM 会在该阶段对二进制
字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。
- 文件格式的验证:确保二进制字节流格式符合class文件的格式规范。
- 元数据验证:对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。
- 字节码验证:通过数据流和控制流分析,确定程序语义是合法的对数据类型做出验证,
- 符号引用验证:
3)Prep
aration(准备)
JVM 会在该阶段对
类变量(也称为静态变量,static 关键字修饰的)分配内存并初始化(对应数据类型的
默认初始值,如 0、0L、null、false 等)。
4)Resolution(解析)
该阶段将常量池中的符号引用转化为直接引用。比如 A类中的a
方法引用了B类中的b
方法,那么它会找到B类的b
方法的内存地址,将符号引用替换为直接引用(内存地址)。
5)Initialization(初始化)
该阶段是类加载过程的最后一步。在准备阶段,
类变量已经被赋过
默认初始值,而在初始化阶段,
类变量将被赋值为
代码期望赋的值。换句话说,初始化阶段是执行类构造器
方法的过程。
使用Class.forName加载指定的类时,可以通过initialize参数设置是否需要对类进行初始化
子类初始化过程:
1)
父类的静态
属性
2)
父类的静态
代码块
3)子类的静态
属性
4)子类的静态
代码块
5)
父类的非静态
属性
6)
父类的非静态
代码块
7)
父类构造
方法
8)子类非静态
属性
9)子类非静态
代码块
10)子类构造
方法
Java 类加载器可以分为三种。
1)启动类加载器(Bootstrap Class-Loader),加载Java_HOME/lib 包下面的 jar
文件,比如说常见的 rt.jar。
2)扩展类加载器(Extension or Ext Class-Loader),加载Java_HOME/lib/ext 包下面的 jar
文件。
3)应用类加载器(Application or App Clas-Loader),根据程序的类路径(cla
sspath)来加载 Java 类。
4)
自定义加载器(User ClassLoader):继承
java.lang.classLoader
双亲委派模型:双亲委派机制指
一个类在收到类加载请求后不会尝试自己加载这个类,而是把该类加载请求向上委派给其
父类去完成,一直到最顶层的类加载器;如果上层加载器无法完成类的加载工作时,当前类加载器才会尝试自己去加载这个类。核心是保障类的唯一性和安全性
跨平台原理
Java源
文件(.java
文件) ----编译----> 通用的Java字节码(.class
文件) ----JVM翻译---->平台相关的机器码 ---
调用本地
方法库---> 执行相应
方法
每种操作系统的解释器都是不同的,但基于解释器实现的虚拟机是相同的,虚拟机识别平台并将字节码转换成机器码,这也是Java能够跨平台的原因。
JVM内存模型
1. 线程私有:
- 栈(Stack): 基本数据类型变量,对象的引用,局部变量
- 程序计数器:指令地址,线程启动时创造一个PC寄存器
- 本地方法区:保存本地(native)方法的地址
2. 线程共有:
- 堆(Heap,是完全二叉树):类的对象,new开辟,GC回收。
逻辑上分成新生代(1/3)+老年代(2/3)+永久代(极少区域),
- 方法区(Method Area):类信息,静态变量,常量,成员方法、包含运行时常量池和字符串常量池,
3. 直接内存(堆外内存):
JDK的NIO模块提供的基于Channel与Buffer的
I/O操作方式
在高并发应用场景下被广泛使用(Netty、Flink、HBase、Hadoop都有用到堆外内存)
GC的内存区域
堆+
方法区
JVM用Java堆的永久代来
实现方法区,便于
垃圾收集器用管理堆的方式管理
方法区的回收
GC过程
新生代的Mi
norGC:
新生代 = Eden (8/10) + SurvivorFrom(1/10) + SurvivorTo(1/10)
Eden区:Java新创建的对象首先会被存放在Eden区,大对象则直接将其分配到老年代。在Eden区的内存空间不足时会触发Mi
norGC,对新生代进行一次
垃圾回收。
ServivorTo区:保留上一次Mi
norGC时的幸存者。
ServivorFrom区:复制算法的过渡区,将上一次Mi
norGC时的幸存者作为这一次Mi
norGC的被扫描者
新生代的GC过程叫作Mi
norGC,采用复制算法实现,具体过程如下。
(1)把在Eden区和ServivorFrom区中存活的对象复制到ServivorTo区。如果某对象的年龄达到老年代的标准(对象晋升老年代的标准由XX:MaxTenuringThreshold设置,
默认为15),则将其复制到老年代,同时把这些对象的年龄加1;如果ServivorTo区的内存空间不够,则也直接将其复制到老年代;如果对象属于大对象(大小为2KB~128KB的对象属于大对象,例如通过XX:PretenureSizeThreshold=2097152设置大对象为2MB,1024×1024×2Byte=2097152Byte=2MB),则也直接将其复制到老年代。
(2)清空Eden区和ServivorFrom区中的对象。
(3)将ServivorTo区和ServivorFrom区互换,原来的ServivorTo区成为下一次GC时的ServivorFrom区。
老年代的MajorGC:
主要存放有长生命周期(年龄大于15)的对象和大对象
JVM会进行一次Mi
norGC,再进行MajorGC,MajorGC
标记清除算法容易产生内存碎片。在老年代没有内存空间可分配时,会抛出Out OfMemory异常。
永久代:
内存的永久保存区域,主要存放Class和
Meta(元数据)的信息。GC不会在程序运行期间对永久代的内存进行清理,会因加载的Class
文件过多时会抛出Out Of Memory异常。
Java 8中永久代已经被元数据区(也叫作元空间)取代。元空间直接使用操作系统的本地内存,不再受JVM的最大可用内存(MaxPermSize)空间决定,由操作系统的实际可用内存空间决定。
GC的对象判定
(判断一个对象是否存活,是否该回收)
1. 引用计数:每个对象有
一个引用计数
属性,计数为0时可以回收。无法
解决对象相互循环引用问题
2. 可达性分析:从GC Roots开始向下
搜索,
搜索所走过的路径称为引用链。当
一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象要经过至少两次
标记才能判定其是否可以被回收。
GC算法
1、
标记-清除算法(Mark-Sweep):为每个对象存储
一个标记位,在
标记阶段,计数为0时记录死亡;清除阶段,该阶段对死亡的对象进行清除,。效率比较低(递归与全堆对象遍历)、碎片多。
2、
标记-压缩法(Mark-Compact):在
标记阶段,该算法也将所有对象
标记为存活和死亡两种状态;清除阶段,将所有存活的对象整理一下,放到另一处空间,然后把剩下的所有对象全部清除。减少碎片,但复制操作耗时。
3.、复制算法(
copying):将内存平均分成两部分,然后每次只使用其中的一部分,当这部分内存满的时候,将内存中所有存活的对象复制到另
一个内存中,然后将之前的内存清空,只使用这部分内存,循环下去。长时间存活的对象来
回复制耗时,适用于小对象、生存时间短的区域。
4. 分代收集(Generational Collecting):
新生代:对象生存期短,每次回收都会有大量对象死去,采用复制算法。
老年代:对象存活率较高,没有额外的空间进行分配担保,所以可以使用
标记-整理 或者
标记-清除。
内存回收算法的具体实现
1、 Serial收集器:串行单线程,新生代复制算法、老年代
标记-压缩
2、 ParNew收集器:并行版本Serial收集器
3、 P
arallel Scavenge收集器:通过自适应调节策略提高系统吞吐量,三个重要参数:控制最大
垃圾收集停顿时
间的-XX:MaxGCPauseMillis参数,控制吞吐量大小的-XX:GCTimeRatio参数和控制自适应调节策略开启与否的UseAdaptiveSizePolicy参数
4、 CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,
标记清除算法,运作过程:初始
标记,并发
标记,重新
标记,并发清除,收集结束会产生大量空间碎片。
(1)初始
标记:只
标记和GC Roots直接关联的对象,速度很快,需要暂停所有工作线程。
(2)并发
标记:和
用户线程一起工作,执行GC Roots跟踪
标记过程,不需要暂停工作线程。
(3)重新
标记:在并发
标记过程中
用户线程继续运行,导致在
垃圾回收过程中部分对象的状态发生变化,为了确保这部分对象的状态正确性,需要对其重新
标记并暂停工作线程。
(4)并发清除:和
用户线程一起工作,执行清除GC Roots不可达对象的任务,不需要暂停工作线程。
5、 G1收集器: 前沿成果,
标记整理算法实现,运作流程主要
包括以下:初始
标记,并发
标记,最终
标记,筛选
标记。不会产生空间碎片,可以精确地控制停顿。
CMS收集器使用“
标记-清除”算法,是老年代的收集器,以最小的停顿时间为目标的收集器。
G1收集器使用的是“
标记-整理”算法,范围是老年代和新生代,进行了空间整合,降低了内存空间碎片。
对象的引用类型
一切皆对象,对象的操作是通过该对象的引用(Reference)实现的,
Java中的引用类型有4种,分别为强引用、软引用、
弱引用和虚引用
(1)强引用:最常见。在把
一个对象赋给
一个引用变量时,这个引用变量就是
一个强引用。有强引用的对象一定为可达性状态,所以不会被
垃圾回收机制回收。因此,强引用是造成Java内存泄漏(Memory Link)的主要原因。
(2)软引用:软引用通过SoftReference类实现。如果
一个对象只有软引用,则在系统内存空间不足时该对象将被回收。
(3)
弱引用:
弱引用通过WeakReference类实现,如果
一个对象只有
弱引用,则在
垃圾回收过程中一定会被回收。
(4)虚引用:虚引用通过Phant
omreference类实现,虚引用和引用队列联合使用,主要用于跟踪对象的
垃圾回收状态。