JVM总结之类加载

目录

JVM 运行时区域

方法区

klass模型

Oop模型

类加载过程

JVM调优总结


JVM 运行时区域:

  • 方法区:当JVM的类装载器加载.class文件,并进行解析,把解析的类型信息放入方法区。运行时的常量池是方法区的一部分。
  • 堆:虚拟机中只有一个堆,所有的线程都共享他。存放所有程序在运行时创建的对象
  • Java栈:存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址
  • PC寄存器:每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的饿地址,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。
  • 本地方法栈:用于支持native方法的执行,存储了每个native方法调用的状态。对于本地方法接口,实现JVM并不要求一定要有它的支持,甚至可以完全没有。Sun公司实现Java本地接口(JNI)是出于可移植性的考虑

方法区:

方法区是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剩余空间容量的百分比,减少为释放空间所导致的垃圾收集。

klass模型:

JVM将java类编译成JVM字节码,在类加载器加载.class文件时,底层JVM(如HotSpot 运用C++代码)读取JVM字节码,将类的代码信息读取到元空间里,parse后以C++的InstanceKlass类实例对象的形式存在于元空间; 之后在JVM堆区生成镜像类(InstanceMirrorClass)实例,对应Java代码中的Class对象; 同时将java类中的静态变量初始化后存储于镜像类实例中。InstanceKlass类实例是java类在JVM内存中的数据结构的体现。

klass模型是java类在JVM底层的数据结构,元空间里的klass模型类的继承关系如下:

image.png

• Klass表示Java类在JVM中的存在形式 
    • InstanceKlass表示类的元信息 
        • InstanceMirrorKlass表示类的Class对象 
        • InstanceRefKlass表示java/lang/ref/Reference类的子类,这部分的概念与强软弱虚引用、垃圾回收有关系
    • ArrayKlass表示数组类的元信息 
        • TypeArrayKlass表示基本数组类的元信息 
        • ObjArrayKlass表示引用数组类的元信息 

Oop模型:

JAVA运行过程中,每创建一个对象,JVM就会在堆区创建一个相应类型的oop(普通对象指针)对象, 下面是这几个oop的继承关系图:

在这里插入图片描述
• oopDesc表示JAVA对象在JVM中的存在形式 
    • instanceOopDesc表示普通类对象(非数组类对象) 
    • arrayOopDesc表示数组类对象 
        • typeArrayOopDesc表示基本数组类对象 
        • objArrayOopDesc表示引用数组类对象

类加载过程:

类的生命周期是由7个阶段组成,但是类的加载说的是前5个阶段:

image.png

  在这七个过程中,加载、验证、准备、初始化、卸载这5个阶段的顺序是一定的,类的加载过程必须按照这种顺序按部就班地开始,而解析过程则不一定:它在某个情况下可以在初始化阶段之后再开始,这是为了支持Java语言语言的运行时绑定(也叫动态绑定和晚期绑定)。 
  这里强调的是:类加载阶段都是互相交叉地混合式进行的,通常是在一个阶段执行的过程中调用、激活另一阶段

加载:

  • 通过一个类的全限定名来读取定义此类的二进制字节流,即class文件。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,即instanceKlass实例,存放在方法区。
  • 在堆区生成类的java.lang.Class对象,作为方法区这个类的各种数据的访问接口,即instanceMirrorKlass实例。

验证:

  • 文件格式检验:检验字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
  • 元数据检验:对字节码描述的信息进行语义分析,以保证其描述的内容符合Java语言规范的要求。
  • 字节码检验:这一阶段是对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事情。 
  • 符号引用检验:发生在虚拟机将符号引用转化为直接引用时,即解析阶段;用于检验符号引用的类,方法、字段是否在内存中存在,是否有权限访问,确保解析动作的正常执行,如果无法通过符号引用检验,将会抛出java.lang.IncompatibleClassChangeError异常的子类,如IllegalAccessError、NoSuchfiledError、NoSuchMethodError等。

准备:

为类变量(静态变量)分配内存、赋初值(默认值)。(实例变量是在创建对象的时候完成赋值的,没有赋初值一说)。

如果类变量被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方法
方法中语句的先后顺序与代码的编写顺序相关

何时进行类的加载?

主动引用时:

  • 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,生成这4条指令最常见的代码情景是:使用new关键字实例化对象、读取过设置一个类的静态字段(被final修饰、已在编译期把结果放进常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
  • 使用java.lang.reflect包对类进行反射调用时
  • 初始化一个类的子类会去加载其父类
  • 启动类(main函数所在类)
  • 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例的最后解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则要先触发其初始化。

预加载时:包装类、String、Thread

被动引用:所有引用类的方式都不会触发初始化。 
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。 
通过数组定义引用类,不会触发此类初始化:当初始化对象数组时,并不会实际触发对象的初始化操作。但是会触发一个是由虚拟机自动生成的、直接继承于java.lang.Object的子类,创建动作由字节码指令newarray触发。值得注意的是:该类代表了实际的对象数组,数组中应有的方法和属性都实现在这个类里。Java语言对数组的访问比C/C++相对安全是因为这个类分装了数组元素的访问方法。 
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义类的初始化。

值得注意的是: 
  接口也有自己的初始化过程:编译器会为接口生成“()”类构造器,用于初始化接口中所定义的成员变量。 
  接口和类初始化的区别:当一个类在初始化时,其父类都基本上初始化过了,然而接口在初始化的时候,只有真正用到父接口的时候(如引用接口中定义的常量)才会进行初始化。

从哪加载?

  • 从压缩包中读取,如jar、war
  • 从网络中获取,如Web Applet
  • 动态生成,如动态代理、CGLIB
  • 由其他文件生成,如JSP
  • 从数据库读取
  • 从加密文件中读取

JVM调优总结 -Xms -Xmx -Xmn -Xss:

• -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

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值