目录
JVM 运行时区域
方法区
klass模型
Oop模型
类加载过程
JVM调优总结
方法区是JVM 所有线程共享。主要用于存储类的相关信息、常量池、方法代码、属性信息等。
JDK1.8以前的HotSpot JVM的方法区就是永久代(permanent generation),是一片连续的堆空间,由于动态类加载情况越来越多,永久代内存变得越来越不可控,设置小了容易内存溢出,设置大了存在内存浪费。因此JDK1.8以后,方法区功能由元空间取代。
Metaspace(元空间)和 PermGen(永久代)类似,都是对 JVM规范中方法区的一种落地实现。不过元空间并不在虚拟机中,而是使用本地内存。(注:常量池在JDK1.7开始由永久代移入堆区)
元空间,其参数设置是 -XX:MetaspaceSize=N -XX:MaxMetaspaceSize=N
-XX:MetaspaceSize ,初始空间大小:达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
-XX:MaxMetaspaceSize,最大空间:默认是没有限制的。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:
-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集;
-XX:MaxMetaspaceFreeRatio ,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集。
JVM将java类编译成JVM字节码,在类加载器加载.class文件时,底层JVM(如HotSpot 运用C++代码)读取JVM字节码,将类的代码信息读取到元空间里,parse后以C++的InstanceKlass类实例对象的形式存在于元空间; 之后在JVM堆区生成镜像类(InstanceMirrorClass)实例,对应Java代码中的Class对象; 同时将java类中的静态变量初始化后存储于镜像类实例中。InstanceKlass类实例是java类在JVM内存中的数据结构的体现。
klass模型是java类在JVM底层的数据结构,元空间里的klass模型类的继承关系如下:
• Klass表示Java类在JVM中的存在形式
• InstanceKlass表示类的元信息
• InstanceMirrorKlass表示类的Class对象
• InstanceRefKlass表示java/lang/ref/Reference类的子类,这部分的概念与强软弱虚引用、垃圾回收有关系
• ArrayKlass表示数组类的元信息
• TypeArrayKlass表示基本数组类的元信息
• ObjArrayKlass表示引用数组类的元信息
JAVA运行过程中,每创建一个对象,JVM就会在堆区创建一个相应类型的oop(普通对象指针)对象, 下面是这几个oop的继承关系图:
• oopDesc表示JAVA对象在JVM中的存在形式
• instanceOopDesc表示普通类对象(非数组类对象)
• arrayOopDesc表示数组类对象
• typeArrayOopDesc表示基本数组类对象
• objArrayOopDesc表示引用数组类对象
类的生命周期是由7个阶段组成,但是类的加载说的是前5个阶段:
在这七个过程中,加载、验证、准备、初始化、卸载这5个阶段的顺序是一定的,类的加载过程必须按照这种顺序按部就班地开始,而解析过程则不一定:它在某个情况下可以在初始化阶段之后再开始,这是为了支持Java语言语言的运行时绑定(也叫动态绑定和晚期绑定)。
这里强调的是:类加载阶段都是互相交叉地混合式进行的,通常是在一个阶段执行的过程中调用、激活另一阶段
加载:
验证:
准备:
为类变量(静态变量)分配内存、赋初值(默认值)。(实例变量是在创建对象的时候完成赋值的,没有赋初值一说)。
如果类变量被final修饰,在编译时Javac将会为该变量生成ConstantValue属性,在准备阶段虚拟机会根据该属性直接赋值,即没有赋初值这一步。
解析:
将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行;解析后的信息存储在ConstantPoolCache类实例中。
何时解析?虚拟机根据需要来判断到底在类被加载器加载时进行解析,还是某个符号引用将被使用时进行解析。
openjdk是用的时候解析,即在执行特定的字节码指令之前进行解析:
anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield
初始化:
执行静态代码块,完成静态变量的赋值
静态字段、静态代码段,字节码层面会生成clinit方法
方法中语句的先后顺序与代码的编写顺序相关
何时进行类的加载?
主动引用时:
预加载时:包装类、String、Thread
被动引用:所有引用类的方式都不会触发初始化。
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
通过数组定义引用类,不会触发此类初始化:当初始化对象数组时,并不会实际触发对象的初始化操作。但是会触发一个是由虚拟机自动生成的、直接继承于java.lang.Object的子类,创建动作由字节码指令newarray触发。值得注意的是:该类代表了实际的对象数组,数组中应有的方法和属性都实现在这个类里。Java语言对数组的访问比C/C++相对安全是因为这个类分装了数组元素的访问方法。
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义类的初始化。
值得注意的是:
接口也有自己的初始化过程:编译器会为接口生成“()”类构造器,用于初始化接口中所定义的成员变量。
接口和类初始化的区别:当一个类在初始化时,其父类都基本上初始化过了,然而接口在初始化的时候,只有真正用到父接口的时候(如引用接口中定义的常量)才会进行初始化。
从哪加载?
• -Xmx:设置JVM最大可用内存
• -Xms:设置JVM初始内存
• -Xmn:设置年轻代大小
• -Xss:设置每个线程的堆栈大小,默认每个线程堆栈大小为1M
• -XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
• -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为1:4,年轻代占整个堆栈的1/5
备注:本文参考学习了https://www.cnblogs.com/zhouyuqin/p/5217609.html