JVM
整体组成部分
类加载器
类加载过程
加载:使用IO读取字节码文件,转换并存储,为每个类创建一个Class对象,存储在方法区中
链接(验证,准备,解析)
验证:对字节码文件格式进行验证,文件是否被污染,对基本的语法格式进行验证
准备:为静态的变量进行内存分配,赋值为默认值(静态的常量在编译期间就进行初始化)
解析:将符号引用转为直接引用,将字节码中的表现形式转为内存中表现(内存地址)
初始化
类的初始化,为类中的定义静态变量进行赋值
类在什么时候会被初始化(加载)
1.在类中运行main方法
2.创建对象
3.使用类中静态常量、静态方法
4.使用反射
5.初始化它的子类
以下两种情况不会被初始化:
1.使用编译期间被赋值的静态常量
2.作为数组类型
作用
负责从硬盘/网络中加载字节码信息,加载到内存中(运行时数据区的方法区中)
启动类加载器
Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。是由c/c++编写的,与Java语言无关
扩展类加载器
ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
是由Java语言编写的
应用程序类加载器
AppClassLoader:主要负责加载程序中自己开发的类
public class ClassLosderTest {
public static void main(String[] args) {
//启动类加载器
ClassLoader a = new String("a").getClass().getClassLoader();
System.out.println(a);
ClassLoader classLoader = new ClassLosderTest().getClass().getClassLoader();
//应用程序类加载器sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(classLoader);
//扩展类加载器 sun.misc.Launcher$ExtClassLoader@1b6d3586
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
}
}
测试结果:
null
sun.misc.Launcher
A
p
p
C
l
a
s
s
L
o
a
d
e
r
@
18
b
4
a
a
c
2
s
u
n
.
m
i
s
c
.
L
a
u
n
c
h
e
r
AppClassLoader@18b4aac2 sun.misc.Launcher
AppClassLoader@18b4aac2sun.misc.LauncherExtClassLoader@1b6d3586
null
运行时数据区
程序计数器是不会产生内存溢出的
堆和方法区是线程共享的,是会出现垃圾回收的
程序计数器,本地方法栈,Java栈是线程私有的
堆,方法区,本地方法栈,Java栈是可以进行内存大小设置的
堆,方法区,本地方法栈,Java栈是会出现内存溢出的
Java栈(stack)
基本作用特征:
栈是运行单位,用来管理方法的调用运行,是用来运行Java方法的区域,可能会出现栈溢出,是线程私有的
运行原理:
是先进先出的结构,最顶部的称为当前栈帧
栈帧结构:
局部变量表(存储方法中声明的变量)
操作数栈(实际计算运行)
动态链接
方法返回地址
堆(heap)
基本作用特征
一个jvm只有一个堆,是线程共享的,堆内存的大小是可以调节的,是用来存储对象的,是内存空间最大的区域,本区域是存在垃圾回收的
堆空间的分区
新生区
(伊甸园区,幸存0区,幸存1区)
永久区
为什么要分区
针对于不同的区,可以使用不同的垃圾回收算法,频繁回收新生代,较少回收老年代
创建对象,垃圾回收的过程
1.新创建的对象,都存储在伊甸园区
2.当垃圾回收时,将伊甸园区中的垃圾对象直接销毁,将幸存下来的对象转移到幸存0区
3.当再次进行垃圾回收时,将伊甸园区的幸存对象和幸存0区中的对象转移到幸存一区,每次保证一个幸存者区为空
4.每次垃圾回收时,幸存下来的对象都会记录幸存次数,当一个对象经历15次垃圾回收仍然幸存下来就会被移动到老年区
5.老年区垃圾回收次数较少,当内存空间不够用时,才会进行回收老年区。
堆空间的配置比例
新生区和老年区内存占比默认为1:2
伊甸园区:幸存0区:幸存1区=8:1:1(默认)
分代收集思想
Minnor GC 新生区收集,经常发生
Major GC 老年区收集,较少发生
整堆收集 对整个堆以及方法区进行收集(发生情况:老年区空间不足,方法区空间不足,System.gc())
字符串常量池
在JDK7之前位于方法区,7之后位于堆空间中,因为方法区的收集是在整堆收集时发生,频率较低
方法区(method area)
静态变量,常量,类信息(构造方法,接口),运行时的常量池(类中各个元素的编号)
(static final Class 常量池)
特点
jvm启动时创建,大小可以调整,是线程共享的,会有内存溢出问题
方法区的垃圾回收
在full GC时发生,主要是回收类信息,发生的条件比较苛刻,满足以下三点即可:
1.在堆中该类的对象以及子类的对象不存在
2.该类的类加载器不存在
3.该类的Class对象不存在
程序计数器(program counter register)
每个线程都有一个程序计数器,是线程私有的,是一个非常小的内存空间,几乎可以忽略不计,是用来记录每个线程运行的指令位置,是运行时数据区唯一不会内存溢出的一块空间,运行速度最快
本地方法栈(native method stack)
每个线程私有,用来运行本地方法的区域,空间大小可以调整,可能会出现栈溢出
执行引擎
作用
将加载到内存的字节码编译/解释为不同平台的机器码
.java文件----编译—>.class文件 是在开发期间,由jdk提供的编译器(javac)进行源码编译(前端编译)
.class文件----解释/编译—>机器码 后端编译,在运行时,由执行引擎完成
解释器和编译器结合
解释器:将字节码逐行进行解释执行,效率低
编译器(JIT):将字节码编译,并且在方法区中缓存起来,执行更高效,不会立即使用编译器(将一些频繁执行的热点代码进行编译)
程序启动后,先使用解释器立即执行,省去了编译时间
程序运行一段时间后,对热点代码进行编译缓存,为了后续提高效率
本地方法接口
什么是本地方法
凡是带了native关键字的,说明Java的作用范围达不到,会去调用本地方法接口,没有方法体(例如 hashCode() new thread().start())
为什么要用本地方法
JAVA语言需要和外部的环境进行交互(例如需要访问内存硬盘其他硬件设备),直接访问操作系统的接口即可
jvm中的一些机制
双亲委派机制
内容
当需要记载一个类时,当前加载器不会先进行加载,会向上委托父类加载器进行加载,一直到启动类加载器,如果启动类加载器无法加载,就会通知子类进行加载,一直到应用程序类加载器,最终如果都没有找到,就会抛出classnotfound异常
优点
安全性较高,防止核心类库被修改,避免了类的重复加载
如何打破双亲委派机制
创建自己的类加载器继承ClassLoader类,并且重写里面的loadClass/findClass方法。
例如:Tomcat使用自己的类加载器来加载类
垃圾回收
什么样的对象是垃圾
在运行过程中,没有被任何引用指向的对象成为垃圾对象
为什么需要GC
如果不及时清理垃圾的话,可能会出现内存溢出问题,在回收时还可以将内存碎片进行整理
内存溢出和内存泄漏
内存溢出:经过垃圾回收后,还是无法存储新创建的对象,内存不够用就是内存溢出
内存泄漏:一些不用的对象但是jvm无法判定为垃圾,不能进行垃圾回收,默默占用内存这样的叫内存泄漏(IO流close jdbc close)
垃圾回收算法
标记阶段
作用: 判断对象是否是垃圾对象, 是否有引用指向对象.
相关的标记算法: :引用计数算法和可达性分析算法
引用计数算法(在现代的jvm中并没有被使用).
有个计数器来记录对象的引用数量
String s1 = new String("aaa");
String s2 = s1; //有两个引用变量指向aaa对象
s2 = null; -1
s1 = null; -1
缺点:
需要维护计数器,占用空间,频繁操作需要事件开销
无法解决循环引用问题. 多个对象之间相互引用,没有其他外部引用指向他们,计数器都不为0,不能回收,产生内存泄漏.
可达性分析算法/根搜索算法
实现思路: 从一些为根对象(GCRoots)的对象出发去查找,与根据对象直接或间接连接的对象就是存活对象,不与根对象引用链连接的对象就是垃圾对象.
GC Roots 可以是哪些元素?
在虚拟机栈中被使用的.
在方法中存储的静态成员指向的对象
作为同步锁使用的 synchronized
在虚拟机内部使用的对象
对象的 finalization 机制
当一个对象被标记为垃圾后,在真正被回收之前,会调用一次Object类中finalize(). 是否还有逻辑需要进行处理.
自己不要在程序中调用finalize(),留给垃圾回收器调用.
有了finalization机制的存在,在虚拟机中把对象状态分为3种:
1.可触及的 不是垃圾,与根对象连接的
2.可复活的 判定为垃圾了,但是还没有调用finalize(),(在finalize()中对象可能会复活)
3.不可触及的: 判定为垃圾了,finalize()也被执行过了,这种就是必须被回收的对象
垃圾回收阶段的算法
标记–复制算法:
将内存分为大小相等的两份空间, 把当前使用的空间中存活的对象 复制到另一个空间中, 将正在使用的空间中垃圾对象清除.
优点: 减少内存碎片
缺点: 如果需要复制的对象数量多,效率低.
适用场景: 存活对象少 新生代适合使用标记复制算法
标记-清除算法
清除不是真正的把垃圾对象清除掉,
将垃圾对象地址维护到一个空闲列表中,后面有新对象到来时,覆盖掉垃圾对象即可.
特点:
实现简单
效率低,回收后有碎片产生
标记-压缩算法(标记-整理)
垃圾回收器
垃圾收集器是垃圾回收的实际实现者,垃圾回收算法是方法论.
垃圾回收器分类
按照线程数量
单线程垃圾回收器
Serial
Serial old
多线程垃圾回收器
Parallel
按照工作模式分为
独占式: 垃圾回收线程执行时,其他线程暂停
并行式: 垃圾回收线程可以和用户线程同时执行
按工作的内存区间
年轻代垃圾回收器
老年代垃圾回收器
垃圾回收器性能指标
暂停时间
吞吐量
回收的速度
占用内存大小
CMS垃圾回收器
Concurrent Mark Sweep 并发标记清除
支持垃圾回收线程与用户线程并发(同时)执行
初始标记: 独占式的暂停用户线程
并发标记: 垃圾回收线程与用户线程并发(同时)执行
重新标记: 独占式的暂停用户线程
并发清除: 垃圾回收线程与用户线程并发(同时)执行 进行垃圾对象的清除
优点: 可以作到并发收集
弊端: 使用标记清除算法,会产生内存碎片, 并发执行影响到用户线程,无法处理浮动垃圾
三色标记
由于cms有并发执行过程,所以在标记垃圾对象时有不确定性.
所以在标记时,将对象分为3种颜色(3种状态)
黑色: 例如GCRoots 确定是存活的对象
灰色: 在黑色对象中关联的对象,其中还有未扫描完的, 之后还需要再次进行扫描
白色: 与黑色,灰色对象无关联的, 垃圾收集算法不可达的对象
标记过程:
1.先确立GCRoots, 把GCRoots标记为黑色
2.与GCRoots关联的对象标记为灰色
3.再次遍历灰色,灰色变为黑色,灰色下面有关联的对象,关联的对象变为灰色
4.最终保留黑色,灰色, 回收白色对象
可能会出现漏标,错标问题
G1(Garbage-First) 垃圾优先
将堆内存各个区又分成较小的多个区域, 对这些个区域进行监测,对某个区域中垃圾数量大的区域优先回收.也是并发收集的.