Java的每个类,在JVM中,都有一个对应的Klass类实例与之对应,存储类的元信息如:常量池、属性信息、方法信息……
从继承关系上也能看出来,类的元信息是存储在元空间的
普通的Java类在JVM
中对应的是instanceKlass
类的实例里面放着元信息,虚拟机层面与Java类对等的数据结构它的三个字类
元信息:属性信息 方法信息
InstanceMirrorKlass
:用于表示java.lang.Class
的实例,Java代码中获取到的Class对象,实际上就是这个C++类的实例,存储在堆区,学名镜像类, 静态属性是存储在镜像类中的InstanceRefKlass
:用于表示java/lang/ref/Reference
类的子类InstanceClassLoaderKlass
:用于遍历某个加载器加载的类,描述类加载器的实例Java中的数组不是静态数据类型,是动态数据类型,即是运行期生成的,Java数组的元信息用ArrayKlass
的子类来表示:
TypeArrayKlass
:用于表示基本类型的数组ObjArrayKlass
:用于表示引用类型的数组类的生命周期是由7个阶段组成,但是类的加载说的是前5个阶段
1、通过类的全限定名获取存储该类的class文件 ,并获取其二进制字节流。
2、解析成运行时数据,即instanceKlass
实例,存放在方法区
3、在堆区生成该类的Class对象,即instanceMirrorKlass
实例
Class
文件格式的规范;例如:是否以0xCAFEBABE
开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。javac
编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object
之外。正式为类中定义的变量分配内存并设置类变量初始值
ConstantValue
属性,准备阶段直接完成赋值实际上是把类的符号引用替换为直接引用的过程。
解析后的信息存储在ConstantPoolCache
()类实例中
常量池缓存是为常量池预留的运行时数据结构。保存所有字段访问和调用字节码的解释器运行时信息。缓存是在类被积极使用之前创建和初始化的。每个缓存项在解析时被填充
clinit
方法new
关键字);java.lang.reflect
包中的方法(如:Class.forName(“xxx”)
);反射main
方法(如:SpringBoot
入口类)。主动引用:在类加载阶段,只执行加载、连接操作,不执行初始化操作
被动引用: 在类加载阶段,会执行加载、连接和初始化操作。
被动引用的几种形式:
JVM内存结构主要有三大块:
堆内存:是JVM中最大的一块,由年轻代和老年代组成,而年轻代内存又被分成三部分,
Eden空间、8
From Survivor空间、1
To Survivor空间,1
方法区 :存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。
栈: java栈 本地方法栈 程序计数器
控制参数
调优 -xms和-xmx最大最小调成一样大-XX:MaxNewSize和-XX:MetaspaceSize最大最小调成一样大防止内存抖动
或者物理内存的1/32
Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。最小值为内存1/4
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,
所以Java堆中还可以细分为:新生代和老年代;
再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。
类出生、成长、消亡的区域,一个类在这里产生,应用,最后被垃圾回收器收集,
新生代经过多次GC仍然存货的对象移动到老年区。若老年代也满了,这时候将发生Major GC(也可以叫Full GC),进行老年区的内存清理。若老年区执行了Full GC之后发现依然无法进行对象的保存,就会抛出OOM
java调用c,c++的动态链接库,运行里面函数需要的栈
方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
方法区有时被称为持久代(PermGen ), 但是实际上应该说使用永久代来实现方法区,1.8叫元空间而已。
可以吧方法区比作java的接口,永久代和原空间是对接口的实现
一个线程有一个虚拟机栈
一共有方法调用次数个栈帧
每个方法从被调用到执行完成的过程,其实就是一个栈帧在虚拟机栈中从入栈到出栈的过程。
不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致
和栈作用很相似,区别不过是Java栈为JVM执行Java方法服务,
而本地方法栈为JVM执行native方法服务。
登记native方法,在Execution Engine执行时加载本地方法库
其是很小的一块内存空间,可以看作是当前线程所执行的字节码的行号
因为Java虚拟机的多线程时通过线程轮流切换,轮流得到处理器的资源来进行的,所以就需要在线程中有一个计数器来记录执行到哪一行,方便切换回来,所以每个线程都需要一个独立的程序计数器,他们相互独立,互不影响。
当一个ClassLoader加载一个类的时候,除非显示的使用另一个ClassLoader,该类所依赖和引用的类也由这个ClassLoader载入
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类
AppClassLoader
加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader
去完成。ExtClassLoader
加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader```去完成。BootStrapClassLoader
加载失败(例如在 $JAVA_HOME/jre/lib
里未查找到该class),会使用 ExtClassLoader
来尝试加载;AppClassLoader
来加载,如果 AppClassLoader
也加载失败,则会报出异常 `ClassNotFoundException堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)
给对象添加一个引用计数器,每当有一个地方引用,计数器就加1。当引用失效,计数器就减1。任何时候计数器为0的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中没有选择这个算法来管理内存,最主要的原因是它很难解决对象之前相互循环引用的问题。所谓对象之间的相互引用问题,通过下面代码所示:除了对象a和b相互引用着对方之外,这两个对象之间再无任何引用。但是它们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数器法无法通知GC回收器回收它们。
这个算法的基本思想就是通过一系列的称为”GC Roots“的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连的话,则证明此对象时不可用的。
GC Roots根节点:类加载器、Thread、虚拟机栈的局部变量表、static成员、常量引用、本地方法栈的变量等等
需要满足以下三个条件:
它是最基础的收集算法,这个算法分为两个阶段,“标记”和”清除“。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它有两个不足的地方:
为了解决效率问题,复制算法出现了。它可以把内存分为大小相同的两块,每次只使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块区,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收
根据老年代的特点提出的一种标记算法,标记过程和“标记-清除”算法一样,但是后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象向一段移动,然后直接清理掉边界以外的内存
现在的商用虚拟机的垃圾收集器基本都采用"分代收集"算法,这种算法就是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
在新生代中,每次收集都有大量对象死去,所以可以选择复制算法,只要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率时比较高的,而且没有额外的空间对它进行分配担保,就必须选择“标记-清除”或者“标记-整理”算法进行垃圾收集
原文:https://www.cnblogs.com/RUAYO/p/14088782.html