《JVM系列,Java实战视频下载

  • 因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行。

  • JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令。

PC寄存器为什么被设定为私有的?

由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令,CPU会不停地进行任务切换,这样必然导致经常中断或恢复。

为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。因此每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。

CPU时间片:

CPU时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片。

在宏观上:俄们可以同时打开多个应用程序,每个程序并行不悖,同时运行。

但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。

Java虚拟机栈


Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。同程序计数器一样,它也是线程私有的,它的生命周期与线程相同。

虚拟机栈描述的是Java方法执行的内存模型:每个线程在创建时都会创建一个虚拟机栈,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

在这里插入图片描述

作用:主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。

对于虚拟机栈来说不存在垃圾回收问题(栈存在溢出的情况)。

栈的特点: 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。JVM直接对Java栈的操作只有两个:

  • 每个方法执行,伴随着进栈(入栈、压栈)
  • 执行结束后的出栈工作

在java虚拟机规范中,对于这个区域规定了两种异常:

  • Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError 异常。

  • 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutOfMemoryError 异常。

栈内存大小: 我们可以使用参数 -Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度(-Xss1m,-Xss1k),通过 -Xss设置栈的大小可以解决栈溢出问题

虚拟机栈的运行过程:

  • JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循“先进后出”/“后进先出”原则。

  • 在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)

  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作,如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧。

在这里插入图片描述

栈运行原理

  • 不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。

  • 如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。

  • Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。

栈帧的内部结构:

  • 局部变量表(Local Variables): 存放编译器可知的各种基本数据类型、对象引用和returnAddress。

  • 操作数栈(operand Stack): 作为计算过程中变量临时的存储空间,保存计算过程的中间结果。

  • 动态链接(DynamicLinking): 指向运行时常量池中该栈帧所属方法的引用。

  • 方法返回地址(Return Address): 方法正常退出或异常退出的定义。

  • 一些附加信息:栈帧中携带的与Java虚拟机实现相关的一些附加信息。例如:对程序调试提供支持的信息。

    在这里插入图片描述

并行每个线程下的栈都是私有的,因此每个线程都有自己各自的栈,并且每个栈里面都有很多栈帧,栈帧的大小主要由局部变量表 和 操作数栈决定的

在这里插入图片描述

局部变量表 (Local Variables): 被称之为局部变量数组或本地变量表。用来存放编译器可知的各种基本数据类型、对象引用和returnAddress。 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题。

在局部变量表里,32位以内的类型只占用一个slot(变量槽),64位的类型占用两个slot。因此63位长度的long和double类型数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配, 当进入一个方法时,这个变量需要在帧中分配多大的局部变量空间时完全确定的,并保存在方法的Code属性的maximum local variables数据项中,在方法运行期间不会改变局部变量表的大小。

局部变量(局部变量表中的变量),它是相比于成员变量来说的,它只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁

静态变量与局部变量的对比:

  • 我们知道类变量表有两次初始化的机会,第一次是在 “准备阶段” ,执行系统初始化,对类变量设置零值,另一次则是在 “初始化”阶段 ,赋予程序员在代码中定义的初始值。

  • 和类变量初始化不同,局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用。

  • 在栈帧中,与性能调优关系最为密切的部分就是前面提到的局部变量表。在方法执行时,虚拟机使用局部变量表完成方法的传递。

  • 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。

本地方法栈


本地方法栈(Native Method Stack) 与虚拟机所发挥的作用非常相似,同样也是线程私有的。它们之间的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为虚拟机使用到的Native方法服务

在这里插入图片描述

本地方法栈是使用C语言实现的,它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。

当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。

  • 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区。

  • 它甚至可以直接使用本地处理器中的寄存器

  • 直接从本地内存的堆中分配任意数量的内存。

在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。 甚至有的虚拟机直接就把本地方法栈和虚拟机栈合二为一了(比如Hotspot JVM)。在Java虚拟机栈和本地方法栈中,规定了两个异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,并且扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

什么是Native方法:

  • Native Method是一个Java调用非Java代码的接囗。一个Native Method是这样一个Java方法:该方法的实现由非Java语言实现,比如C。这个特征并非Java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern “c” 告知c++编译器去调用一个c的函数。

  • 在定义一个native method时,并不提供实现体(有些像定义一个Java interface),因为其实现体是由非java语言在外面实现的。

为什么使用Native Method?

Java使用起来非常方便,然而有些层次的任务用Java实现起来不容易,或者我们对程序的效率很在意时,问题就来了。

  • 与Java环境的交互

    有时Java应用需要与Java外面的环境交互,这是本地方法存在的主要原因。 你可以想想Java需要与一些底层系统,如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解Java应用之外的繁琐的细节。

  • 与操作系统的交互

    JVM支持着Java语言本身和运行时库,它是Java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。然而不管怎样,它毕竟不是一个完整的系统,它经常依赖于一底层系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法,我们得以用Java实现了jre的与底层系统的交互,甚至JVM的一些部分就是用c写的。 还有,如果我们要使用一些Java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。

  • Sun’s Java

    Sun的解释器是用C实现的,这使得它能像一些普通的C一样与外部交互。 jre大部分是用Java实现的,它也通过一些本地方法与外界交互。

目前Native方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间的通信很发达,比如可以使用Socket通信,也可以使用Web Service等等。

Java堆


对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,并确定空间大小。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

在这里插入图片描述

根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。

堆内存的大小是可以调节的在实现时,即可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。

Java堆 ,是垃圾收集器(GC,Garbage Collection)执行垃圾回收的重点区域,在方法结束后,堆中的对象不会马上被移除,仅仅在垃圾收集的时候才会被移除,也就是触发了GC的时候,才会进行回收。因此很多时候也被称作“GC堆”。

在这里插入图片描述

从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代、老年代和永久区(jdk8以后被称为元空间);

在这里插入图片描述

新生代与老年区:

  • Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(OldGen),默认情况下新生代与老年代的占比为1:2,-XX:NewRatio=2

  • 其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也被叫做from区、to区)

    在HotSpot中,Eden空间与Survivor空间缺省的占比为8:1:1(-xx:SurvivorRatio=8)。

    在这里插入图片描述

存储在JVM中的Java对象可以被划分为两类:

  • 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速

  • 另一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM是生命周期保持一致

几乎所有的Java对象都是在Eden区被new出来的,绝大部分的Java对象的销毁都在新生代进行了。(有些大的对象在Eden区无法存储时候,将直接进入老年代),IBM公司的专门研究表明,新生代中80%的对象都是“朝生夕死”的。

注意:在Eden区满了的时候,才会触发MinorGC,而幸存者区满了后,不会触发MinorGC操作。如果Survivor区满了后,将会触发一些特殊的规则,也就是可能直接晋升老年代

可以使用选项"-Xmn"设置新生代最大内存大小。

在这里插入图片描述

从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。不过无论如何划分,都与存放内存无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好的回收内存,或者更快的分配内存。

堆的大小在JVM启动时就已经设定好了,大家可以通过选项"-Xmx"和"-Xms"来进行设置。

**-Xms10m:最小堆内存/起始内存,等价于-xx:InitialHeapSize

-Xmx10m:最大堆内存**,等价于-XX:MaxHeapSize

通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在ava垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能。

默认情况下:

初始内存大小:物理电脑内存大小/64

最大内存大小:物理电脑内存大小/4

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时(内存大小超过“-xmx"所指定的最大内存时),将会抛出OutOfMemoryError异常。

方法区


方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,他用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码缓存等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是他却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

在这里插入图片描述

Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样在JVM启动的时候被创建,它不需要连续的内存和可以喧嚣而固定大小或者可扩展外, 还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是必要的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值