JVM——-程序计数器、虚拟机栈、本地方法栈

2022年10月25日11:15:11

关于jvm虚拟机中的知识点总结与汇总资料

ghttps://blog.csdn.net/weixin_40701758/article/details/121756782?spm=1001.2014.3001.5501

为什么说程序计数器、虚拟机栈、本地方法栈是线程私有的?堆和方法区是线程共享的呀?

程序计数器的主要作用是:

        字节码解释器通过改变程序计数器来依次的读取指令,实现代码的流程控制

在多线程的情况下,程序计数器是用来记录线程的执行位置。

程序计数器的私有主要是为了线程切换后能够恢复到正确的执行的位置。

虚拟机栈:每个java方法在执行的时候,会创建一个栈帧来存储局部的变量表,操作数栈、常量池等信息。从方法的调用到执行完成的过程,就对应一个栈帧在java虚拟机栈中入栈和出栈的过程。

本地方法栈:虚拟机栈为执行java方法,本地方法栈是为虚拟机栈中使用到Native的方法,

为了保证线程中的局部变量不受别的线程所访问,本地方法栈和虚拟机栈是线程所私有的。

堆和方法区是线程所共享的资源,我们所知道的堆是进程中最大的一块内存的,主要是为了放新存放的对象的,方法区是为了存放已经加载的类的信息、常量、静态变量、及时的编译器后的代码的数据。

判断 一个对象是否存活的两种方式:

(1)引用计数法

所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.

引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。

(2)可达性算法(引用链法)

该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。

在java中可以作为GC Roots的对象有以下几种:虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象。

什么时候会出现StackOverflowError(栈溢出)?

  • 无限递归循环调用(最常见)。
  • 执行了大量方法,导致线程栈空间耗尽。
  • 方法内声明了海量的局部变量。
  • native 代码有栈上分配的逻辑,并且要求的内存还不小,比如 java.net.SocketInputStream.read0 会在栈上要求分配一个 64KB 的缓存(64位 Linux)。

什么时候会出现 OutOfMemoryError(堆溢出)?

  • 内存bai中加载的数据量过于庞大,如一次从数据库取出过多数据。
  • 集合类中有对对象的引用,使用完后未清空,dao使得JVM不能回收。
  • 代码中存在死循环或循环产生过多重复的对象实体。
  • 启动参数内存值设定的过小。

类加载的过程:

Java的每个类,在JVM中,都有一个对应的Klass类实例与之对应,存储类的元信息如:常量池、属性信息、方法信息

从继承的关系来看,类的元信息是存储在元空间中的,

普通的Java类在JVM中对应的是instanceKlass类的实例,再来说下它的三个字类

  1. InstanceMirrorKlass:用于表示java.lang.Class,Java代码中获取到的Class对象,实际上就是这个C++类的实例,存储在堆区,学名镜像类
  2. InstanceRefKlass:用于表示java/lang/ref/Reference类的子类
  3. InstanceClassLoaderKlass:用于遍历某个加载器加载的类

Java中的数组不是静态数据类型,是动态数据类型,即是运行期生成的,Java数组的元信息用ArrayKlass的子类来表示:

  1. TypeArrayKlass:用于表示基本类型的数组
  2. ObjArrayKlass:用于表示引用类型的数组

加载

1、通过类的全限定名获取存储该类的class文件(没有指明必须从哪获取)

2、解析成运行时数据,即instanceKlass实例,存放在方法区

3、在堆区生成该类的Class对象,即instanceMirrorKlass实例

什么时候进行加载的呀?

主动使用时

1、new、getstatic、putstatic、invokestatic

2、反射

3、初始化一个类的子类会去加载其父类

4、启动类(main函数所在类)

(1)加载

加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:

  1. 通过一个类的全限定名获取该类的二进制流。
  2. 将该二进制流中的静态存储结构转化为方法去运行时数据结构。
  3. 在内存中生成该类的Class对象,作为该类的数据访问入口。

(2)验证

验证的目的是为了确保Class文件的字节流中的信息不回危害到虚拟机.在该阶段主要完成以下四钟验证:

  1. 文件格式验证:验证字节流是否符合Class文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.
  2. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。
  3. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。
  4. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。
  5. 准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。

(3)解析

该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

(4)初始化

初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。

  • 作者:努力撸代码的小刑
  • 原文链接:https://blog.csdn.net/weixin_40701758/article/details/123542607
    更新时间:2022年10月25日11:15:11 ,共 2542 字。