【转】JVM虚拟机内存模型

转发声明:

本文原创作者:书呆子Rico 
作者博客地址:http://blog.csdn.net/justloveyou_/

摘要:

  我们都知道,Java程序在执行前首先会被编译成字节码文件,然后再由Java虚拟机执行这些字节码文件从而使得Java程序得以执行。事实上,在程序执行过程中,内存的使用和管理一直是值得关注的问题。Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些数据区域都有各自的用途,以及创建和销毁的时间,并且它们可以分为两种类型:线程共享的方法区和堆,线程私有的虚拟机栈、本地方法栈和程序计数器。在此基础上,我们探讨了在虚拟机中对象的创建和对象的访问定位等问题,并分析了Java虚拟机规范中异常产生的情况。



友情提示:

  本文内容是基于 JDK 1.6 的,不同版本虚拟机之间也许会有些许差异,但不影响我们对JVM 内存模型的整体把握和了解。

  关于JVM垃圾回收机制的更多内容,请移步我的博文《 Java 垃圾回收机制概述》


一. Java 虚拟机内存模型

  Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些数据区域可以分为两个部分:一部分是线程共享的,一部分则是线程私有的。其中,线程共享的数据区包括方法区和堆,线程私有的数据区包括虚拟机栈、本地方法栈和程序计数器。如下图所示:

                



1、线程私有的数据区

  线程私有的数据区 包括 程序计数器、 虚拟机栈 和 本地方法栈 三个区域,它们的内涵分别如下:



1)、程序计数器

  我们知道,线程是CPU调度的基本单位。在多线程情况下,当线程数超过CPU数量或CPU内核数量时,线程之间就要根据 时间片轮询抢夺CPU时间资源。也就是说,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程都需要一个独立的程序计数器去记录其正在执行的字节码指令地址。

  因此,程序计数器是线程私有的一块较小的内存空间,其可以看做是当前线程所执行的字节码的行号指示器。如果线程正在执行的是一个 Java 方法,计数器记录的是正在执行的字节码指令的地址;如果正在执行的是 Native 方法,则计数器的值为空。

  程序计数器是唯一一个没有规定任何 OutOfMemoryError 的区域。



2)、虚拟机栈

  虚拟机栈描述的是Java方法执行的内存模型,是线程私有的。每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,而且 每个方法从调用直至完成的过程,对应一个栈帧在虚拟机栈中入栈到出栈的过程。其中,局部变量表主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和 对象句柄,它们可以是方法参数,也可以是方法的局部变量。

  虚拟机栈有两种异常情况:StackOverflowError 和 OutOfMemoryError。我们知道,一个线程拥有一个自己的栈,这个栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss 参数可以设置虚拟机栈大小),若线程请求的栈深度大于虚拟机允许的深度,则抛出 StackOverFlowError 异常。此外,栈的大小可以是固定的,也可以是动态扩展的,若虚拟机栈可以动态扩展(大多数虚拟机都可以),但扩展时无法申请到足够的内存(比如没有足够的内存为一个新创建的线程分配栈空间时),则抛出 OutofMemoryError 异常。下图为栈帧结构图:

           

3)、本地方法栈

  本地方法栈与Java虚拟机栈非常相似,也是线程私有的,区别是虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈为虚拟机执行 Native 方法服务。与虚拟机栈一样,本地方法栈区域也会抛出 StackOverflowError 和 OutOfMemoryError 异常。



2、线程共享的数据区

  线程共享的数据区 具体包括 Java堆 和 方法区 两个区域,它们的内涵分别如下:



1)、Java 堆

  Java 堆的唯一目的就是存放对象实例,几乎所有的对象实例(和数组)都在这里分配内存。Java堆是线程共享的,类的对象从中分配空间,这些对象通过new、newarray、 anewarray 和 multianewarray 等指令建立,它们不需要程序代码来显式的释放。

  由于Java堆唯一目的就是用来存放对象实例,因此其也是垃圾收集器管理的主要区域,故也称为称为 GC堆从内存回收的角度看,由于现在的垃圾收集器基本都采用分代收集算法,所以为了方便垃圾回收Java堆还可以分为 新生代老年代 新生代用于存放刚创建的对象以及年轻的对象,如果对象一直没有被回收,生存得足够长,对象就会被移入老年代。新生代又可进一步细分为 eden、survivorSpace0 和 survivorSpace1。刚创建的对象都放入 eden,s0 和 s1 都至少经过一次GC并幸存。如果幸存对象经过一定时间仍存在,则进入老年代。更多关于Java堆和分代收集算法的介绍,请移步我的博文《Java 垃圾回收机制概述》。下图给出了Java堆的结构图:

              

  注意,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。而且,Java堆在实现时,既可以是固定大小的,也可以是可拓展的,并且主流虚拟机都是按可扩展来实现的(通过-Xmx(最大堆容量) 和 -Xms(最小堆容量)控制)。如果在堆中没有内存完成实例分配,并且堆也无法再拓展时,将会抛出 OutOfMemoryError 异常。
     
(1)、TLAB (Thread Local Allocation Buffer,线程私有分配缓冲区)

  Sun Hotspot JVM 为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间 TLAB,其大小由JVM根据运行的情况计算而得。在TLAB上分配对象时不需要加锁(相对于CAS配上失败重试方式 ),因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配。

  在下文中我们提到,虚拟机为新生对象分配内存时,需要考虑修改指针 (该指针用于划分内存使用空间和空闲空间) 时的线程安全问题,因为存在可能出现正在给对象A分配内存,指针还未修改,对象B又同时使用原来的指针分配内存的情况。TLAB 的存在就是为了解决这个问题:每个线程在Java堆中预先分配一小块内存 TLAB,哪个线程需要分配内存就在自己的TLAB上进行分配,若TLAB用完并分配新的TLAB时,再加同步锁定,这样就大大提升了对象内存分配的效率。



2)、方法区

  方法区与Java堆一样,也是线程共享的并且不需要连续的内存,其用于存储已被虚拟机加载的 类信息常量静态变量即时编译器编译后的代码等数据方法区通常和永久区(Perm)关联在一起,但永久代与方法区不是一个概念,只是有的虚拟机用永久代来实现方法区,这样就可以用永久代GC来管理方法区,省去专门内存管理的工作。根据Java虚拟机规范的规定,当方法区无法满足内存分配的需求时,将抛出 OutOfMemoryError 异常。

(1)、运行时常量池

  运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放编译期生成的各种 字面量符号引用。其中,字面量比较接近Java语言层次的常量概念,如文本字符串、被声明为final的常量值等;而符号引用则属于编译原理方面的概念,包括以下三类常量:类和接口的全限定名字段的名称和描述符方法的名称和描述符。因为运行时常量池(Runtime Constant Pool)是方法区的一部分,那么当常量池无法再申请到内存时也会抛出 OutOfMemoryError 异常。

  运行时常量池相对于Class文件常量池的一个重要特征是具备动态性。Java语言并不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中,比如字符串的手动入池方法intern()。



3)、Java堆 与 方法区的区别

  Java堆是 Java代码可及的内存,是留给开发人员使用的;而非堆(Non-Heap)是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存 (如JIT编译后的代码缓存)、每个类结构 (如运行时常量池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。



4)、方法区的回收

  方法区的内存回收目标主要是针对 常量池的回收 对类型的卸载。回收废弃常量与回收Java堆中的对象非常类似。以常量池中字面量的回收为例,假如一个字符串“abc”已经进入了常量池中,但是当前系统没有任何一个String对象是叫做“abc”的,换句话说是没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用了这个字面量,如果在这时候发生内存回收,而且必要的话,这个“abc”常量就会被系统“请”出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

  判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是“无用的类”:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例;
  • 加载该类的ClassLoader已经被回收;
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

      虚拟机可以对满足上述3个条件的无用类进行回收(卸载),这里说的仅仅是“可以”,而不是和对象一样,不使用了就必然会回收。特别地,在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。


二. Java对象在虚拟机中的创建与访问定位

  Java是一门面向对象的编程语言,在Java程序运行过程中无时无刻都有对象被创建和使用。在此,我们以最流行的HotSpot虚拟机以及常用的内存区域Java堆为例来探讨在虚拟机中对象的创建和对象的访问等问题。



1、对象在虚拟机中的创建过程

  (1). 检查虚拟机是否加载了所要new的类,若没加载,则首先执行相应的类加载过程。虚拟机遇到new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个引用代表的类是否已经被加载、解析和初始化过。



  (2). 在类加载检查通过后,对象所需内存的大小在类加载完成后便可完全确定,虚拟机就会为新生对象分配内存。一般来说,根据Java堆中内存是否绝对规整,内存的分配有两种方式:

  • 指针碰撞:如果Java堆中内存绝对规整,所有用过的内存放在一边,空闲内存放在另一边,中间一个指针作为分界点的指示器,那分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相同的距离。
  • 空闲列表:如果Java堆中内存并不规整,那么虚拟机就需要维护一个列表,记录哪些内存块是可用的,以便在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。


  除了如何划分可用空间之外,还需要考虑修改指针 (该指针用于划分内存使用空间和空闲空间)时的线程安全问题,因为存在可能出现正在给对象A分配内存,指针还未修改,对象B又同时使用原来的指针分配内存的情况。解决这个问题有两种方案:

  • 对分配内存空间的动作进行同步处理:采用CAS+失败重试的方式保证更新操作的原子性;
  • 把内存分配的动作按照线程划分的不同的空间中:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB),哪个线程要分配内存,就在自己的TLAB上分配,如果TLAB用完并分配新的TLAB时,再加同步锁定。


  (3). 内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。如果使用TLAB,也可以提前到TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初值就直接使用,程序能访问到这些字段的数据类型所对应的零值。



  (4). 在上面的工作完成之后,从虚拟机的角度来看,一个新的对象已经产生了,但从Java程序的视角来看,对象的创建才刚刚开始,此时会执行<init>方法把对象按照程序员的意愿进行初始化,从而产生一个真正可用的对象。



2、对象在虚拟机中的访问定位

  创建对象是为了使用对象,我们的Java程序通过栈上的reference数据来操作堆上的具体对象。在虚拟机规范中,reference类型中只规定了一个指向对象的引用,并没有定义这个引用使用什么方式去定位、访问堆中的对象的具体位置。目前的主流的访问方式有使用句柄访问和直接指针访问两种。


  • 句柄访问:Java堆中会划分出一块内存作为句柄池,栈中的reference指向对象的句柄地址,句柄中包含了对象实例数据和类型数据各自的具体地址信息,如下图所示。

              


  • 直接指针访问:reference中存储的就是对象地址。

              



  总的来说,这两种对象访问定位方式各有千秋。使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,reference本身不需要修改;而使用直接指针访问的最大好处就是速度快,节省了一次指针定位的时间开销。


三. 内存异常产生情况分析

1、Java堆溢出 (OOM)

  Java堆用于存储对象的实例,只要不断地创建对象,并且保证GC roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。如下所示,

public class Test {

public static void main(String[] args){
        List list=new ArrayList();   // 持有“大对象”的引用,防止垃圾回收
        while(true){
            int[] tmp = new int[10000000];  // 不断创建“大对象”
            list.add(tmp);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

              

  要解决这个异常,一般先通过内存映像分析工具对堆转储快照分析,确定内存的对象是否是必要的,即判断是 内存泄露 还是 内存溢出。如果是内存泄露,可以进一步通过工具查看泄露对象到GC Roots的引用链,比较准确地定位出泄露代码的位置。如果是内存溢出,可以调大虚拟机堆参数,或者从代码上检查是否存在某些对象生命周期过长的情况。



2、虚拟机栈和本地方法栈溢出 (SOF/OOM)

(1). SOF

  如果线程请求的栈深度大于虚拟机栈允许的最大深度,将抛出StackOverflowError异常。我们知道,每当Java程序启动一个新的线程时,Java虚拟机会为它分配一个栈,并且Java虚拟机栈以栈帧为单位保持线程运行状态。每当线程调用一个方法时,JVM就压入一个新的栈帧到这个线程的栈中,只要这个方法还没返回,这个栈帧就存在。 那么可以想象,如果方法的嵌套调用层次太多,比如递归调用,随着Java虚拟机栈中的栈帧的不断增多,最终很可能会导致这个线程的栈中的所有栈帧的大小的总和大于-Xss设置的值,从而产生StackOverflowError溢出异常。看下面的栗子:

public class Test {

    public static void main(String[] args) {
          method();
    }

    // 递归调用导致 StackOverflowError
    public static void method(){
        method();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

              

  上面的SOF异常就是由递归引起的,具体而言就是因为method()方法中没有递归终止条件,从而使得该方法不断递归调用、不断创建栈帧导致的。



(2). OOM

  如果虚拟机在拓展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。在虚拟机栈和本地方法栈发生OOM异常场景如下:当Java 程序启动一个新线程时,若没有足够的空间为该线程分配Java栈(一个线程Java栈的大小由-Xss设置决定),JVM将抛出OutOfMemoryError异常。



3、方法区和运行时常量池溢出 (OOM)

  运行时常量池溢出的情况: String.intern()是一个native方法,在JDK1.6及之前的版本中,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象,否则将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。由于常量池分配在永久代中,如果不断地使用intern方法手动入池字符串,则会抛出OutOfMemoryError异常。但在JDK1.7及其以后的版本中,对intern()方法的实现作了进一步改进,其不会再复制实例到常量池中,而仅仅是在常量池中记录首次出现的实例的引用。看下面的例子(在JDK1.7中运行),

public class Test {
    public static void main(String[] args) {  

        String str1 = new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern() == str1);

        String str2 = new StringBuilder("java").toString();
        System.out.println(str2.intern() == str2);
    }/* Output:
        true
        false
     *///:~  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  为什么第一个返回true,而第二个返回false呢?因为在JDK1.7中,intern()方法的实现不会再复制实例,只是在常量池中记录 首次 出现的实例的引用,因此str1.intern()和str1指向的是同一个字符串,所以返回true。同一个引用。对于“java”这个字符串,由于在执行StringBuilder.toString() 之前已经出现过,所以字符串常量池中在new StringBuilder(“java”).toString()之前已经有它的引用了,不符合首次出现的原则,因此返回fasle。有人可能心里可能就要嘀咕了,为啥第二个不符合首次出现的原则,而第一个就符合首次出现的原则呢? 实际上,

String str2 = new StringBuilder("java").toString();
  • 1

等价于:

String s1 = "java";
StringBuilder sb = new StringBuilder(s1);
String str2 = sb.toString();

// StringBuilder 的 toString()方法
public String toString() {
        // Create a copy, don‘t share the array
        return new String(value, 0, count);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

  由上面代码可知,字符串”java”早就出现了,因此不符合首次出现的原则,返回false。同理,“计算机软件”这个字符串在new StringBuilder(“计算机”).append(“软件”).toString()之前从未出现过,因此符合首次出现的原则,返回true。

  要想更彻底地了解本实例,建议移步我的博文 《Java String 综述(上篇)》《Java String 综述(下篇)》进行进一步了解。



  方法区溢出的情况:一个类要被垃圾回收器回收掉,判断条件是比较苛刻的。 在经常动态产生大量Class的应用中,需要特别注意类的回收状况,比如动态语言、大量JSP或者动态产生JSP文件的应用(JSP第一次运行时需要编译为Java类)、基于OSGi的应用(即使是同一个类文件,被不同的加载器加载也会视为不同的类)等。

  更多关于JSP本质的全面介绍,请移步我的博文 《Java Web基础 — Jsp 综述(上)》《Java Web基础 — Jsp 综述(下)》


四. 更多

  更多关于Java 垃圾回收机制概述的知识,包括对象是否可以回收的判别算法,典型的垃圾回收算法的基本思想、经典垃圾收集器的介绍及内存分配规则等,请见我的博文《 Java 垃圾回收机制概述》

  更多关于Java String类及其手动入池的全面介绍,请移步我的博文 《Java String 综述(上篇)》《Java String 综述(下篇)》

  更多关于JSP本质的全面介绍,请移步我的博文 《Java Web基础 — Jsp 综述(上)》《Java Web基础 — Jsp 综述(下)》

  更多关于 Java SE 进阶 方面的内容,请关注我的专栏 《Java SE 进阶之路》。本专栏主要研究 JVM基础、Java源码和设计模式等Java进阶知识,从初级到高级不断总结、剖析各知识点的内在逻辑,贯穿、覆盖整个Java知识面,在一步步完善、提高把自己的同时,把对Java的所学所思分享给大家。万丈高楼平地起,基础决定你的上限,让我们携手一起勇攀Java之巅…


引用:

《深入理解Java虚拟机:JVM高级特性与最佳实践》
《Java基础总结》
《JVM系列文章(四):类加载机制》
JVM方法区内存回收
JAVA CAS原理深度分析

原文地址:https://www.cnblogs.com/stankangyong/p/10807420.html

时间: 2024-11-05 12:11:55

【转】JVM虚拟机内存模型的相关文章

java中JVM虚拟机内存模型详细说明

java中JVM虚拟机内存模型详细说明 2012-12-12 18:36:03|  分类: JAVA |  标签:java  jvm  堆内存  虚拟机  |举报|字号 订阅 JVM的内部结构如下图: 一个优秀Java程序员,必须了解Java内存模型.GC工作原理,以及如何优化GC的性能.与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率,才能提高整个应用程序的性能. 本文将从JVM内存模型.GC工作原理,以及GC的几个关键问题进行探讨,从

JVM虚拟机内存模型以及GC机制

JAVA堆的描述如下: 内存由 Perm 和 Heap 组成. 其中 Heap = {Old + NEW = { Eden , from, to } } JVM内存模型中分两大块,一块是 NEW Generation, 另一块是Old Generation. 在New Generation中,有一个叫Eden的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from,to), 它们用来存放每次垃圾回收后存活下来的对象.在Old Generation中,主要存放应用程序中生

JVM运行时数据区与JVM堆内存模型小结

前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 程序计数器:保存当前线程执行的指令的地址(大意如此). 虚拟机栈:由栈帧组成,而每个栈帧又包括局部变量表.操作数栈.动态连接(调用其他方法).出口(被调用时返回值) -- 每个栈帧就代表了一个方法的执行. 本地方法栈:类似虚拟机栈,只不过方法改成了native方法. 方法区:保存了类的各种信息.类的

Java虚拟机内存模型及垃圾回收监控调优

Java虚拟机内存模型及垃圾回收监控调优 如果你想理解Java垃圾回收如果工作,那么理解JVM的内存模型就显的非常重要.今天我们就来看看JVM内存的各不同部分及如果监控和实现垃圾回收调优. JVM内存模型         正如你上图所看到的,JVM内存可以划分为不同的部分,广义上,JVM堆内存可以划分为两部分:年轻代和老年代(Young Generation and Old Generation) 年轻代(Young Generation) 年轻代用于存放由new所生成的对象.当年轻代空间满时,

JVM的内存模型

JVM的内存模型 概述 Java虚拟机在执行java程序的过程中,会把它所管理的内存划分为若干个不同的数据区域.这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁. java虚拟机所管理的内存包括以下几个运行时数据区域: 方法区(包括运行时常量池):存储类信息.常量.静态变量.即时编译器编译后的代码等:各种字面量和符号引用. Java堆:存储java对象. 虚拟机栈:存储方法以及方法中的存储局部变量表(存放基本数据类型

设置TOMCAT的JVM虚拟机内存大小

你知道如何设置TOMCAT的JVM虚拟机内存大小吗,这里和大家分享一下,JAVA程序启动时JVM都会分配一个初始内存和最大内存给这个应用程序.这个初始内存和最大内存在一定程度都会影响程序的性能. 设置TOMCAT的JVM虚拟机内存大小 Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个java虚拟机.JAVA程序启动时JVM都会分配一个初始内存 和最大内存给这个应用程序.这个初始内存和最大内存在一定程度都会影响程序的性能.比如说在应用程序用到最大内存的时候,JVM是要

jvm Java内存模型

硬件的效率与一致性 在计算机中,内存的读写与处理器的计算速度有几个级的差距.这样会严重影响到TPS(Transations Per Second). 所以会为每个处理器配一个高速缓存以缓和处理器的速度.而在计算机中,多个处理器共享一个内存,这个时候数据的读写操作将不会安全 什么是内存模型                                                                        名词解释:JMM(Java Memory Model),即Java内

Java的虚拟机内存模型

JVM是JavaVirtualMachine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java语言的一个非常重要的特点就是与平台的无关性.而使用Java虚拟机是实现这一特点的关键.一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码.而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译.Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程

Java8虚拟机内存模型

Java虚拟机运行时数据区 在JDK1.8之前,JVM运行时数据区分为堆.虚拟机栈.本地方法栈.方法区.程序计数器.如下图所示: 虚拟机栈:线程私有,随线程创建而创建.栈里面是一个一个"栈帧",每个栈帧对应一次方法调用.栈帧中存放了局部变量表(基本数据类型变量和对象引用).操作数栈.方法出口等信息.当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误. 本地方法栈:线程私有,这部分主要与虚拟机用到的Native方法相关,一般情况下,并不需要关心这部分的内