JVM学习总结(一)

 一、jvm的划分区域

1.程序计数器

  • 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器
  • 程序计数器处于线程独占区
  • 如果线程执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是native方法,这个计数器的值为undefined(因为native方法大多数是C实现的并没有编译成字节码指令,所以为undefined)
  • 此区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

举个栗子:

程序计数器是每个线程独有的,而线程是cpu中最小的调度单元,Java 虚拟机的多线程是通过切换线程并分配处理器执行时间的方式来实现的,在任何一个确定的时间,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。如果有一个线程A在执行过程中切换到线程B,且线程B有更高的优先级,就会把线程A挂起执行线程B,而当线程B执行完成后就要唤醒线程A,就需要知道线程A执行到的位置,就可以通过查看线程A中的程序计数器指令

2.java虚拟机栈

  • 虚拟机栈描述的是java方法执行的动态内存模型
  • 栈帧:每个方法执行,都会创建一个栈帧,伴随着方法从创建到执行完成。用于存储局部变量表,操作数栈,动态链接,方法出口等
  • 局部变量表:存放编译时期可知的各种基本数据类型,引用类型,returnAddress类型。局部变量表的内存空间在编译时期完成分配,当进入一个方法时,这个方法需要在帧分配多少内存是固定的,在方法运行期间是不会改变局部变量表的大小

3.本地方法栈

  • 本地方法栈为虚拟机执行的native方法服务
  • 当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法

4.java堆

  • 堆(heap)是存储java实例或者对象的地方
  • 是垃圾收集器GC管理的主要区域
  • 是线程共享的内存区域

5.方法区

  • 方法区里存储了所有类装载进来后和这个类相关的所有运行时需要的信息(如类的静态变量,常量,类的全局名称,方法信息等)

对象在jvm中创建的过程:

1.检查对应的类是否已经被加载

jvm虚拟机遇到一个new的指令的时候,先检查指令参数是否能在常量池中定位到一个类的符号引用:

  • 能:检查这个符号引用对应的类是否已经被加载、解析和初始化
  • 否:先加载对象的类

2.为对象分配内存

  • 指针碰撞

如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器;分配内存只需向空闲那边移动指针,这种分配方式称为"指针碰撞"(Bump the Pointer);

  • 空闲列表

 如果Java堆不是规整的:用过的和空闲的内存相互交错;需要维护一个列表,记录哪些内存可用;分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为"空闲列表"(Free List);

 注:Java堆是否规整由JVM采用的垃圾收集器是否带有压缩功能决定的;

 

在java中对对象的操作:

在java中对对象的操作是通过引用来操作的,栈中存储引用,堆中存储对象。当时不同的jvm实现引用的方式可能不同,主要有两种比较流行的方式,分别是通过句柄引用和通过直接指针的引用。jvm中的GC也是通过引用来确定那些对象可以回收。

对比这两种引用实现,句柄池的方法在GC需要移动对象(消除内存碎片以存放大对象)时,只需要将句柄池中每个对象的指针地址修改即可。但是引用访问对象需要经过两个地址查找,降低了效率。直接指向对象的方式在需要移动对象时要将每个引用的地址都做修改,这相对直接修改句柄池来说要昂贵的多,但是因为一次寻址提高了效率。其中每个引用都有一个指向方法区里该类数据的指针,这是因为在java里面不像C++可以直接对内存对象做类型转换,Java类型转换前一定要做类型检查以保证这次转换是安全的以避免可能因此带来的程序崩溃。因此每个引用都有一个指向类型数据的指针。

 

猜你喜欢

转载自blog.csdn.net/qq_37776015/article/details/82216193