JVM虚拟机原理
1.JVM内存区域
JVM内存区域包含如下:
- 方法区:用来存放加载的类信息、常量、静态变量、编译后的代码等数据
- 堆内存:JVM启动时创建,存放的是对象的实例,可以分为新生代和老年代。
- 虚拟机栈:一个线程单独开辟的空间,线程会执行一个或多个方法,一个方法对应一个栈帧,栈帧中存放的内容是局部变量、操作数栈、方法返回地址、动态链接、附加信息等
- 本地方法栈:和虚拟机栈的功能类似,是为虚拟机执行Native本地方法准备的。
- 程序计数器:用于记录当前程序执行字节码的位置,存储的是字节码指令地址。
其中虚拟机栈、本地方法栈、程序计数器3个内存区域是线程独占区域,当启用一个线程时就会开辟出这3个区域,线程结束之后这3个内存区域也就关闭了。
方法区和堆内存属于线程共享区域。
2.类加载机制
1、至少需要3个类加载器进行加载不同的类,分别是核心类库加载器(Bootstrap loader)、拓展类库加载器(Extension class loader)和应用程序加载器(application class loader)。
- 核心类库加载器负责加载保存在jdk/jre/lib/路径下的核心类库rt.jar包
- 拓展类库加载器负责加载在jre/lib/ext/路径下的拓展类包
- 应用程序加载器加载的是项目的class文件
2、双亲委派模型
该模型是为了避免类重复加载,也就是类在加载的时候,会从下到上逐级委托,从上倒下逐级查找。一级一级委托给最上级,然后从最上级的核心类库加载器进行查找对应的类,如果没有找到,则一级一级往下查找,查找到了对应的类则进行加载。
3、如何加载类
public static void main(String[] args) {
try {
URL[] urls=new URL[]{new URL("file:H:\\")};//类存放的位置
URLClassLoader loader=new URLClassLoader(urls);
Class clazz = loader.loadClass("ClassLoadTest");//根据类名称加载类
System.out.println("输出ClassLoadTest类使用的类加载器-->"+clazz.getClassLoader());
Object instance = clazz.newInstance();
clazz.getMethod("test").invoke(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
3.垃圾回收机制
1、标记对象是否回收的方法
- 引用计数
当对象被引用之后将这个对象进行标记并加1,如果对象引用完成之后则减一,但是这种方法不能将互相引用的对象进行清除,例如A对象引用B对象,但又没有其他地方用到这2个对象,所以这2个也是需要清除的,但是又处于循环引用中,所以这种引用计数的方法不能把它们都清除掉。 - 可达性分析
垃圾回收机制中会设置一些根对象(GCRoot),从根对象中开始查找类的引用,将所有被引用过的类都找出来并标记计数,而没有被引用过的类则全部会被清理掉。 - 对象的引用类型
强引用:最常见的普通对象引用,只要还有强引用指向对象,就不会被回收
软引用:JVM任务内存不足时,会去试图回收软引用指向的对象
弱引用:虽然是引用,但是随时可能被回收
虚引用:不同通过它访问对象,当对象被finalize后,执行指定逻辑的机制(Cleaner) - 可达性的类型
强可达:对象可以有一个或多个线程不通过各种引用访问到的情况
软可达:当只能通过软引用访问到对象的状态
弱可达:只能通过弱引用访问的状态,当弱引用被清除时,符合销毁条件
幻可达:不存在其他引用,并且finalize过了,只有虚引用指向这个对象
不可达:表示对象可以被清除
2、垃圾回收算法
- 标记-清除:
将被标记为需要清理的类直接在内存中清除掉,这种方法的缺点就是会产生内存碎片,因为将需要清理的内存直接清除,会出现已经清理的内存不是连续的,如果有更大的类被创建,这时需要开辟更大的内存,然而干净的内存不是连续的,所以开辟不了更大的内存了,所以这些直接清理出来的内存就是内存碎片。 - 复制算法:
复制一块相同的内存空间,将标记出计数的内存空间全部移动到新开辟的内存空间中,而之前的内存空间则全部清除。这种方法的缺点就是每次使用都要开辟新的内存空间,如果内存空间越大,开辟出新的内存空间就越大,会浪费内存空间。 - 标记-整理:
在一块内存空间中,将标记为需要清理的内存全部清除之后,将剩下正在使用的内存进行整理,将这些内存整理在一起,这样就不会出现内存碎片了。缺点就是每次清除垃圾之后都要整理内存空间,会减缓清理速率。
3、JVM中的垃圾回收原理
在堆内存中开辟了一块内存空间用来专门处理垃圾回收,这块内存空间分为好几个区域,分别是新生代,S0,S1,老年代。
清理步骤:
首先新建的对象优先存放到新生代区域,如果是大对象,则直接存放到老年代。新生代区域中使用的是标记清除的算法,当新生代区域的对象被清理之后,存活的对象将会全部移动至S0内存区,S0和S1区域就用来使用复制方法,当对象被引用计数到8或者15次之后就会被移动到老年代区域,而这个引用次数可以进行设置。老年代区域使用的算法则是标记-整理。JVM中使用的这种垃圾回收方式将各种方法都混合在一起,可以有效的提高回收效率。
4、垃圾收集器
- 串行收集器 -Serial GC -XX:+UseSerialGC(新生代收集器):
使用单个线程来处理垃圾收集工作,适合单处理器机器
Client模式下JVM的默认选择。 - 串行收集器 -Serial Old-XX:+UseSerialOldGC(老年代收集器):
可以在老年代使用,采用标记-整理的算法。 - 并行收集器 -Parallel GC -XX:UseParallelGC(新生代收集器):
使用多线程来处理垃圾回收,可以设置GC时间或吞吐量等值
server模式下JVM的默认选择
吞吐量=用户代码运行时间/(用户代码运行时间+GC时间) - 并行收集器 -Parallel Old GC -XX:UseParallelOldGC(老年代收集器):
-XX:ParallelGCThreads:设置用于回收垃圾的线程数,通常情况下与CPU数量相同
-XX:MAaxGCPauseMills:设置最大垃圾收集停顿时间,是一个大于0的整数
-XX:GCTimeRatio:设置吞吐量大小,为0~100之间的整数
-XX:+UseAdaptiveSizePolicy:打开自适应GC策略,以达到在堆大小、吞吐量、停顿时间之间的平衡点
以上收集器存在一个stop the world的问题,在某个时间,程序会存在不能运行的情况。 - 并发收集器-CMS(Concurrent Mark Sweep) GC -XX:+UseConcMarkSweepGC
专用于老年代的收集器,基于标记-清除算法,目的是尽量减少停顿时间
原理是会尝试GC操作和用户线程一起执行,但是会抢占用户线程,更占用CPU资源 - 并行收集器-ParNew GC -XX:+UseParNewGC(新生代收集器,已经放弃维护)
用于新生代GC的实现,实际上是串行收集器SerialGC的多线程版本
经常用于配合老年代的CMS GC一起工作
-XX:ParallelGCThreads 参数可用于设置多线程的数量 - 并发收集器-G1 -XX:+UseG1GC(新生代和老年代共用)
针对于大堆内存设计的收集器,兼顾吞吐量和停顿时间,目的是替代CMS
JDK9的版本中JVM的默认选择