JVM深入学习(一)
目录
一.JVM概述
1.1 为什么要学jvm?
1.2 jvm的作用
1.3 jvm内部构造
二.JVM类加载
2.1类加载过程
2.2类加载器
2.3类加载器的分类
2.4双亲委派机制
三.运行时数据区
堆空间区域划分(堆)
为什么分区(代)?(堆)
对象创建存储过程(堆)
jvm调优
方法区的垃圾回收(方法区)
一.JVM概述
1.1 为什么要学jvm?
1.面试的需要。
2.高级程序员需要了解 。
1.2 jvm的作用
jvm负责把编译后的字节码转缓缓为机器码。
1.3 jvm内部构造
类加载部分 | 负责把硬盘上的字节码加载到内存中(运行时数据区)。 |
运行时数据区 | 负责存储运行时产生的各种数据,类信息,对象信息,方法信息... |
执行引擎 | 负责将字节码转为机器码。 |
本地方法接口 | 调用本地方法。 Object类中的hashCode()--对象内存中的地址。 read0()方法调用操作系统,操作系统返回数据。 |
垃圾回收 | 使用引用计数法或可达性分析法来判断对象是否存活。 |
二.JVM类加载
类加载系统:负责将硬盘上的字节码文件加载到jvm中,生成类的Class对象,存储在方法区。
2.1类加载过程
1.加载:以二进制文件流进行读取 ,在内存中为类生成Class对象。
2.链接:
验证:验证文件格式是否正确,字节码文件都以(CAFEBABE标识开头)
准备:为类的静态属性分配内存,并进行初始化赋值(不包含final修饰的)如:static int n=123;准备阶段先赋值默认0,初始化阶段时赋值123。
注意:不包含用 final 修饰的 static 常量,在编译时进行初始化。
解析:将类中的符号引用替换为直接引用(把字节码的符号引用替换成内存中的直接引用)
3.初始化:(类加载完成的标志)
初始化阶段主要是为类中静态成员进行赋值。
因为类加载执行完初始化,才说明类加载完成了。
类在哪些情况下会被加载
1.调用类中的静态成员(变量(不是final修饰的常量),方法)
2.new类的对象
3.在类中执行main方法
4.反射加载类(Class.forName()生成的class对象)
5.子类被加载
类在以下两种情况下,是不会被加载的
1. 类作为数组类型
Demo demo[] = new Demo[10]; //new 的数组对象 不是Demo对象,Demo只是作为类型存在2.只是访问类中的静态的常量
System.out.println(Demo.P);//优化 不加载整个类了, 只获取到用到的静态常量
2.2类加载器
类加载器就是实际负责读取类的功能.
类加载器分类: 站在jvm的角度上, 分为
1.引导类加载器(不是用java写的,是用c/c++),负责读取加载java中底层系统库
2.java写的类加载器(用来读取我们写的应用程序)
输出为null,说明为引导类加载器写的,能输出则为java写的。
2.3类加载器的分类
再细分类加载器:
1.启动类加载器
C/C++语言实现,负责加载java核心类库(系统库,java.lang)
2. 扩展类加载器
用java语言实现,继承ClassLoader类,加载jre包下面的扩展类的jre/lib/ext子目录
3.应用程序类加载器
用Java语言实现,继承ClassLoader类,用来加载我们自己的应用程序类
2.4双亲委派机制
当加载一个类时,总是先让他的父级类加载器去加载,确保把系统中类优先加载,直到父类加载器找不到类时, 再逐级向下,让子级类加载器加载,如果子级也找不到, 最终抛类找不到异常。这就是双亲委派机制。
为什么这样做?
防止我们自己写的类替换了系统中的类(如:String类)
如何打破双亲委派机制?
自定义类加载器:MyClassLoader extends ClassLoader
重写findClass()
三.运行时数据区
存储运行时产生的各种数据
程序计数器 | 用来记录每一个线程执行的指令位置,速度最快,是线程私有(每个线程都会有一个程序计数器),此区域不会出现内存溢出(不够用),也不会出现垃圾回收。 |
虚拟机栈 | 栈是运行的,解决程序方法执行。 在虚拟机中,运行我们java自己写的方法 调用方法时,方法入栈,运行结束出栈 (先进后出 栈顶的方法,称为当前栈帧) 一个方法就是一个栈帧,,在栈帧中存储局部变量,运行结果.... 虚拟机栈也是线程私有的,线程之间互相隔离 栈区域不存在垃圾回收, 但是会存在内存溢出问题。 栈帧中存储内容:1.局部变量表 int a =10; 2.操作数栈 (计算过程) int c = a+b; 3.方法返回地址 |
本地方法栈 | 是用来执行调用的本地方法的,是线程私有的,不会存在垃圾回收,会出现内存溢出问题。 |
堆 | 堆的作用是用来存储java语言产生的对象的。 是运行时数据区中最大的一块内存空间, 空间大小可以设置。 堆空间是所有线程共享的。 堆空间是垃圾回收(没有用的垃圾对象,有周期性)的重点区域, 堆中没有被使用到的垃圾对象,会被垃圾回收器回收掉。有可能出现内存溢出。 |
方法区 | 方法区主要用来存储加载类的信息。 方法区的大小也是可以设置的。 方法区也会进行垃圾回收, 方法区也可能会出现内存溢出问题。 |
堆空间区域划分(堆)
堆分为:新生区(新生代/年轻代):伊甸园区、幸存者0区、幸存者1区
老年区(老年代)
为什么分区(代)?(堆)
可以将不同生命周期的对象存储在不同的区域,不同的区域采用不同的垃圾回收算法,使垃圾回收策略更加优化。
对象创建存储过程(堆)
新创建的对象都存储在伊甸园区。(如上述的图)
当垃圾回收时,将还被使用的对象,转移至某一个幸存者区,将伊甸园区垃圾对象进行清除。
当下次垃圾回收时,将伊甸园区存活的对象与当前正在使用的幸存者区存活的对象转移到另一个幸存者区(每次空闲一个幸存者区)。
当一个对象经历过15次垃圾回收后,仍然存活,那么就把该对象移动到老年代。
对老年代比较少的进行垃圾回收,在老年代空间不足时,对老年代会进行垃圾回收。
当回收后,内存仍然不足时,会出现FULL GC(整堆收集 应尽量避免)。
当整堆收集后仍然不够使用,那么就会出现内存溢出错误 --- OOM(OutOfMemoryError)
jvm调优
可以根据程序具体的使用场景, 对①运行时数据区的各种空间大小进行调整 例如:堆,方法区
②对垃圾回收器进行选择
方法区的垃圾回收(方法区)
方法区的垃圾回收, 是对类信息进行回收的。
类信息如果不再被使用,类信息也可以被卸载。
卸载条件:①该类所产生的对象都不存在了。
②该类的Class对象,也不再被使用了。
③ 加载该类的类加载器也被回收了。
感谢你的阅读与关注,如有问题欢迎探讨!💓