如何写论文?写好论文?免费论文网提供各类免费论文写作素材!
当前位置:免费论文网 > 美文好词 > 优质好文 > jvm虚拟机详解

jvm虚拟机详解

来源:免费论文网 | 时间:2018-11-08 14:38 | 移动端:jvm虚拟机详解

篇一:jvm虚拟机工作原理

1Java技术与Java虚拟机

说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。它们的关系如下图所示:

图1Java四个方面的关系

运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件)。最后字节码被装入内存, 一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。从上图也可以看出Java平台由Java虚拟机和 Java应用程序接口搭建,Java语言则是进入这个平台的通道,用Java语言编写并编译的程序可以运行在这个平台上。这个平台的结构如下图所示:

在Java平台的结构中, 可以看出,Java虚拟机(JVM) 处在核心的位置,是程序与底层操作系统和硬件无关的关键。它的下方是移植接口,移植接口由两部分组成:适配器和Java操作系统,

其中依赖于平台的部分

称为适配器;JVM 通过移植接口在具体的平台和操作系统上实现;在JVM 的上方是Java的基本类库和扩展类库以及它们的API, 利用Java API编写的应用程序(application) 和小程序(Java applet) 可以在任何Java平台上运行而无需考虑底层平台, 就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java 的平台无关性。

那么到底什么是Java虚拟机(JVM)呢?通常我们谈论JVM时,我们的意思可能是:

1. 对JVM规范的的比较抽象的说明;

2. 对JVM的具体实现;

3. 在程序运行期间所生成的一个JVM实例。

对JVM规范的的抽象说明是一些概念的集合,它们已经在书《The Java Virtual Machine Specification(》《Java虚拟机规范》)中被详细地描述了;对JVM的具体实现要么是软件,要么是软件和硬件的组合,它已经被许多生产厂 商所实现,并存在于多种平台之上;运行Java程序的任务由JVM的运行期实例单个承担。在本文中我们所讨论的Java虚拟机(JVM)主要针对第三种情 况而言。它可以被看成一个想象中的机器,在实际的计算机上通过软件模拟来实现,有自己想象中的硬件,如处理器、堆栈、寄存器等,还有自己相应的指令系统。

JVM在它的生存周期中有一个明确的任务,那就是运行Java程序,因此当Java程序启动的时候,就产生JVM的一个实例;当程序运行结束的时候,该实例也跟着消失了。下面我们从JVM的体系结构和它的运行过程这两个方面来对它进行比较深入的研究。

2Java虚拟机的体系结构

刚才已经提到,JVM可以由不同的厂商来实现。由于厂商的不同必然导致JVM在实现上的一些不同,然而JVM还是可以实现跨平台的特性,这就要归功于设计JVM时的体系结构了。

我们知道,一个JVM实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、数据类型和指令这些部分,它们描述了JVM的一个抽象的内部体系 结构,其目的不光规定实现JVM时它内部的体系结构,更重要的是提供了一种方式,用于严格定义实现时的外部行为。每个JVM都有两种机制,一个是装载具有 合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、 Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成的体系结构图为:

图3JVM的体系结构

JVM的每个实例都有一个它自己的方法域和一个堆,运行于JVM内的所有的线程都共享这些区域;当虚拟机装载类文件的时候,它解析其中的二进制数据 所包含的类信息,

并把它们放到方法域中;当程序运行的

时候,JVM把程序初始化的所有对象置于堆上;而每个线程创建的时候,都会拥有自己的程序计数器和 Java栈,其中程序计数器中的值指向下一条即将被执行的指令,线程的Java栈则存储为该线程调用Java方法的状态;本地方法调用的状态被存储在本地 方法栈,该方法栈依赖于具体的实现。

下面分别对这几个部分进行说明。

执行引擎处于JVM的核心位置,在Java虚拟机规范中,它的行为是由指令集所决定的。尽管对于每条指令,规范很详细地说明了当JVM执行字节码遇 到指令时,它的实现应该做什么,但对于怎么做却言之甚少。Java虚拟机支持大约248个字节码。每个字节码执行一种基本的CPU运算,例如,把一个整数 加到寄存器,子程序转移等。Java指令集相当于Java程序的汇编语言。

Java指令集中的指令包含一个单字节的操作符,用于指定要执行的操作,还有0个或多个操作数,提供操作所需的参数或数据。许多指令没有操作数,仅由一个单字节的操作符构成。

虚拟机的内层循环的执行过程如下:

do{

取一个操作符字节;

根据操作符的值执行一个动作;

}while(程序未结束)

由于指令系统的简单性,使得虚拟机执行的过程十分简单,从而有利于提高执行的效率。指令中操作数的数量和大小是由操作符决定的。如果操作数比一个字节大,那么它存储的顺序是高位字节优先。例如,一个16位的参数存放时占用两个字节,其值为:

第一个字节*256+第二个字节字节码。

指令流一般只是字节对齐的。指令tableswitch和lookup是例外,在这两条指令内部要求强制的4字节边界对齐。

对于本地方法接口,实现JVM并不要求一定要有它的支持,甚至可以完全没有。Sun公司实现Java本地接口(JNI)是出于可移植性的考虑,当然 我们也可以设计出其它的本地接口来代替Sun公司的JNI。但是这些设计与实现是比较复杂的事情,需要确保垃圾回收器不会将那些正在被本地方法调用的对象 释放掉。 Java的堆是一个运行时数据区,类的实例(对象)从中分配空间,它的管理是由垃圾回收来负责的:不给程序员显式释放对象的能力。Java不规定具体使用的垃圾回收算法,可以根据系统的需求使用各种各样的算法。 Java方法区与传统语言中的编译后代码或是Unix进程中的正文段类似。它保存方法代码(编译后的java代码)和符号表。在当前的Java实现 中,方法代码不包括在垃圾回收堆中,但计划在将来的版本中实现。每个类文件包含了一个Java类或一个Java界面的编译后的代码。可以说类文件是 Java语言的执行代码文件。为了保证类文件的平台无关性,Java虚拟机规范中对类文件的格式也作了详细的说明。其具体细节请参考Sun公司的Java 虚拟机规范。

Java虚拟机的寄存器用于保存机器的运行状态,与微处理器中的某些专用寄存器类似。Java虚拟机的寄存器有四种:

1. pc: Java程序计数器;

2. optop: 指向操作数栈顶端的指针;

3. frame: 指向当前执行方法的执行环境的指针;。

4. vars: 指向当前执行方法的局部变量区第一个变量的指针。

在上述体系结构图中,我们所说的是第一种,即程序计数器,每个线程一旦被创建就拥有了自己的程序计数器。当线程执行Java方法的时候,它包含该线程正在被执行的指令的地址。但是若线程执行的是一个本地的方法,那么程序计数器的值就不会被定义。

Java虚拟机的栈有三个区域:局部变量区、运行环境区、操作数区。

局部变量区

每个Java方法使用一个固定大小的局部变量集。它们按照与vars寄存器的字偏移量来寻址。局部变量都是32位的。长整数和双精度浮点数占据了两 个局部变量的空间,却按照第一个局部变量的索引来寻址。(例如,一个具有索引n的局部变量,如果是一个双精度浮点数,那么它实际占据了索引n和n+1所代 表的存储空间)虚拟机规范并不要求在局部变量中的64位的值是64位对齐的。虚拟机提供了把局部变量中的值装载到操作数栈的指令,也提供了把操作数栈中的 值写入局部变量的指令。

运行环境区

在运行环境中包含的信息用于动态链接,正常的方法返回以及异常捕捉。

动态链接

运行环境包括对指向当前类和当前方法的解释器符号表的指针,用于支持方法代码的动态链接。方法的class文件代码在引用要调用的方法和要访问的变 量时使用符号。动态链接把符号形式的方法调用翻译成实际方法调用,装载必要的类以解释还没有定义的符号,并把变量访问翻译成与这些变量运行时的存储结构相 应的偏移地址。动态链接方法和变量使得方法中使用的其它类的变化不会影响到本程序的代码。

正常的方法返回

如果当前方法正常地结束了,在执行了一条具有正确类型的返回指令时,调用的方法会得到一个返回值。执行环境在正常返回的情况下用于恢复调用者的寄存器,并把调用者的程序计数器增加一个恰当的数值,以跳过已执行过的方法调用指令,然后在调用者的执行环境中继续执行下去。

异常捕捉

异常情况在Java中被称作Error(错误)或Exception(异常),是Throwable类的子类,在程序中的原因是:①动态链接错,如无法找到所需的class文件。②运行时错,如对一个空指针的引用。程序使用了throw语句。 当异常发生时,Java虚拟机采取如下措施:

?

? 检查与当前方法相联系的catch子句表。每个catch子句包含其有效指令范围,能够处理的异常类型,以及处理异常的代码块地址。 与异常相匹配的catch子句应该符合下面的条件:造成异常的指令在其指令范围之内,发生的异常类型

是其能处理的异常类型的子类型。如果找到了匹 配的catch子句,那么系统转移到指定的异常处理块处执行;如果没有找到异常处理块,重复寻找匹配的catch子句的过程,直到当前方法的所有嵌套的 catch子句都被检查过。

? 由于虚拟机从第一个匹配的catch子句处继续执行,所以catch子句表中的顺序是很重要的。因为Java

代码是结构化的,因此总可以把某个方 法的所有的异常处理器都按序排列到一个表中,对任意可能的程序计数器的值,都可以用线性的顺序找到合适的异常处理块,以处理在该程序计数器值下发生的异常 情况。

? 如果找不到匹配的catch子句,那么当前方法得到一个"未截获异常"的结果并返回到当前方法的调用

者,好像异常刚刚在其调用者中发生一样。如果 在调用者中仍然没有找到相应的异常处理块,那么这种错误将被传播下去。如果错误被传播到最顶层,那么系统将调用一个缺省的异常处理块。

操作数栈区

机器指令只从操作数栈中取操作数,对它们进行操作,并把结果返回到栈中。选择栈结构的原因是:在只有少量寄存器或非通用寄存器的机器(如 Intel486)上,也能够高效地模拟虚拟机的行为。操作数栈是32位的。它用于给方法传递参数,并从方法接收结果,也用于支持操作的参数,并保存操作 的结果。例如,iadd指令将两个整数相加。相加的两个整数应该是操作数栈顶的两个字。这两个字是由先前的指令压进堆栈的。这两个整数将从堆栈弹出、相 加,并把结果压回到操作数栈中。

每个原始数据类型都有专门的指令对它们进行必须的操作。每个操作数在栈中需要一个存储位置,除了long和double型,它们需要两个位置。操作 数只能被适用于其类型的操作符所操作。例如,压入两个int类型的数,如果把它们当作是一个long类型的数则是非法的。在Sun的虚拟机实现中,这个限 制由字节码验证器强制实行。但是,有少数操作(操作符dupe和swap),用于对运行时数据区进行操作时是不考虑类型的。

本地方法栈,当一个线程调用本地方法时,它就不再受到虚拟机关于结构和安全限制方面的约束,它既可以访问虚拟机的运行期数据区,也可以使用本地处理 器以及任何类型的栈。例如,本地栈是一个C语言的栈,那么当C程序调用C函数时,函数的参数以某种顺序被压入栈,结果则返回给调用函数。在实现Java虚 拟机时,本地方法接口使用的是C语言的模型栈,那么它的本地方法栈的调度与使用则完全与C语言的栈相同。

3Java虚拟机的运行过程

上面对虚拟机的各个部分进行了比较详细的说明,下面通过一个具体的例子来分析它的运行过程。虚拟机通过调用某个指定类的方法main启动,传递给main一个字符串数组参数,使指定的类被装载,同时链接该类所使用的其它的类型,并且初始化它们。例如对于程序:

class HelloApp

{

public static void main(String[] args)

{

System.out.println("Hello World!");

for (int i = 0; i < args.length; i++ )

{

System.out.println(args[i]);

}

}

}

编译后在命令行模式下键入: java HelloApp run virtual machine

将通过调用HelloApp的方法main来启动java虚拟机,传递给main一个包含三个字符串"run"、"virtual"、"machine"的数组。现在我们略述虚拟机在执行HelloApp时可能采取的步骤。

开始试图执行类HelloApp的main方法,发现该类并没有被装载,也就是说虚拟机当前不包含该类的二进制代表,于是虚拟机使用 ClassLoader试图寻找这样的二进制代表。如果这个进程失败,则抛出一个异常。类被装载后同时在main方法被调用之前,必须对类 HelloApp与其它类型进行链接然后初始化。链接包含三个阶段:检验,准备和解析。检验检查被装载的主类的符号和语义,准备则创建类或接口的静态域以 及把这些域初始化为标准的默认值,解析负责检查主类对其它类或接口的符号引用,在这一步它是可选的。类的初始化是对类中声明的静态初始化函数和静态域的初 始化构造方法的执行。一个类在初始化之前它的父类必须被初始化。整个过程如下:

篇二:深入理解Java虚拟机学习笔记

JVM的自动内存管理机制

一 如何划分JVM内存

JVM所管理的内存在运行时会被分为这样几个数据区:虚拟机栈区,堆区,方法区,本地方法栈,程序计数器。

程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,每条线程都需要有一个独立的程序计数器,各条线程之间程序计数器互不影响,独立存储,是线程隔离的。程序计数器所在的内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

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

局部变量表存放了8种基本数据类型、对象的引用和returnAddress。局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。

本地方法栈,作用与虚拟机栈区是相似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。

堆,Java堆,也称GC堆,是最大的一块,是被线程共享的区域,在虚拟机启动时创建。所有类的实例(对象)和数组都是在堆上分配内存的,堆内存由存活和死亡的对象,空闲碎片区组成,对象所占的堆内存是由自动内存管理系统回收。(数组是一种对象)

从内存回收角度来看,Java堆还可以细分为新生代和老年代;甚至还可以分为Eden空间、 From Survivor空间、To Survivor空间等。

从内存分配角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。 Java堆可以处于物理上不连续的内存中,只要逻辑上连续即可。

方法区在JVM中也是一个非常重要的区域,在HotSpot虚拟机上,方法区被称为“永久代”。虽然Java虚拟机规范把方法区描述为堆区的一个逻辑部分,但还是要区分来对待。方法区用于存储已被JVM加载的类信息(包括类的名称、方法信息、字段信息)、类变量(静态变量)、常量、即时编译器编译后的代码等数据。虽然方法区中有些数据是线程隔离的,但是编译器编译后的代码等数据,是线程共享的。

除了和Java堆一样不需要连续的内存和可以固定大小或者可扩展外,还可以选择不实现垃圾收集。但不并非方法区就不要内存回收了,方法区的内存回收只要针对常量池的回收和对类型的卸载。

运行时常量池,是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用。

二 对象的创建

在语言层面上,创建对象通常仅仅是一个new关键字而已。但在虚拟机中,对象的创建过程大致分为以下四步:

第一步,检查类加载。虚拟机遇到一条new指令时,首先需要去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

第二步,分配内存。在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可完全确定。分配方式大致有两种:指针碰撞和空闲列表。除了考虑如何划分可用空间之外,还需要考虑在并发的情况下的线程安全。解决方案有两种:一种是对分配空间的动作进行同步处理;另外一种本地线程分配缓冲(TLAB)。

第三步,内存空间初始化。如果使用TLAB,这一过程可以提前至TLAB分配时进行。

第四步,必要的设置。初始化后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。

上面的工作完成后,从虚拟机角度来看,一个新的对象已经产生了,但在程序员的角度来看,对象的创建才刚刚开始,init方法还没有执行,所有字段都还为零。所以,一般来说,执行new指令后会接着执行init方法,把对象按照程序员的意愿进行初始化,这样一个可用的对象才算完全产生出来。

三 对象的内存布局

在HotSpot虚拟机中,对象在内存中存储的布局分为3块:对象头、实例数据和对齐填充。 对象头包括两部分信息,一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁等;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型字段内容。无论是从父类继承下来,还是在子类中定义的,都需要记录。这部分的存储顺序会受到虚拟机分配策略参数和字段在Java源码中的顺序的影响。

对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotspotVM的自动内存管理系统要求对象起始地址必须是8字节的整数倍。

四 对象的访问定位

建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用地址,并没有定义这个引用应该通过那种方式去定位,访问到Java堆中的对象位置,因此不同的虚拟机实现的访问方式可能不同,主流的方式有两种:使用句柄和直接指针。

句柄访问方式:Java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。

指针访问方式:reference变量中直接存储的就是对象的地址,而Java堆对象一部分存储了对象实例数据,另外一部分存储了到对象类型数据的指针。

这两种访问对象的方式各有优势,使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就Hotspot虚拟机而言,它使用的是第二种方式(直接指针访问)。

五 JVM的内存配置参数

-XX:+<option> 启用选项

-XX:-<option> 不启用选项

-XX:<option>=<value> 将option参数的值设置为value

堆设置

-Xms :初始堆大小

-Xmx :最大堆大小

-Xmn:新生代大小。通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个

Survivor

空间。实际可用空间为 = Eden + 1 个 Survivor,即 90%。

-XX:NewSize=n :设置年轻代大小

-XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4

-XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5

-XX:PermSize=n 永久代(方法区)的初始大小

-XX:MaxPermSize=n :设置永久代最大大小

-Xss 设定栈容量;对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,因为在HotSpot中并不区分虚拟机和本地方法栈。 -XX:PretenureSizeThreshold (该设置只对Serial和ParNew收集器生效) 可以设置进入老生代的大小限制

-XX:MaxTenuringThreshold=n(默认15)垃圾最大年龄如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率 该参数只有在串行GC时才有效。

收集器设置

-XX:+UseSerialGC :设置串行收集器

-XX:+UseParallelGC :设置并行收集器

-XX:+UseParallelOldGC :设置并行年老代收集器

-XX:+UseConcMarkSweepGC :设置并发收集器

垃圾回收统计信息

-XX:+PrintHeapAtGC 打印GC的heap详情

-XX:+PrintGCDetails 打印GC详情

-XX:+PrintGCTimeStamps 打印GC时间信息

-XX:+PrintTenuringDistribution 打印年龄信息等

-XX:+HandlePromotionFailure 老年代分配担保(true or false)

并行收集器设置

-XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。 -XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间

-XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) 并发收集器设置

-XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。

-XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

其他

-XX:PermSize=10M和-XX:MaxPermSize=10M限制方法区大小。

-XX:MaxDirectMemorySize=10M指定DirectMemory(直接内存)容量,如果不指定,则默认与JAVA堆最大值(-Xmx指定)一样。

-XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照(.hprof文件)以便时候进行分析(比如Eclipse Memory Analysis)。

六 JVM的堆内存(heap) 简单的来说Java的堆内存分为两块:permant space(持久代/方法区)和 heap space。 持久代/方法区:主要存储结构信息的地方,比如方法体,同时也是存储静态变量,以及静态代码块的区域,构造函数,常量池,接口初始化等等 。与垃圾收集器要收集的Java对象关系不大。

而heapspace分为新生代和年老代。

新生代(由一个Eden区和俩个survivor区组成):对象被创建时(new)的对象通常被放在新生代的Eden区(除了一些占据内存比较大的对象直接进老年代),经过一次GC收集后,存活下来的会被复制到survivor区(一个满了,就全部移动到另外一个大的中,但要保证其中一个survivor为空),经过一定的Minor GC(针对新生代的内存回收)还活着的对象会被移动到年老代(一些具体的移动细节省略)。

年老代:就是上述新生代移动过来的和一些比较大的对象。FullGC是针对年老代的回收 新生代的垃圾回收叫 Minor GC, 年老代的垃圾回收叫 Full GC。

在年轻代中经历了多次垃圾回收后仍然存活的对象,就会被复制到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。为了做到这点,虚拟机给每个对象定义了一个对象年龄计数器。如果对象在Eden出生并经过一次 Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor空间中每熬过一次 Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到年老代中。对象晋升年老代的年龄阈值,可以通过参数

-XX:MaxTenuringThreshold设置。

年老代溢出原因:循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存。

持久代溢出原因 :动态加载了大量Java类而导致溢出。

堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定。 默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数

–XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。

默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。

JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。

因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。

七 垃圾回收(GC)

垃圾回收主要针对的是堆区的回收,因为栈区的内存是随着线程而释放的。垃圾回收线程在


jvm虚拟机详解》由:免费论文网互联网用户整理提供;
链接地址:http://www.csmayi.cn/meiwen/23386.html
转载请保留,谢谢!
相关文章