目录
一.JVM概述
1.jvm的作用?
将字节码文件装载到虚拟机中并将java字节码文件转为机器码文件。
当前的jvm不仅可以将java字节码转为机器码,同样也可以执行其他语言编译后的字节码文件。
2.jvm内部构造?
1.类加载器:负责将硬盘上的字节码文件加载到内存当中(运行时数据区)
2.运行时数据区:负责存储运行时产生的各种数据 类的信息,静态变量,方法信息.....
3.执行引擎:负责将.class文件转为机器码文件
4.本地方法接口:调用本地方法 native修饰的
5.垃圾回收部分:负责对运行时数据区中的堆和方法区的空间进行垃圾回收。
程序在执行之前先要把 java 代码转换成字节码(class 文件),jvm 首先需要把字节码通过一
定的方式 类加载器(ClassLoader) 把文件加载到内存中的运行时数据区(Runtime Data Area)
,而字节码文件是 jvm 的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的
命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU 去执行,而
这个过程中需要调用其他语言的接口 本地库接口(NativeInterface) 来实现整个程序的功能,这
就是这 4 个主要组成部分的职责与功能。
二.类加载器
1.类加载器子系统概述
类加载器子系统负责从文件系统或者网络中加载 class 文件。类加载系统只负责 class 文件的加载,至于它是否可以运行,则由执行引擎决定。加载的类信息存放于一块称为方法区的内存空间。
类加载系统,负责将硬盘上的字节码文件加载到jvm中,生成类的Class对象,存储在方法区。类就是一个模板。
2.类加载过程
1.加载
以二进制文件流进行读取文件,在内存中为类生成Class对象。
2.链接
1.验证
验证字节码文件结构是否正确,以 CA FE BA BE 标识开头)
2.准备
为类的静态属性进行分配内存空间,并进行设置默认初始值。
public static int value = 123;
value 在准备阶段后的初始值是 0,而不是 123.
3.解析
将字节码的符号引用 替换为 内存中的直接引用地址。
3.初始化
对准备阶段分配的静态属性进行正确的初始化。
3.类在哪种情况下会被定义?
new 类的对象
调用类中的静态成员(方法,变量)
在类中执行main()方法
反射加载类 Class Class.forName("地址")
子类被加载时,父类也会被一块加载。
4.类在那两种情况下不会被加载?
①当调用类中的静态常量时类就不会被加载。
static final 修饰的
public final static int NUMBER = 5 ; //不会导致类初始化,被动使用
public final static int RANDOM = new Random().nextInt() ; //会导致类加载
②new一个对象数组时类不会被加载。
Demo[] d=new Demo[10];//注意现在new的是数组对象,而不是Demo对象,Demo只是数组类型而已。
注意现在new的是数组对象,而不是Demo对象,Demo只是数组类型而已。
5.类加载器的分类
站在jvm的角度上分为:
1.引导类加载器(不是由java代码写的,是由c/c++写的),负责读取加载java中的底层系统库。
2.java写的类加载器(用来读取我们写的java应用程序)。
再细分类加载器:
1.启动类加载器
C/C++语言实现,负责加载java核心类库(系统库 .lang等)
2.扩展类类加载器
用java语言实现的,继承于ClassLoader类,从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 系统安装目录的
jre/lib/ext 子目录(扩展目录)下加载类库.如果用户创建的 jar 放在此目录下,也会自动由扩展类加载器加载.
3.应用程序类加载器
java语言实现的,继承于ClassLoader类,加载我们自定义的类,用于加载用户类路径(classpath)上所有的类。该类加载器是程序中默认的类加载器.
ClassLoader 类 , 它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)。
6.双亲委派机制
jvm对class文件采用的是按需加载的方式,也就是需要用到某个类时,jvm才会将class文件加载到内存中生成class对象。而且在加载class类对象时,jvm采用的是双亲委派模式,即把请求交给父类处理,它是一种委派机制。
工作原理:
1.当一个类加载器收到了加载请求时,他先不会自己去加载,而是看自己有没有父类加载器能加载,可以的话就先交给父类加载器去加载。
2.如果父类加载器向上还有父类加载器,那就继续往上委托,依次递归,直到找到顶层的加载器为止。
3.如果顶层父类加载器不能够加载请求类时,就向下由子类加载器自己加载。
为什么使用双亲委派机制?
①优先加载系统中的类,防止自定义类将系统中的类替换。
②如果到了最底层都没有加载的话,那么就只能抛出ClassNotFoundException异常。
双亲委派机制的优点
安全,当自定义类存在和java类库中同样的类包和类名时,可避免用户自己编写的类替换 Java 的核心类,如 java.lang.String.
如何打破双亲委派机制?
自定义类加载器
Java 虚拟机的类加载器本身可以满足加载的要求,但是也允许开发者自定义类加载器。
在 ClassLoader 类中涉及类加载的方法有两个,loadClass(String name),
findClass(String name),这两个方法并没有被 final 修饰,也就表示其他子类可以 重写.
重写 findClass 方法
我们可以通过自定义类加载重写方法打破双亲委派机制,
再例如 tomcat 等都有自己定义的类加载器.
三.运行时数据区
功能:存储运行时的各种数据
1.程序计数器
用于记录在某一个线程中执行到的位置。
特点:
内存小,速度最快。
是每一个线程私有的.
不存在内存溢出,不存在垃圾回收
2.虚拟机栈
用于解决线程中的方法。
特点:
栈 先进后出/后进先出.
是每一个线程私有的.
存在内存溢出:栈压爆了(递归)
不存在垃圾回收.
每个虚拟机栈都是相互隔离的,互不打扰。
每一个方法都是一个栈帧。
栈帧的内部结构:
1.局部变量表(存储方法中生成的局部变量)
2.操作数栈(记录操作/过程的)
3.方法返回地址(就是一个方法中调用另一个方法,另一个方法执行完之后回到该方法的位置继续向下执行)
3.本地方法栈
本地方法栈中放的是本地方法。
特点:
栈先进后出。
存在内存溢出。
没有垃圾回收。
是每个线程私有的。
4.堆
堆用来存放生成的对象。
特点:
堆空间对于每个线程来说是共享的。
堆中是有垃圾回收的。
堆也会存现内存溢出问题。
堆是jvm中区域最大的一部分,并且jvm中除了程序计数器,其余四个区域的空间都是可以自定义设置的。
堆的分区?
新生代/新生区
伊甸园区 幸存者0(from) 幸存者1(to)
老年代/老年区
为什么要分区?
可以把存活时间不同的对象分到不同的区,频繁回收新生代,减少回收老年代。
对象在堆中的分配过程
①新new出来的对象都是先存在于伊甸园区。
②经过一次垃圾回收之后,伊甸园区中存活下来的对象被存放到幸存者0中。
③第二次垃圾回收时,伊甸园区中存活下来的对象和幸存者0中存活的对象被存放到幸存者0中。
④循环往复,直到某一个对象已经经历过15次垃圾回收时,就会将该对象存放到老年区中。
⑤因为对象头中有字段存储的就是垃圾回收的次数,长度为4bit,也就是1111=>15。
⑥幸存者0 和幸存者1 中总会存在一个区域是空闲的。
存入老年区的两个案例:
①经历15次垃圾回收
②内存比较大(eg对象数组)直接存到老年区。
堆空间的参数配置:
-Xmx:最大堆空间内存
-Xmn:设置新生代的大小
-Xms:初始堆空间内存
-XX:MaxTenuringTreshold:设置新生代垃圾的最大年龄
-XX:+PrintGCDetails 输出详细的 GC 处理日志
5.方法区
方法区用来存储加载类的信息(类字节码,静态常量,静态变量等等)。
特点:
方法区物理上和堆是一个空间。
方法区是所有线程共享的。
方法区存在垃圾回收和内存溢出。
方法区的大小也是可以设置的。
元数据区大小可以使用参数-XX:MetaspaceSize 指定。方法区一旦装满就会发生 Fall GC (整堆收集)
方法区的垃圾回收也称为类卸载。
判定一个类不会再使用的三个条件?
①该类的所有对象实例不会再使用
②该类的类加载器已经被回收
③该类的Class对象已经被回收
四.本地方法接口
1.什么是本地方法?
就是给操作系统所调用的,java不能调用,由C/C++实现。
native修饰的
eg:
Object.class.hasCode() public native int hashCode();//哈希
Thread.start() private native void start0(); //线程启动
new FileInputStream("").read(); private native int read0()// 读硬盘数据
2.为什么要使用本地方法?
与硬件进行交互,因为java语言是应用程序语言,没有权限直接操作硬件。
例如获取内存地址,读取硬盘数据这时就需要调用操作系统中的本地方法进行实现.
五.执行引擎
1.作用
将字节码文件解释/编译为机器码
两个编译:
前端编译:将.java文件编译为.class文件
后端编译:将字节码文件编译为机器码
2.什么是解释器?什么是 JIT编译器?
解释器:对字节码采用逐行解释的方法,立即执行,但是执行效率低。
JIT编译器:先将字节码进行编译操作,所以不是立即执行,所以开始慢需要花费时间编译,但是编译完之后执行效率就很高了。
3.为什么java是半编译半解释的语言?
起初将 Java 语言定位为“解释执行”还是比较准确的。再后来,Java 也发展出可以直接生成本地代码的编译器。现在 JVM 在执行 Java 代码的时候,通常都会将解释执行与编译执行二者结合起来进行。
4.JIT编译器效率高为什么还要使用解释器?
①程序启动后,解释器可以立即执行,响应速度快,省去编译的时间。
②而编译器需要先进行编译操作,需要消耗时间,但是编译为本地代码之后执行效率就很高了。
所以需要采用解释器+编译器并存的架构来换取一个平衡点。
六.垃圾回收
1.垃圾回收概述
a.什么是垃圾?
在运行的程序中,没有任何引用所指向的对象就称为垃圾。
b.早期垃圾回收
早期垃圾回收都是用C/C++实现,并且在new对象时需要先申请空间,并且最后在不使用这个对象时需要手动地将垃圾空间释放。
优点:可以使程序员自行的管理内存空间
缺点:比较麻烦,每次都需要去空间申请和释放,如果未及时的释放垃圾,则可能出现内存泄漏。
现在的垃圾回收(eg:Java,Python,C#....)都是自动的,不需要申请空间和手动释放内存空间。
优点:简化代码,避免了繁忙的内存申请和释放。
缺点:弱化了程序员的能力。
c.哪些区域可以回收垃圾?
在运行时数据区的堆和方法区中存在垃圾回收。
堆空间为主要垃圾回收部分,方法区中的垃圾回收一般都是 full gc(整堆收集)。
堆:频繁回收新生代,较少回收老年代。
d.内存溢出与内存泄漏
内存泄漏:
指在垃圾回收区域中,未能及时回收垃圾的情况。
内存溢出:
应用系统中无法回收的内存或者使用的内存过多,最终使得程序需要执行的内存空间不够用,程序运行不了,就会提示内存溢出。
2.垃圾回收相关算法
1.标记阶段算法
标记阶段一般是用来标记堆中的哪些对象需要回收,哪些是垃圾对象,把没有引用的指向的对象标记出来。
在垃圾回收时进行垃圾回收.
a.引用计数算法(目前没有被使用)
当一个对象被引用时,其中的引用计数就会+1,最终系统会自动清除计数值为0的垃圾对象。
缺点:
当存在循环利用时,,无法解决循环利用的问题。
在循环体中,对象之间只是相互引用,对于外界来说没有用,所以应当被判定为垃圾对象,但是由于之间存在相互引用,所以计数值并不为0,所以在这种算法下不会被判定为垃圾对象,实际上是应该被回收的。
循环引用未被回收还可能存在垃圾泄露的问题。
b.可达性分析算法
这种算法可以解决循环引用的问题。
思想:从一些活跃的对象开始查找,与活跃对象相连的对象就不是垃圾对象,没有与活跃对象相连的就是垃圾对象。
c.哪些对象可以被认定为是活跃对象?
①虚拟机栈中引用的对象
②方法区中静态属性引用的对象
③所有同步锁(synchronized)引用的对象
④Java虚拟机内部调用的对象
d.finalize()
finalized()是Object类中的一个受保护的方法。
当一个对象被判定为垃圾对象,在垃圾回收这个垃圾对象之前就会调用finalized()方法。
finalized()方法规定只能被调用一次,当一个对象被垃圾回收是调用了finalized()方法之后,又因为方法体中的语句使得对象复活,那么这个对象再次被当作垃圾对象回收时就不会再调用这个finalized()方法了。
我们可以对finalized()方法进行重写,可以使垃圾对象在被回收之前执行一些操作,但是切记我们不要手动的去调用finalized()方法,还有此方法中的内容也要注意,不然会影响垃圾回收的性能。
由于finalized()存在,我们把对象分为三类:
可触及:使用中的对象,没有被判定为是垃圾对象。
可复活:垃圾对象,还没有调用finalized()方法,可能会在方法中复活。
不可触及:第二次被判定为垃圾对象,已经调用过finalized()方法。
2.回收阶段算法
a.标记-复制算法
将一个空间中存活下来的对象复制放到另一个空间中,位置不改变,并且清空上个内存空间中的对象。
特点:存在多个内存空间
回收时需要移动对象,适用于存活对象少,内存小,垃圾多的场景(适用于新生代)
回收后,垃圾碎片多。
b.标记-清除算法
只在一个内存空间中,清除所有的垃圾对象,但也不是真正的清除,是将垃圾对象的地址存放到空闲的地址列表当中,新对象来的时候能放进垃圾空间就直接覆盖。
特点:只存在一个空间
回收不需要移动对象,只清除垃圾对象
回收后,存在垃圾碎片
适用于老年代(不移动对象)。
c.标记-压缩算法
只存在一个内存空间,就是在标记-清除算法的基础上,将存活下来的对象移动到内存空间的一侧,清除其余空间。
特点:只存在一个空间,
是一种移动式的垃圾回收算法,内存中就不存在碎片化问题
适用于老年代回收情况。
分代收集
新生代的垃圾回收一般采用标记-复制算法,标记-清除算法,垃圾多存活对象少,进行多次回收。
老年代的垃圾回收一般采用标记-压缩算法,垃圾少,存活对象多,进行少次回收。
3.垃圾回收器
垃圾回收器是jvm垃圾回收的的实现者。
垃圾回收器的种类很多,不同的虚拟机中可以使用不同的垃圾回收器。
垃圾回收器的分类:
从垃圾收集器线程数量上划分:
单线程:垃圾回收器中只能有一个线程进行垃圾回收操作。
多线程:垃圾回收器中有多个线程在执行垃圾回收操作。
从工作模式上划分:
独占式:当垃圾回收线程正在执行,其他线程就会暂停(stw)。
并发式:当垃圾回收线程正在执行,其他线程也是可以执行的。
按照工作内存划分:
新生代垃圾收集器
老年代垃圾收集器
CMS(Concurrent Mark Sweep,并发标记清除)收集器
首创了垃圾回收线程和用户线程并发执行,追求低延时。
垃圾回收过程:
初始标记阶段:独占式的
并发标记阶段:并发式的
重新标记阶段:独占式的
并发清除阶段:并发式的
G1(Garbage First)垃圾收集器
首先G1垃圾收集器适用于整堆收集,不再划分新生代和老年代。
适用于服务器场景.
G1垃圾收集器将每个区域划分为更小的区域,例如将伊甸园区划分为多个小区域,
优先回收垃圾对象较多的区域,故得名Garbage-First----垃圾优先。
也是支持并发执行的.
查看并设置垃圾收集器