JVM运行时数据区、类加载器和GC算法!!!
JVM简介
JVM是Java虚拟机,存在于JRE中,即Java运行时环境,JVM是运行在操作系统上的程序,而我们的Java程序是运行在JRE(JVM)之上的,操作系统是运行在硬件之上的。
我们所编写的Java文件,首先会被编译成class文件,然后通过类加载器进行加载到JVM中,也就是我们的运行时数据区,核心有五部分组成,分别是,方法区、堆、虚拟机栈、本地方法栈、程序计数器。
类加载器
所谓类加载器,也就是将.class文件加载为Class模板,并可以通过new关键字创建实例对象,实例对象可以通过getClass方法获取Class模板,Class模板可以通过getClassLoader方法获取类加载器。
其中这里的类加载器有三种,分别是启动类(根)加载器(BootStrap Class Loader)、扩展加载器()ExtClassLoader、应用程序加载器(AppClassLoader),根据双亲委派机制,当加载一个对象的时候,会从应用程序加载器往上找,找到扩展类加载器,然后再往上找,找到根加载器,如果根加载器无法加载,那么就会看看扩展类加载器是否可以加载,如果也不可以那么就去应用程序加载器加载,如果还不可以,就会报错。
我们一般自定义的类,都是被应用程序加载器进行加载,JDK自带的类,一般都是通过根加载器进行加载,下图ext包下的类都是扩展类加载器进行加载的。
本地方法栈
本地方法栈主要是存储被native关键字修饰的方法,该方法会调用本地方法接口(Java Native Interface JNI)本地方法接口会调用本地方法库,其中这里面主要是一些C/C++程序。
程序计数器
程序计数器:程序计数器是线程私有的,每个线程都有一个程序计数器,是线程私有的,它可以看作是当前线程所执行的字节码的行号指示器。
方法区
方法区:方法区是线程共享的,主要存储静态变量、常量、类型信息(构造方法、接口的定义)和常量池。
虚拟机栈
当我们的方法被调用的时候,就会进栈,当方法执行结束的时候就会出栈,其中main方法是第一个进栈的方法,当然了,由于栈是先进后出的数据结构,所以main方法是最后出栈的方法,即最后结束的方法。
当我们的main方法调用其他方法的时候,其他方法也会进栈,如果其他方法也调用了其他方法,那么还会进栈那,如果我们有死循环(或其他不当操作)进栈,那么就会出现Stack Overflow即栈溢出。下面模拟一个栈溢出
package com.hzy;
public class Main {
public static void main(String[] args){
a();
}
public static void a() {
b();
}
public static void b() {
a();
}
}
栈中主要存放:八大基本数据类型、对象的引用、实例方法
堆
当类加载器加载了类之后,会把类、方法、常量、变量放到堆中,并保存引用类型所指的真实对象。
当某个对象使用完毕之后,会出栈,但是,所指向的对象会残留在堆中,也就成为了垃圾。对于堆来说,又被细分为了三部分:新生区、老年区、元空间(JDK8以后永久区改名为元空间)。新生区又细分为:伊甸园区(Eden)、幸存0区、幸存1区。这样分的目的也就是为了解决堆中的垃圾问题,GC垃圾回收机制就是,会对新生区进行垃圾回收,没有被回收的垃圾会进入到幸存区(0区或1区),GC垃圾回收机制会对幸存区进行回收,这种在新生区进行垃圾回收的机制是轻量级,即轻GC,如果在幸存区也没有被清理掉,那么就会进入老年区(默认是被清理了15次还没死,就进入老年区),对新生区和老年区都进行垃圾回收的机制是重量级的,即重GC,会把所有的垃圾都清理一下,我们所听说的垃圾回收机制,也就是对新生区和老年区进行的回收。
如果我们的新生区和老年区都满了,就会报一个Out Of Memory(OOM)内存不足。下面模拟一个OOM
package com.hzy;
public class Main {
public static void main(String[] args){
String s = "hello";
while (true) {
s += s;
}
}
}
这里的元空间常驻在内存中,主要是存放我们的Class对象,接口定义,常量池和Java运行时的一些环境,这里不存在垃圾,虚拟机关闭时会被释放内存
这里需要注意一下,我们的方法区就是在元空间的,其中常量池是在方法区中的,很多时候,我们习惯把方法区单独提出来,有时称方法区为非堆。
GC算法
如何判断该对象是垃圾?
引用计数法
用一个标识位去计数该对象被引用的次数,被引用一次该标识位的值就加1,当该标识位的值为0的时候就认为该对象是垃圾。
可达性分析法
从GC Roots往下搜索,如果某个对象不可达(也就是不在任何一棵树上),就认为该对象及与之相连的对象为垃圾(5,6,7为垃圾)。
GC主要是对新生区和老年区进行回收
标记-清除算法
标记清除算法是最基础的垃圾回收算法,主要包括两步,标记和清除,首先标记处所有需要回收的对象,标记之后,统一回收掉所有被标记的对象,反过来也可以,标记存活的对象,统一回收所有未标记的对象。该算法进行标记清除后,会有大量不连续的内存碎片。
标记-复制算法
复制算法主要是对新生区进行垃圾回收,其中新生区又包括:伊甸园区,幸存0区和幸存1区,其实这里的0区和1区又叫做from区和to区,这里的from区和to区是动态的,当进行垃圾回收的时候,如果伊甸园区有残余垃圾,from区也有残余垃圾,那么会把这两个区的垃圾都放到to区,并把to区改名为from区,目的是保证to区永远为空(谁空谁是to),方便把伊甸园区和from区的垃圾都放到to区。这样一来就会有一个空闲的区域,导致空间浪费。
标记-整理算法
即是对标记清楚算法再次扫描,进行空位填充,这样一来,没有了空余空间,但是多了一遍遍历,更耗时(一般都是执行多次标记清除算法才执行一次标记压缩算法)。
总结
对于GC垃圾回收算法适用场景:新生区用标记-复制算法,老年区用标记-整理算法