jvm
- jvm的理解
- 什么是OOM,什么是栈stack溢出?
- jvm常用调优 参数是什么?
- 内存快照如何抓取?怎么分析Dump文件?
- 类加载器的认识?
1.类加载机制
类加载机制,就是虚拟机把类的数据从class文件加载到内存中,并对数据进行校验,转换,解析和初始化,最终形成能被虚拟机直接使用的java类的过程;
过程:加载,验证,准备,解析,初始化,使用,卸载;
-
加载阶段
- JVM获取类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行结构,在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。
-
验证阶段
- 文件格式验证:验证字节流是否符合class文件格式的规范,能否被当前虚拟机处理,通过该阶段,字节流会进入内存的方法区中进行存储;
- 元数据验证:确保类的描述信息符合java语言的规范要求;
- 字节码验证:对类的方法进行校验分析;(引用沙箱安全机制,确保java类文件遵循java语言规范)
- 符号引用验证:对类自身以外的信息进行验证;
-
准备阶段
-
正式为类变量分配内存空间 并设置数据类型零值,包括static修饰的变量
-
public static int value= 123 ; //变量value在准备阶段过后的初始值是0,不是123. public static final int value = 123 ; //特殊情况:会生成ConstantValue属性,在准备阶段初始值是123.
final、static、static final修饰的字段赋值的区别:
(1)static修饰的字段在准备阶段被初始化为0或null等默认值,然后在初始化阶段(触发类构造器)才会被赋予代码中设定的值,如果没有设定值,那么它的值就为默认值。
(2)static final修饰的字段在Javac时生成ConstantValue属性,在类加载的准备阶段根据ConstantValue的值为该字段赋值,它没有默认值,必须显式地赋值,否则Javac时会报错。可以理解为在编译期即把结果放入了常量池中。
(3)final修饰的字段在运行时被初始化(可以直接赋值,也可以在实例构造器中()赋值),一旦赋值便不可更改。
-
-
解析阶段
将常量池中的符号引用替换为直接引用的过程。
-
初始化
初始化阶段才真正执行类中定义的java代码,执行构造器()方法,并按程序设置的值初始类变量。
-
类的主动使用
(1)遇到new、getstatic、putstatic、invokestatic这4条指定时。对应的场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已经在编译期把结果放入常量池的静态字段除外),以及调用一个类的静态方法的时候。 (2)使用java.lang.reflect包的方法对类进行发射调用的时候。 (3)当初始化一个类的时候,如果发现其父类还没进行初始化,则必须对父类进行初始化。(与接口的区别:接口在初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候,才会初始化) (4)当虚拟机启动时,用户指定的要执行的主类(包含main方法的类)。 (5)java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄所对应的类没有进行过初始化,则需要触发其初始化。
-
类的被动引用
调用一个类的静态方法,静态属性会出发类的被动初始化;
2.类加载器
生命周期的第一段,通过类加载器来完成,类加载器根据一个类的全限定名读取类的二进制字节流到JVM中,然后生成对应的java.lang.Class对象的实例;
-
在虚拟机中默认提供了三种类加载器,启动类加载器(Bootstrap ClassLoader),扩展类加载器(Extension ClassLoader),应用类加载器(Application ClassLoader)
-
自定义加载器:Customer ClassLoader 属于应用程序根据自身需要自定义的ClassLoader;
- 任意一个类在jvm中的唯一性,是由加载他的加载器和类全限定名共同确定,因此,比较两个类是否相等的前提是否是同一个类加载器加载,否则,即使源于一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不一致,那么这两个类也不相同。
- JVM中规定,一个类有且只有一个类加载器对他进行加载,由双亲委派机制保证。
3.双亲委派机制
双亲委派模型要求除了顶层的类加载器外,其余类加载器都应有自己的父类加载器;
首先 我们创建一个类 首先会从根加载器中 找这个类 ,如果找不到,去扩展类加载器找,如果还找不到,再去程序Application加载器中找;
所以我们命名类的时候不要和java中的类名冲突,不然会执行不到你这个类,因为已经在更高层的类加载器中找到这个类了。
工作原理
- 类加载器收到类加载的请求 Application
- 将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器(rt)
- 启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的类加载器,否则,抛出,通知子加载器进行加载;
- 重复步骤3
- ClassNotFoundException
优点
- 避免类的重复加载
- 解决各个类加载器的基础类统一问题,越基础的类由越上层的加载器进行加载。
- 安全,避免java核心api被修改。
- 将代码归入安全区域,确保代码可以执行哪些操作。
(Robot类操作电脑)
沙箱安全机制
将java代码限定在虚拟机特定大范围中,并严格限制代码对本地系统资源的访问
四.运行时数据区
-
程序计数器
内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成
一个永远不会OutOfMemoryError的地方
-
本地方法栈
栈内存,主管程序的运行,生命周期和线程同步,先进后出 ;
8大基本类型,对象的引用,实例的方法;
堆,栈,方法区的交互 栈在运行的过程中使用的对象引用堆中具体的实例, 堆中的常量引用方法区中常量池
-
虚拟机栈stack
线程私有,生命周期和线程一致。
-
java堆Heap
一个jvm只有一个堆,堆中存放的主要是 **类,方法,变量,常量 ,所有引用了类型的真是对象 **
对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。JDK1.8之后主要分为三个区域,新生区,老年区 和元空间(用来存放jdk自身携带的class对象,Interface元数据,存储的是一些java运行时的一些环境或类的信息(常量池就在其中),很小的一块区域,逻辑上存在,物理上忽略 也叫非堆)
long max = Runtime.getRuntime().maxMemory(); long total = Runtime.getRuntime().totalMemory(); System.out.println("max:"+max/1024/1024+"mb"); System.out.println("total:"+total/1024/1024+"mb");
堆内存error OOM ,
-
1.尝试扩大堆内存查看结果 -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15 -XX:+printGCDetails
Xms表示jvm启动时的初始堆大小,Xmx为最大堆大小,Xmn为新生代的大小
PermSize为永久带的初始大小,MaxPermSize为永久代的最大空间
MaxTenuringThreshold为达到这个值,对象就会被移进老年代
-
Gc
垃圾回收分为轻量和重量,轻量级触发条件当新生代
-
方法区
属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
静态变量,常量,类信息(构造方法,接口定义)运行时的常量池存在方法区中,但是实例变量存在堆内存中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6uAvqMMa-1620742990332)(C:\Users\Mr.zhang\Desktop\截图\jvm4.png)]
Native
native关键字修饰的方法,调用底层C语言的库,进入本地方法栈,调用本地方法接口JNI(JAVA NATIVE INTERFACE)
作用:扩展java的使用,融合不同的语言为java所用,
Gc算法
如何逃避被Gc的命运?
可以通过在finalize()方法中想办法让对象重新和GC Roots对象建立链接,那么这个对象就可以被救活了
-
轻Gc和重Gc分别在什么时候发生(分代收集算法)
对于新生代采用标记算法,对于老年代采用标记清除+标记压缩算法;
-
标记清楚法
扫描堆内的对象,对对象进行标记,清除没有标记的对象,但是会产生内存碎片;
-
标记压缩
对标记清除法的优化,再次扫描,防止产生内存碎片;
-
复制算法
新生区from to复制算法 每次GC to区的对象,将有引用的对象复制到from区 每次gc完毕后to区是空的 经历过15次(默认)之后会进入老年区
-
引用计数法
用数法 引用计数法师垃圾收集的早期策略,在这中方法中,堆中每个对象都有一个引用计数,每当有一个地方引用他时,引用计数值就+1,当引用失效时,引用计数值就-1,任何时刻引用计数值为0的对象就是可以被回收