深入理解java虚拟机

深入理解java虚拟机
深入理解java虚拟机

深入理解java虚拟机

(一)虚拟机内存划分

Java虚拟机在执行Java程序时,会把它管理的内存划分为若干个不同的数据区。这些区域有不同的特性,起不同的作用。它们有各自的创建时间,销毁时间。有的区域随着进程的启动而创建,随着进程结束而销毁,有的则始终贯穿虚拟机整个生命周期。

Java虚拟机运行时内存区域主要分为七部分,分别是:程序计数器,Java虚拟机栈,本地方法栈,方法区,Java堆,运行时常量池,直接内存。

如上图所示(图片来源于网络):

蓝色区域包裹的部分为运行时几个数据区域:

白色的部分为线程私有的,既随着线程的启动而创建。每个线程都拥有各自的一份内存区域。它们是:JAVA栈(JAVA STACK),本地方法栈(NATIVE METHOD STACK),和程序计数器(PROGRAM COUNTER REGISTER)。

黄色部分是线程共享的,所有的线程共享该区域的内容。他们是:

方法区(METHOD AREA),堆(HEAP)。

我们分别来介绍这些区域。

(1)程序计数器(program counter register)

学过计算机组成原理的都知道计算机处理器中的程序计数器。当处理器执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。

处理器的程序计数器是指寄存器,而java程序计数器是指一小块内存空间。java代码编译字节码之后,虚拟机会一行一行的解释字节码,并翻印成本地代码。这个程序计数器盛放的就是当前线程所执行字节码的行号的指示器。在虚拟机概念模型中,字节码解释器工作室就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理等都依赖于它。

Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,因此为了线程切换后还能恢复执行位置,每条线程都需要一个独立的程序计数器。

如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Java Native方法,这个计数器值为空。

而且程序计数器是Java虚拟机中没有规定任何OutOfMemoryError的区域。

(2)虚拟机栈

Java虚拟机栈(VM Stack)也是线程私有的,因此它的生命周期也和线程相同。它存放的是Java方法执行时的数据,既描述的是Java方法执行的内存模型:每个方法开始执行的时候,都会创建一个栈帧(Stack Frame)用于储存局部变量表、栈操作数、动态链接、方法出口等信息。每个方法从调用到执行完成就对应一个栈帧在虚拟机栈中入栈到出栈的过程。经常有人把Java内存分为堆内存和栈内存,这种是比较粗糙的分法,很大原因是大多数程序‘猿’最关注的,与对象内存分配最密切的区域就是堆和栈。局部变量表存放的是编译器可知的各种基本数据类型(boolean 、byte、int、long、char、short、float、double)、对象引用(reference类型)和returnAddress类型(它指向了一条字节码指令的地址)。其中64bit长度的long和double会占用两个局部变量空间(Slot),其余的数据类型只占用一个。局部变量表所需的内存空间是在编译时期确定的,在方法运行期间不会改变局部变量表的大小。在Java虚拟机规范中,对这部分区域规定了两种异常:1、当一个线程的栈深度大于虚拟机所允许的深度的时候,将会抛出StackOverflowError异常; 2、如果当创建一个新的线程时无法申请到足够的内存,则会抛出OutOfMemeryError异常。

(3)本地方法栈

本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是十分相似的,他们之间的区别不过是虚拟机栈为Java方法字节码服务,而本地方法栈则为Native方法服务。在虚拟机规范中对本地方法使用的语言和使用方法与数据结构没有强制规定,因此具体的虚拟机可

以自由实现它。Sun HotSpot虚拟机把本地方法栈和虚拟机栈合二为一。和虚拟机栈一样,本地方法栈也会抛出OutOfMemoryError和StackOverflowError异常。

接下来我们介绍的都是所有线程共享的区域了。

(4)堆(heap)

堆是虚拟机中最大的一块内存区域了,被所有线程共享,在虚拟机启动时创建。它的目的便是存放对象实例。堆是垃圾收集器管理的主要区域,因此很多时候也被成为…GC’堆(Garbage Collected Heap)。

从垃圾回收的角度来讲,现在的收集器包括HotSpot都采用分代收集算法,所以堆又可以分为:新生代(Young)和老年代(Tenured),再细致一点,新生代又可分为Eden、From Survivor 空间和To Survivor空间。从内存分配的角度来讲,又可以分为若干个线程私有的分配缓冲区(Thread Local Allocation Buffer ,TLAB)。当堆空间不足切无法扩展,会抛出OutOfMemoryError异常。

(5)方法区(Method Area)

与Java堆一样,是各个线程共享的内存区域,用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它有个别名叫做非堆Non-Heap。对于HotSpot 开发者来说,很多人称它为“永久代”(Permanent Generation),但是两者并不等价,仅仅是因为HotSpot虚拟机设计团队把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已,这样HotSpot的垃圾收集器可以向管理堆一样管理这部分内存。但是因为永久代有“-XX:MaxPermSize的上限,使其更容易内存溢出。因此在JDK1.7的HotSpot中,已经把原本放在永久代的字符串常量池移出去了。

当方法区无法满足内存分配需求的时候,会抛出OutOfMemoryError异常。

(6)常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中出了类的版本、字段、方法、接口等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译器生成的各种字面量和符号引用,这些内容将在类加载后进入方法区存放。

运行时常量池相对于Class文件常量池的另外一个重要特征是具有动态性,运行期间也可能有新的常量池放入持重,比如String.intern()方法。

运行时常量池属于方法区一部分,自然会抛出OutOfMemoryError异常。

(7)直接内存

直接内存(Direct Memory)不属于虚拟机中定义的内存区域,而是堆外内存。

JDK1.4 中新加入了NIO(new Input/Output)类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过Java堆中的

DirectByteBuffer对象作为这快内存的引用进行操作。这样能在一些场景中显著提高新能性能。如果直接内存不足时,会抛出OutOfMemoryError异常。

GC在什么时候对什么做了什么?

要回答这个问题,先了解下GC的发展史、jvm运行时数据区的划分、jvm内存分配策略、jvm垃圾收集算法等知识。

先说下jvm运行时数据的划分,粗暴的分可以分为堆区(Heap)和栈区(Stack),但jvm 的分法实际上比这复杂得多,大概分为下面几块:

1、程序计数器(Program Conuter Register)

程序计数器是一块较小的内存空间,它是当前线程执行字节码的行号指示器,字节码解释工作器就是通过改变这个计数器的值来选取下一条需要执行的指令。它是线程私有的内存,也是唯一一个没有OOM异常的区域。

2、Java虚拟机栈区(Java Virtual Machine Stacks)

也就是通常所说的栈区,它描述的是Java方法执行的内存模型,每个方法被执行的时候都创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等。每个方法被调用到完成,相当于一个栈帧在虚拟机栈中从入栈到出栈的过程。此区域也是线程私有的内存,可能抛出两种异常:如果线程请求的栈深度大于虚拟机允许的深度将抛出StackOverflowError;如果虚拟机栈可以动态的扩展,扩展到无法动态的申请到足够的内存时会抛出OOM异常。

3、本地方法栈(Native Method Stacks)

本地方法栈与虚拟机栈发挥的作用非常相似,区别就是虚拟机栈为虚拟机执行Java方法,本地方法栈则是为虚拟机使用到的Native方法服务。

4、堆区(Heap)

所有对象实例和数组都在堆区上分配,堆区是GC主要管理的区域。堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。此块内存为所有线程共享区域,当堆中没有足够内存完成实例分配时会抛出OOM异常。

5、方法区(Method Area)

方法区也是所有线程共享区,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。GC在这个区域很少出现,这个区域内存回收的目标主要是对常量池的回收和类型的卸载,回收的内存比较少,所以也有称这个区域为永久代(Permanent Generation)的。当方法区无法满足内存分配时抛出OOM异常。

6、运行时常量池(Runtime Constant Pool)

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

垃圾收集(Garbage Collection)并不是Java独有的,最早是出现在Lisp语言中,它做的事就是自动管理内存,也就是下面三个问题:

1、什么时候回收

2、哪些内存需要回收

3、如何回收

1、什么时候回收?

上面说到GC经常发生的区域是堆区,堆区还可以细分为新生代、老年代,新生代还分为一个Eden区和两个Survivor区。

1.1对象优先在Eden中分配,当Eden中没有足够空间时,虚拟机将发生一次Minor GC,因为Java大多数对象都是朝生夕灭,所以Minor GC非常频繁,而且速度也很快;

1.2 Full GC,发生在老年代的GC,当老年代没有足够的空间时即发生Full GC,发生Full GC一般都会有一次Minor GC。大对象直接进入老年代,如很长的字符串数组,虚拟机提供一个-XX:PretenureSizeThreadhold参数,令大于这个参数值的对象直接在老年代中分配,避免在Eden区和两个Survivor区发生大量的内存拷贝;

1.3发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,如果大于,则进行一次Full GC,如果小于,则查看HandlePromotionFailure设置是否允许担保失败,如果允许,那只会进行一次Minor GC,如果不允许,则改为进行一次Full GC。

2、哪些内存需要回收

jvm对不可用的对象进行回收,哪些对象是可用的,哪些是不可用的?Java并不是采用引用计数算法来判定对象是否可用,而是采用根搜索算法(GC Root Tracing),当一个对象到GC Roots没有任何引用相连接,用图论来说就是从GC Roots到这个对象不可达,则证明此对象是不可用的,说明此对象可以被GC。对于这些不可达对象,也不是一下子就被GC,而是至少要经历两次标记过程:如果对象在进行根搜索算法后发现没有与GC Roots

相连接的引用链,那它将会第一次标记并且进行一次筛选,筛选条件是此对象有没有必要执行finalize()方法,当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用执行过一次,这两种情况都被视为没有必要执行finalize()方法,对于没有必要执行finalize()方法的将会被GC,对于有必要有必要执行的,对象在finalize()方法中可能会自救,也就是重新与引用链上的任何一个对象建立关联即可。

3、如何回收

选择不同的垃圾收集器,所使用的收集算法也不同。

在新生代中,每次垃圾收集都发现有大批对象死去,只有少量存活,则使用复制算法,新生代内存被分为一个较大的Eden区和两个较小的Survivor区,每次只使用Eden区和一个Survivor区,当回收时将Eden区和Survivor还存活着的对象一次性的拷贝到另一个Survivor 区上,最后清理掉Eden区和刚才使用过的Survivor区,Eden和Survivor的默认比例是8:1,可以使用-XX:SurvivorRatio来设置该比例。

而老年代中对象存活率高,没有额外的空间对它进行分配担保,必须使用“标记-清理”或“标记-整理”算法。

内存管理与回收

1、Java虚拟机运行时的数据区

2、常用的内存区域调节参数

-Xms:初始堆大小,默认为物理内存的1/64(<1GB);默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制

-Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制

-Xmn:新生代的内存空间大小,注意:此处的大小是(eden+ 2 survivor space)。与jmap -heap中显示的New gen是不同的。整个堆大小=新生代大小+ 老生代大小+ 永久代大小。

在保证堆大小不变的情况下,增大新生代后,将会减小老生代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-XX:SurvivorRatio:新生代中Eden区域与Survivor区域的容量比值,默认值为8。两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10。-Xss:每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。应根据应用的线程所需内存大小进行适当调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,

经验值在3000~5000左右。一般小的应用,如果栈不是很深,应该是128k够用的,大的应用建议使用256k。这个选项对性能影响比较大,需要严格的测试。和threadstacksize 选项解释很类似,官方文档似乎没有解释,在论坛中有这样一句话:"-Xss is translated in a VM flag named ThreadStackSize‖一般设置这个值就可以了。

-XX:PermSize:设置永久代(perm gen)初始值。默认值为物理内存的1/64。

-XX:MaxPermSize:设置持久代最大值。物理内存的1/4。

3、内存分配方法

1)堆上分配2)栈上分配3)堆外分配(DirectByteBuffer或直接使用Unsafe.allocateMemory,但不推荐这种方式)

4、监控方法

1)系统程序运行时可通过jstat –gcutil来查看堆中各个内存区域的变化以及GC的工作状态;

2)启动时可添加-XX:+PrintGCDetails –Xloggc:输出到日志文件来查看GC的状况;

3)jmap –heap可用于查看各个内存空间的大小;

5)断代法可用GC汇总

一、新生代可用GC

1)串行GC(Serial Copying):client模式下默认GC方式,也可通过-XX:+UseSerialGC 来强制指定;默认情况下eden、s0、s1的大小通过-XX:SurvivorRatio来控制,默认为8,含义

为eden:s0的比例,启动后可通过jmap –heap [pid]来查看。

默认情况下,仅在TLAB或eden上分配,只有两种情况下会在老生代分配:

1、需要分配的内存大小超过eden space大小;

2、在配置了PretenureSizeThreshold的情况下,对象大小大于此值。

默认情况下,触发Minor GC时:

之前Minor GC晋级到old的平均大小<老生代的剩余空间

默认情况下,新生代对象晋升到老生代的规则:

1、经历多次minor gc仍存活的对象,可通过以下参数来控制:以MaxTenuringThreshold值为准,默认为15。

2、to space放不下的,直接放入老生代;

2)并行GC(ParNew):CMS GC时默认采用,也可采用-XX:+UseParNewGC强制指定;垃圾回收的时候采用多线程的方式。

3)并行回收GC(Parallel Scavenge):server模式下默认的GC方式,也可采用-XX:+UseParallelGC强制指定;eden、s0、s1的大小可通过-XX:SurvivorRatio来控制,但默认情况下

以-XX:InitialSurivivorRatio为准,此值默认为8,代表的为新生代大小: s0,这点要特别注意。

默认情况下,当TLAB、eden上分配都失败时,判断需要分配的内存大小是否>= eden space的一半大小,如是就直接在老生代上分配;

默认情况下的垃圾回收规则:

1、在回收前PS GC会先检测之前每次PS GC时,晋升到老生代的平均大小是否大于老生代的剩余空间,如大于则直接触发full GC;

2、在回收后,也会按照上面的规则进行检测。

默认情况下的新生代对象晋升到老生代的规则:

1、经历多次minor gc仍存活的对象,可通过以下参数来控制:AlwaysTenure,默认false,表示只要minor GC时存活,就晋升到老生代;NeverTenure,默认false,表示永不晋升到老生代;上面两个都没设置的情冴下,如UseAdaptiveSizePolicy,启动时以InitialTenuringThreshold值作为存活次数的阈值,在每次psgc后会动态调整,如不使用UseAdaptiveSizePolicy,则以MaxTenuringThreshold为准。

2、to space放不下的,直接放入老生代。

在回收后,如UseAdaptiveSizePolicy,PS GC会根据运行状态动态调整eden、to 以及TenuringThreshold的大小。如果不希望动态调整可设置-XX:-UseAdaptiveSizePolicy。如希望跟踪每次的变化情况,可在启劢参数上增加:PrintAdaptiveSizePolicy。

二、老生代可用GC

1、串行GC(Serial Copying):client方式下默认GC方式,可通过-XX:+UseSerialGC 强制指定。

触发机制汇总:

1)old gen空间不足;

2)perm gen空间不足;

3)minor gc时的悲观策略;

4)minor GC后在eden上分配内存仍然失败;

5)执行heap dump时;

6)外部调用System.gc,可通过-XX:+DisableExplicitGC来禁止。

2、并行回收GC(Parallel Scavenge):server模式下默认GC方式,可通过-XX:+UseParallelGC强制指定;并行的线程数为当cpu core<=8 ? cpu core : 3+(cpu core*5)/8或通过-XX:ParallelGCThreads=x来强制指定。如ScavengeBeforeFullGC 为true(默认值),则先执行minor GC。

3、并行Compacting:可通过-XX:+UseParallelOldGC强制指定。

4、并发CMS:可通过-XX:+UseConcMarkSweepGC来强制指定。并发的线程数默认为:( 并行GC线程数+3)/4,也可通过ParallelCMSThreads指定。

触发机制:

1、当老生代空间的使用到达一定比率时触发;

Hotspot V 1.6中默认为65%,可通过PrintCMSInitiationStatistics(此参数在V 1.5中不能用)来查看这个值到底是多少;可通过CMSInitiatingOccupancyFraction来强制指定,默认值并不是赋值在了这个值上,是根据如下公式计算出来的:((100 - MinHeapFreeRatio) +(double)(CMSTriggerRatio * MinHeapFreeRatio) / 100.0)/ 100.0; 其中,MinHeapFreeRatio默认值:40 CMSTriggerRatio默认值:80。

2、当perm gen采用CMS收集且空间使用到一定比率时触发;

perm gen采用CMS收集需设置:-XX:+CMSClassUnloadingEnabled Hotspot V 1.6中默认为65%;可通过CMSInitiatingPermOccupancyFraction来强制指定,同样,它是根据如下公式计算出来的:((100 - MinHeapFreeRatio) +(double)(CMSTriggerPermRatio* MinHeapFreeRatio) / 100.0)/ 100.0; 其中,MinHeapFreeRatio默认值:40 CMSTriggerPermRatio默认值:80。

3、Hotspot根据成本计算决定是否需要执行CMS GC;可通过-XX:+UseCMSInitiatingOccupancyOnly来去掉这个动态执行的策略。

4、外部调用了System.gc,且设置了ExplicitGCInvokesConcurrent;需要注意,在hotspot 6中,在这种情况下如应用同时使用了NIO,可能会出现bug。

6、GC组合

1)默认GC组合

2)可选的GC组合

7、GC监测

1)jstat –gcutil [pid] [intervel] [count]

2)-verbose:gc // 可以辅助输出一些详细的GC信息;-XX:+PrintGCDetails // 输出GC详细信息;-XX:+PrintGCApplicationStoppedTime // 输出GC造成应用暂停的时间

-XX:+PrintGCDateStamps // GC发生的时间信息;-XX:+PrintHeapAtGC // 在GC 前后输出堆中各个区域的大小;-Xloggc:[file] // 将GC信息输出到单独的文件中,建议都加上,这个消耗不大,而且对查问题和调优有很大的帮助。gc的日志拿下来后可使用GCLogViewer或gchisto进行分析。

3)图形化的情况下可直接用jvisualvm进行分析。

4)查看内存的消耗状况

(1)长期消耗,可以直接dump,然后MAT(内存分析工具)查看即可

(2)短期消耗,图形界面情况下,可使用jvisualvm的memory profiler或jprofiler。

8、系统调优方法

步骤:1、评估现状2、设定目标3、尝试调优4、衡量调优5、细微调整

设定目标:

1)降低Full GC的执行频率?

2)降低Full GC的消耗时间?

3)降低Full GC所造成的应用停顿时间?

4)降低Minor GC执行频率?

5)降低Minor GC消耗时间?

例如某系统的GC调优目标:降低Full GC执行频率的同时,尽可能降低minor GC的执行频率、消耗时间以及GC对应用造成的停顿时间。

衡量调优:

1、衡量工具

1)打印GC日志信息:-XX:+PrintGCDetails –XX:+PrintGCApplicationStoppedTime -Xloggc: {文件名} -XX:+PrintGCTimeStamps

2)jmap:(由于每个版本jvm的默认值可能会有改变,建议还是用jmap首先观察下目前每个代的内存大小、GC方式)

3)运行状况监测工具:jstat、jvisualvm、sar、gclogviewer

2、应收集的信息

1)minor gc的执行频率;full gc的执行频率,每次GC耗时多少?

2)高峰期什么状况?

3)minor gc回收的效果如何?survivor的消耗状况如何,每次有多少对象会进入老生代?4)full gc回收的效果如何?(简单的memory leak判断方法)

5)系统的load、cpu消耗、qps or tps、响应时间

QPS每秒查询率:是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。在因特网上,作为域名服务器的机器性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。

TPS(Transaction Per Second):每秒钟系统能够处理的交易或事务的数量。

尝试调优:

注意Java RMI的定时GC触发机制,可通过:-XX:+DisableExplicitGC来禁止或通过-Dsun.rmi.dgc.server.gcInterval=3600000来控制触发的时间。

1)降低Full GC执行频率–通常瓶颈

老生代本身占用的内存空间就一直偏高,所以只要稍微放点对象到老生代,就full GC了;通常原因:系统缓存的东西太多;

例如:使用oracle 10g驱动时preparedstatement cache太大;

查找办法:现执行Dump然后再进行MAT分析;

(1)Minor GC后总是有对象不断的进入老生代,导致老生代不断的满

通常原因:Survivor太小了

系统表现:系统响应太慢、请求量太大、每次请求分配的内存太多、分配的对象太大...

查找办法:分析两次minor GC之间到底哪些地方分配了内存;

利用jstat观察Survivor的消耗状况,-XX:PrintHeapAtGC,输出GC前后的详细信息;对于系统响应慢可以采用系统优化,不是GC优化的内容;

(2)老生代的内存占用一直偏高

调优方法:①扩大老生代的大小(减少新生代的大小或调大heap的大小);

减少new注意对minor gc的影响并且同时有可能造成full gc还是严重;

调大heap注意full gc的时间的延长,cpu够强悍嘛,os是32 bit的吗?

②程序优化(去掉一些不必要的缓存)

(3)Minor GC后总是有对象不断的进入老生代

前提:这些进入老生代的对象在full GC时大部分都会被回收

调优方法:

①降低Minor GC的执行频率;

②让对象尽量在Minor GC中就被回收掉:增大Eden区、增大survivor、增大TenuringThreshold;注意这些可能会造成minor gc执行频繁;

③切换成CMS GC:老生代还没有满就回收掉,从而降低Full GC触发的可能性;

④程序优化:提升响应速度、降低每次请求分配的内存、

(4)降低单次Full GC的执行时间

通常原因:老生代太大了...

调优方法:1)是并行GC吗?2)升级CPU 3)减小Heap或老生代

(5)降低Minor GC执行频率

通常原因:每次请求分配的内存多、请求量大

通常办法:1)扩大heap、扩大新生代、扩大eden。注意点:降低每次请求分配的内存;横向增加机器的数量分担请求的数量。

(6)降低Minor GC执行时间

通常原因:新生代太大了,响应速度太慢了,导致每次Minor GC时存活的对象多

通常办法:1)减小点新生代吧;2)增加CPU的数量、升级CPU的配置;加快系统的响应速度

细微调整:

首先需要了解以下情况:

①当响应速度下降到多少或请求量上涨到多少时,系统会宕掉?

②参数调整后系统多久会执行一次Minor GC,多久会执行一次Full GC,高峰期会如何?需要计算的量:

①每次请求平均需要分配多少内存?系统的平均响应时间是多少呢?请求量是多少、多常时间执行一次Minor GC、Full GC?

②现有参数下,应该是多久一次Minor GC、Full GC,对比真实状况,做一定的调整;

必杀技:提升响应速度、降低每次请求分配的内存?

9、系统调优举例

现象:1、系统响应速度大概为100ms;2、当系统QPS增长到40时,机器每隔5秒就执行一次minor gc,每隔3分钟就执行一次full gc,并且很快就一直full GC了;4、每次Full gc后旧生代大概会消耗400M,有点多了。

解决方案:解决Full GC次数过多的问题

(1)降低响应时间或请求次数,这个需要重构,比较麻烦;——这个是终极方法,往往能够顺利的解决问题,因为大部分的问题均是由程序自身造成的。

(2)减少老生代内存的消耗,比较靠谱;——可以通过分析Dump文件(jmap dump),并利用MAT查找内存消耗的原因,从而发现程序中造成老生代内存消耗的原因。

(3)减少每次请求的内存的消耗,貌似比较靠谱;——这个是海市蜃楼,没有太好的办法。

(4)降低GC造成的应用暂停的时间——可以采用CMS GS垃圾回收器。参数设置如下:

-Xms1536m -Xmx1536m -Xmn700m -XX:SurvivorRatio=7 -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection

-XX:CMSMaxAbortablePrecleanTime=1000 -XX:+CMSClassUnloadingEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:+DisableExplicitGC

(5)减少每次minor gc晋升到old的对象。可选方法:1)调大新生代。2)调大Survivor。3)调大TenuringThreshold。

调大Survivor:当前采用PS GC,Survivor space会被动态调整。由于调整幅度很小,导致了经常有对象直接转移到了老生代;于是禁止Survivor区的动态调整了,

-XX:-UseAdaptiveSizePolicy,并计算Survivor Space需要的大小,于是继续观察,并做微调…。最终将Full GC推迟到2小时1次。

10、垃圾回收的实现原理

内存回收的实现方法:1)引用计数:不适合复杂对象的引用关系,尤其是循环依赖的场景。2)有向图Tracing:适合于复杂对象的引用关系场景,Hotspot采用这种。常用算法:Copying、Mark-Sweep、Mark-Compact。

Hotspot从root set开始扫描有引用的对象并对Reference类型的对象进行特殊处理。

以下是Root Set的列表:1)当前正在执行的线程;2)全局/静态变量;3)JVM Handles;4)JNI 【Java Native Interface 】Handles;

另外:minor GC只扫描新生代,当老生代的对象引用了新生代的对象时,会采用如下的处理方式:在给对象赋引用时,会经过一个write barrier的过程,以便检查是否有老生代引用新生代对象的情况,如有则记录到remember set中。并在minor gc时,remember set指向的新生代对象也作为root set。

新生代串行GC(Serial Copying):

新生代串行GC(Serial Copying)完整内存的分配策略:

1)首先在TLAB(本地线程分配缓冲区)上尝试分配;

2)检查是否需要在新生代上分配,如需要分配的大小小于PretenureSizeThreshold,则在eden区上进行分配,分配成功则返回;分配失败则继续;

3)检查是否需要尝试在老生代上分配,如需要,则遍历所有代并检查是否可在该代上分配,如可以则进行分配;如不需要在老生代上尝试分配,则继续;

4)根据策略决定执行新生代GC或Full GC,执行full gc时不清除soft Ref;

5)如需要分配的大小大于PretenureSizeThreshold,尝试在老生代上分配,否则尝试在新生代上分配;

6)尝试扩大堆并分配;

7)执行full gc,并清除所有soft Ref,按步骤5继续尝试分配。

新生代串行GC(Serial Copying)完整内存回收策略

1)检查to是否为空,不为空返回false;

2)检查老生代剩余空间是否大于当前eden+from已用的大小,如大于则返回true,如小于且HandlePromotionFailure为true,则检查剩余空间是否大于之前每次minor gc 晋级到老生代的平均大小,如大于返回true,如小于返回false。

3)如上面的结果为false,则执行full gc;如上面的结果为true,执行下面的步骤;

4)扫描引用关系,将活的对象copy到to space,如对象在minor gc中的存活次数超过tenuring_threshold或分配失败,则往老生代复制,如仍然复制失败,则取决于

HandlePromotionFailure,如不需要处理,直接抛出OOM,并退出vm,如需处理,则保持这些新生代对象不动;

新生代可用GC-PS

完整内存分配策略

1)先在TLAB上分配,分配失败则直接在eden上分配;

2)当eden上分配失败时,检查需要分配的大小是否>= eden space的一半,如是,则直接在老生代分配;

3)如分配仍然失败,且gc已超过频率,则抛出OOM;

4)进入基本分配策略失败的模式;

5)执行PS GC,在eden上分配;

6)执行非最大压缩的full gc,在eden上分配;

7)在旧生代上分配;

8)执行最大压缩full gc,在eden上分配;

9)在旧生代上分配;

10)如还失败,回到2。

最悲惨的情况,分配触发多次PS GC和多次Full GC,直到OOM。

完整内存回收策略

1)如gc所执行的时间超过,直接结束;

2)先调用invoke_nopolicy

2.1 先检查是不是要尝试scavenge;

2.1.1 to space必须为空,如不为空,则返回false;

2.1.2 获取之前所有minor gc晋级到old的平均大小,并对比目前eden+from已使用的大小,取更小的一个值,如老生代剩余空间小于此值,则返回false,如大于则返回true;

2.2 如不需要尝试scavenge,则返回false,否则继续;

2.3 多线程扫描活的对象,并基亍copying算法回收,回收时相应的晋升对象到旧生代;

2.4 如UseAdaptiveSizePolicy,那么重新计算to space和tenuringThreshold 的值,并调整。

3)如invoke_nopolicy返回的是false,或之前所有minor gc晋级到老生代的平均大小>旧生代的剩余空间,那么继续下面的步骤,否则结束;

4)如UseParallelOldGC,则执行PSParallelCompact,如不是UseParallelOldGC,则执行PSMarkSweep。

老生代并行CMS GC:

优缺点:

1)大部分时候和应用并发进行,因此只会造成很短的暂停时间;

2)浮动垃圾,没办法,所以内存空间要稍微大一点;

3)内存碎片,-XX:+UseCMSCompactAtFullCollection来解决;

4)争抢CPU,这GC方式就这样;

5)多次remark,所以总的gc时间会比并行的长;

6)内存分配,free list方式,so性能稍差,对minor GC会有一点影响;

7)和应用并发,有可能分配和回收同时,产生竞争,引入了锁,JVM分配优先。11、TLAB的解释

堆内的对象数据是各个线程所共享的,所以当在堆内创建新的对象时,就需要进行锁操作。锁操作是比较耗时,因此JVM为每个线在堆上分配了一块―自留地‖——TLAB(全称是Thread Local Allocation Buffer),位于堆内存的新生代,也就是Eden区。每个线程在创建新的对象时,会首先尝试在自己的TLAB里进行分配,如果成功就返回,失败了再到共享的Eden区里去申请空间。在线程自己的TLAB区域创建对象失败一般有两个原因:一是对象太大,二是自己的TLAB区剩余空间不够。通常默认的TLAB区域大小是Eden区域的1%,当然也可以手工进行调整,对应的JVM参数是

了解JVM的系统结构,再来看看JVM内存回收问题了——

Sun的JVM Generational Collecting(垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)

如上图所示,为Java堆中的各代分布。

1. Young(年轻代)

年轻代分三个区。一个Eden 区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor 区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

2. Tenured(年老代)

年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。

3. Perm(持久代)

用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

举个例子:当在程序中生成对象时,正常对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。

通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的Method Area,不属于Heap。

了解完这些之后,以下的转载一热衷于钻研技术的哥们Richen Wang关于内存管理的一些建议——

1、手动将生成的无用对象,中间对象置为null,加快内存回收。

2、对象池技术如果生成的对象是可重用的对象,只是其中的属性不同时,可以考虑采用对象池来较少对象的生成。如果有空闲的对象就从对象池中取出使用,没有再生成新的对象,大大提高了对象的复用率。

3、JVM调优通过配置JVM的参数来提高垃圾回收的速度,如果在没有出现内存泄露且上

面两种办法都不能保证内存的回收时,可以考虑采用JVM调优的方式来解决,不过一定要经过实体机的长期测试,因为不同的参数可能引起不同的效果。如-Xnoclassgc参数等。

JVM原理以及JVM内存管理机制

一、 JVM简介 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.exe来完成, 首先来说一下JVM工作原理中的jdk这个东西, .JVM 在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机. 操作系统装入JVM是通过jdk中Java.exe来完成。 通过下面4步来完成JVM环境. 1.创建JVM装载环境和配置 2.装载JVM.dll 3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例 4.调用JNIEnv实例装载并处理class类。 对于JVM自身的物理结构,我们可以从下图了解:

JVM的一个重要的特征就是它的自动内存管理机制,在执行一段Java代码的时候,会把它所管理的内存划分 成几个不同的数据区域,其中包括: 1. 程序计数器,众所周知,JVM的多线程是通过线程轮流切换并 分配CPU执行时间的方式来实现的,那么每一个线程在切换 后都必须记住它所执行的字节码的行号,以便线程在得到CPU 时间时进行恢复,这个计数器用于记录正在执行的字节码指令的地址,这里要强调的是“字节码”,如果执行的是Native方法,那么这个计数器应该为null; 2.

3. Java计算栈,可以说整个Java程序的执行就是一个出栈入栈 的过程,JVM会为每一个线程创建一个计算栈,用于记录线程中方法的调用和变量的创建,由于在计算栈里分配的内存出栈后立即被抛弃,因此在计算栈里不存在垃圾回收,如果线程请求的栈深度大于JVM允许的深度,会抛出StackOverflowError 异常,在内存耗尽时会抛出OutOfMemoryError异常; 4. Native方法栈,JVM在调用操作系统本地方法的时候会使用到 这个栈; 5. Java堆,由于每个线程分配到的计算栈容量有限,对于可能会 占据大量内存的对象,则会被分配到Java堆中,在栈中包含了指向该对象内存的地址;对于一个Java程序来说,只有一个Java堆,也就是说,所有线程共享一个堆中的对象;由于Java堆不受线程的控制,如果在一个方法结束之后立即回收这个方法使用到的对象,并不能保证其他线程是否正在使用该对象;因此堆中对象的回收由JVM的垃圾收集器统一管理,和某一个线程无关;在HotSpot虚拟机中Java堆被划分为三代:o新生代,正常情况下新创建的对象会被分配到新生代,但如果对象占据的内存足够大以致超过了新生代的容量限 制,也可能被分配到老年代;新生代对象的一个特点是最 新、且生命周期不长,被回收的可能性高;

Java虚拟机(JVM)参数配置说明

Java虚拟机(JVM)参数配置说明 在Java、J2EE大型应用中,JVM非标准参数的配置直接关系到整个系统的性能。 JVM非标准参数指的是JVM底层的一些配置参数,这些参数在一般开发中默认即可,不需要任何配置。但是在生产环境中,为了提高性能,往往需要调整这些参数,以求系统达到最佳新能。另外这些参数的配置也是影响系统稳定性的一个重要因素,相信大多数Java开发人员都见过“O utOfMem ory”类型的错误。呵呵,这其中很可能就是JVM参数配置不当或者就没有配置没意识到配置引起的。 为了说明这些参数,还需要说说JDK中的命令行工具一些知识做铺垫。 首先看如何获取这些命令配置信息说明: 假设你是windows平台,你安装了J2SDK,那么现在你从cmd控制台窗口进入J2SDK安装目录下的bin目录,然后运行java命令,出现如下结果,这些就是包括java.exe工具的和J VM的所有命令都在里面。 ----------------------------------------------------------------------- D:\j2sdk15\bin>java Usage: java [-options] class [args...] (to execute a class) or java [-options] -jar jarfile [args...] (to execute a jar file) where options include: -client to select the "client" VM -server to select the "server" VM -hotspot is a synonym for the "client" VM [deprecated] The default VM is client.

Java虚拟机工作原理(JVM)

As the Java V irtual Machine is a stack-based machine, almost all of its instructions involve the operand stack in some way. Most instructions push values, pop values, or both as they perform their functions. Java虚拟机是基于栈的(stack-based machine)。几乎所有的java虚拟机的指令,都与操作数栈(operand stack)有关.绝大多数指令都会在执行自己功能的时候进行入栈、出栈操作。 1Java体系结构介绍 Javaís architecture arises out of four distinct but interrelated technologies, each of which is defined by a separate specification from Sun Microsystems: 1.1 Java体系结构包括哪几部分? Java体系结构包括4个独立但相关的技术 the Java programming language →程序设计语言 the Java class file format →字节码文件格式 the Java Application Programming Interface→应用编程接口 the Java V irtual Machine →虚拟机 1.2 什么是JVM java虚拟机和java API组成了java运行时。 1.3 JVM的主要任务。 Java虚拟机的主要任务是装载class文件并执行其中的字节码。 Java虚拟机包含了一个类装载器。 类装载器的体系结构 二种类装载器 启动类装载器 用户定义的类装载器 启动类装载器是JVM实现的一部分 当被装载的类引用另外一个类时,JVM就是使用装载第一个类的类装载器装载被引用的类。 1.4 为什么java容易被反编译? ●因为java程序是动态连接的。从一个类到另一个类的引用是符号化的。在静态连接的 可执行程序中。类之间的引用只是直接的指针或者偏移量。相反在java的class文件中,指向另一个类的引用通过字符串清楚的标明了所指向的这个类的名字。

java虚拟机详解 免费

深入理解JVM 1 Java技术与Java虚拟机 说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成: Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(Java API)。它们的关系如下图所示: 图1 Java四个方面的关系 运行期环境代表着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的体系结构和它的运行过程这两个方面来对它进行比较深入的研究。 2 Java虚拟机的体系结构 刚才已经提到,JVM可以由不同的厂商来实现。由于厂商的不同必然导致JVM在实现上的一些不同,然而JVM还是可以实现跨平台的特性,这就要归功于设计JVM时的体系结构了。 我们知道,一个JVM实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、数据类型和指令这些部分,它们描述了JVM的一个抽象的内部体系结构,其目的不光规定实现JVM时它内部的体系结构,更重要的是提供了一种方式,用于严格定义实现时的外部行为。每个JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成的体系结构图为:

Elasticsearch Java虚拟机配置详解

JVM参数Elasticsearch默认值Environment变量 -Xms 256m ES_MIN_MEM -Xmx 1g ES_MAX_MEM -Xms and -Xmx ES_HEAP_SIZE -Xmn ES_HEAP_NEWSIZE -XX:MaxDirectMemorySize ES_DIRECT_SIZE -Xss 256k -XX:UseParNewGC + -XX:UseConcMarkSweepGC + -XX:CMSInitiatingOccupancyFraction 75 -XX:UseCMSInitiatingOccupancyOnly + -XX:UseCondCardMark (commented out) 首先你注意到的是,Elasticsearch预留了256M到1GB的堆内存。 这个设置适用于开发和演示环境。开发人员只需要简单的解压发行包,再执 行./bin/elasticsearch -f就完成了Elasticsearch的安装。当然这点对于开发来说非常棒,并且在很多场景下都能工作,但是当你需要更多内存来降低Elasticsearch负载的时候就不行了,你需要比2GB RAM更多的可用内存。

ES_MIN_MEM/ES_MAX_MEM是控制堆大小的配置。新的ES_HEAP_SIZE变量是一个更为便利的选择,因为将堆的初始大小和最大值设为相同。也推荐在分配堆内存时尽可能不要用内存的碎片。内存碎片对于性能优化来说非常不利。 ES_HEAP_NEWSIZE是可选参数,它控制堆的子集大小,也就是新生代的大小。 ES_DIRECT_SIZE控制本机直接内存大小,即JVM管理NIO框架中使用的数据区域大小。本机直接内存可以被映射到虚拟地址空间上,这样在64位的机器上更高效,因为可以规避文件系统缓冲。Elasticsearch对本机直接内存没有限制(可能导致OOM)。 由于历史原因Java虚拟机有多个垃圾收集器。可以通过以下的JVM参数组合启用: JVM parameter Garbage collector -XX:+UseSerialGC serial collector -XX:+UseParallelGC parallel collector -XX:+UseParallelOldGC Parallel compacting collector -XX:+UseConcMarkSweepGC Concurrent-Mark-Sweep (CMS) collector -XX:+UseG1GC Garbage-First collector (G1) UseParNewGC和UseConcMarkSweepGC组合启用垃圾收集器的并发多线程模式。UseConcMarkSweepGC自动选择UseParNewGC模式并禁用串行收集器(Serial collector)。在Java6中这是默认行为。 CMSInitiatingOccupancyFraction提炼了一种CMS(Concurrent-Mark-Sweep)垃圾收集设置;它将旧生代触发垃圾收集的阀值设为75.旧生代的大小是堆大小减去新生代大小。这告诉JVM当堆内容达到75%时启用垃圾收集。这是个估计的值,因为越小的堆可能需要越早启动GC。 UseCondCardMark将在垃圾收集器的card table使用时,在marking之前进行额外的判断,避免冗余的store操作。UseCondCardMark不影响Garbage-First收集器。强烈推荐在高并发场景下配置这个参数(规避card table marking技术在高并发场景下的降低吞吐量的负面作用)。在ElasticSearch中,这个参数是被注释掉的。 有些配置可以参考诸如Apache Cassandra项目,他们在JVM上有类似的需求。 总而言之,ElastciSearch配置上推荐: 1. 不采用自动的堆内存配置,将堆大小默认最大值设为1GB 2.调整触发垃圾收集的阀值,比如将gc设为75%堆大小的时候触发,这样不会影响性能。 3.禁用Java7默认的G1收集器,前提是你的ElasticSearch跑在Java7u4以上的版本上。JVM进程的内存结果 JVM内存由几部分组成: Java代码本身:包括内部代码、数据、接口,调试和监控代理或者字节码指令 非堆内存:用于加载类 栈内存:用于为每个线程存储本地变量和操作数

深入理解Java虚拟机笔记(带目录)

目录 1.虚拟机内存结构 (1) 2.对象在内存中的布局 (3) 3.判断对象是否死亡 (4) 4.引用的4中情况 (4) 5.垃圾收集算法 (5) 6.HotSpot虚拟机算法实现 (6) 7.如何在GC发生时让所有线程都要附近的安全点停下 (6) 8.垃圾收集器 (7) 9.GC日志 (9) 10.内存分配 (10) 11.Class类文件的结构 (10) 12.类的生命周期 (13) 13.类加载器 (15) 14.运行时栈帧的结构 (16) 15. 方法调用 (18) 16. 分派 (19) 17.虚方法表 (19) 18.Java内存模型(JMM) (19) 19.内存间的交互 (20) 20.volatile变量 (20) 21.原子性 (21) 22.可见性 (22) 23.有序性 (22) 24.先行发生原则 (22) 25.Java线程调度 (23) 26.线程的状态 (24) 27.线程安全 (25) 28.线程安全的实现方法 (26) 29.锁优化 (27) 30.编译优化技术 (29) 1.虚拟机内存结构 线程私有:虚拟机栈,本地方法栈,程序计数器 线程共享:堆,方法区(包括运行时常量池)

1.1程序计数器 当前程序锁执行的字节码行号指示器,记录下一条需要执行的 指令。 1.2虚拟机栈 生命周期与线程相同,每个方法在执行时都会创建一个栈帧。 方法执行的过程,就是栈帧入栈到出栈的过程。 栈帧用于存放局部变量表,操作数栈,动态链接,方法出口等 信息。 局部变量表存放了编译期可知的基本数据类型和对象引用。1.3 本地方法栈 为虚拟机使用到的Native方法服务。 目前HotSpot虚拟机将本地方法栈和虚拟机栈合二为一。 1.4堆 存放对象实例,所有线程共享。 1.5 方法区(永久代) 存放被虚拟机加载的类信息,常量,静态变量,即时编译器编 译后的代码等。

深入理解java虚拟机

深入理解java虚拟机 (一)虚拟机内存划分 Java虚拟机在执行Java程序时,会把它管理的内存划分为若干个不同的数据区。这些区域有不同的特性,起不同的作用。它们有各自的创建时间,销毁时间。有的区域随着进程的启动而创建,随着进程结束而销毁,有的则始终贯穿虚拟机整个生命周期。 Java虚拟机运行时内存区域主要分为七部分,分别是:程序计数器,Java虚拟机栈,本地方法栈,方法区,Java堆,运行时常量池,直接内存。 如上图所示(图片来源于网络): 蓝色区域包裹的部分为运行时几个数据区域: 白色的部分为线程私有的,既随着线程的启动而创建。每个线程都拥有各自的一份内存区域。它们是:JAVA栈(JAVA STACK),本地方法栈(NATIVE METHOD STACK),和程序计数器(PROGRAM COUNTER REGISTER)。 黄色部分是线程共享的,所有的线程共享该区域的内容。他们是: 方法区(METHOD AREA),堆(HEAP)。 我们分别来介绍这些区域。 (1)程序计数器(program counter register)

学过计算机组成原理的都知道计算机处理器中的程序计数器。当处理器执行一条指令时,首先需要根据PC中存放的指令地址,将指令由内存取到指令寄存器中,此过程称为“取指令”。与此同时,PC中的地址或自动加1或由转移指针给出下一条指令的地址。此后经过分析指令,执行指令。完成第一条指令的执行,而后根据PC取出第二条指令的地址,如此循环,执行每一条指令。 处理器的程序计数器是指寄存器,而java程序计数器是指一小块内存空间。java代码编译字节码之后,虚拟机会一行一行的解释字节码,并翻印成本地代码。这个程序计数器盛放的就是当前线程所执行字节码的行号的指示器。在虚拟机概念模型中,字节码解释器工作室就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理等都依赖于它。 Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,因此为了线程切换后还能恢复执行位置,每条线程都需要一个独立的程序计数器。 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Java Native方法,这个计数器值为空。 而且程序计数器是Java虚拟机中没有规定任何OutOfMemoryError的区域。 (2)虚拟机栈 Java虚拟机栈(VM Stack)也是线程私有的,因此它的生命周期也和线程相同。它存放的是Java方法执行时的数据,既描述的是Java方法执行的内存模型:每个方法开始执行的时候,都会创建一个栈帧(Stack Frame)用于储存局部变量表、栈操作数、动态链接、方法出口等信息。每个方法从调用到执行完成就对应一个栈帧在虚拟机栈中入栈到出栈的过程。经常有人把Java内存分为堆内存和栈内存,这种是比较粗糙的分法,很大原因是大多数程序‘猿’最关注的,与对象内存分配最密切的区域就是堆和栈。局部变量表存放的是编译器可知的各种基本数据类型(boolean 、byte、int、long、char、short、float、double)、对象引用(reference类型)和returnAddress类型(它指向了一条字节码指令的地址)。其中64bit长度的long和double会占用两个局部变量空间(Slot),其余的数据类型只占用一个。局部变量表所需的内存空间是在编译时期确定的,在方法运行期间不会改变局部变量表的大小。在Java虚拟机规范中,对这部分区域规定了两种异常:1、当一个线程的栈深度大于虚拟机所允许的深度的时候,将会抛出StackOverflowError异常; 2、如果当创建一个新的线程时无法申请到足够的内存,则会抛出OutOfMemeryError异常。 (3)本地方法栈 本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是十分相似的,他们之间的区别不过是虚拟机栈为Java方法字节码服务,而本地方法栈则为Native方法服务。在虚拟机规范中对本地方法使用的语言和使用方法与数据结构没有强制规定,因此具体的虚拟机可

Java虚拟机的内存结构

我们都知道虚拟机的内存划分了多个区域,并不是一张大饼。那么为什么要划分为多块区域呢,直接搞一块区域,所有用到内存的地方都往这块区域里扔不就行了,岂不痛快。是的,如果不进行区域划分,扔的时候确实痛快,可用的时候再去找怎么办呢,这就引入了第一个问题,分类管理,类似于衣柜,系统磁盘等等,为了方便查找,我们会进行分区分类。另外如果不进行分区,内存用尽了怎么办呢?这里就引入了内存划分的第二个原因,就是为了方便内存的回收。如果不分,回收内存需要全部内存扫描,那就慢死了,内存根据不同的使用功能分成不同的区域,那么内存回收也就可以根据每个区域的特定进行回收,比如像栈内存中的栈帧,随着方法的执行栈帧进栈,方法执行完毕就出栈了,而对于像堆内存的回收就需要使用经典的回收算法来进行回收了,所以看起来分类这么麻烦,其实是大有好处的。 提到虚拟机的内存结构,可能首先想起来的就是堆栈。对象分配到堆上,栈上用来分配对象的引用以及一些基本数据类型相关的值。但是·虚拟机的内存结构远比此要复杂的多。除了我们所认识的(还没有认识完全)的堆栈以外,还有程序计数器,本地方法栈和方法区。我们平时所说的栈内存,一般是指的栈内存中的局部变量表。下面是官方所给的虚拟机的内存结构图

从图中可以看到有5大内存区域,按照是否被线程所共享可分为两部分,一部分是线程独占区域,包括Java栈,本地方法栈和程序计数器。还有一部分是被线程所共享的,包括方法区和堆。什么是线程共享和线程独占呢,非常好理解,我们知道每一个Java进行都会有多个线程同时运行,那么线程共享区的这片区域就是被所有线程一起使用的,不管有多少个线程,这片空间始终就这一个。而线程的独占区,是每个线程都有这么一份内存空间,每个线程的这片空间都是独有的,有多少个线程就有多少个这么个空间。上图的区域的大小并不代表实际内存区域的大小,实际运行过程中,内存区域的大小也是可以动态调整的。下面来具体说说每一个区域的主要功能。

JVM详解

JVM详解 本文详细讲解了JVM(Java Virtual Machine)的方方面面,首先由java的特性来描绘JVM 的大致应用,再细细阐述了JVM的原理及内存管理机制和调优.最后讲述了与JVM密切相关的Java GC机制. 本文内容大多来自网络,但内容十分丰富,是学习JVM的好资料. 后面会再针对JVM的两大职责class loader和execution engine进行讲解 若有疑问 目录 Java相关 (2) 1.1Java定义 (2) 1.2Java的开发流程 (2) 1.3Java运行的原理 (3) 1.4半编译半解释 (4) 1.5平台无关性 (5) JVM内存模型 (5) 2.1JVM规范 (6) 2.2 Sun JVM (9) 2.3 SUN JVM内存管理(优化) (10) 2.4 SUN JVM调优 (13) 2.5.JVM简单理解 (16) 2.5.1Java栈 (16) 2.5.2堆 (16) 2.5.3堆栈分离的好处 (19) 2.5.4 堆(heap)和栈(stack) (19) JAVA垃圾收集器 (20) 3.1垃圾收集简史 (20) 3.2常见的垃圾收集策略 (20) 3.2.1Reference Counting(引用计数) (20) 3.2.2跟踪收集器 (21) 3.3JVM的垃圾收集策略 (25) 3.3.1Serial Collector (25) 3.3.2 Parallel Collector (25) 3.3.3 Concurrent Collector (26) Java虚拟机(JVM)参数配置说明 (26)

java实验报告实验1答案

实验一熟悉NetBeans IDE 平台,开发环境及Java编程 实验目的: 1、我们使用的开发平台是NetBeans IDE,希望通过本次实验同学们能对NetBeans IDE 的开发环境有一个清楚的了解并能熟练运用,对Java语法进行初步运用,对面向对象的编程有一个直观的认识和深入理解,对于Java的基础知识进行理解运用和巩固。为以后的实验中能够进行开发程序打下基础。 2、通过编程和上机实验理解Java语言是如何体现面向对象编程基本思想,了解类的封装方法,以及如何创建类和对象,了解成员变量和成员方法的特性,掌握OOP方式进行程序设计的方法,了解类的继承性和多态性的作用。 实验内容: ● 1. 编写一个体现面向对象思想的程序。 ● 2. 编写一个创建对象和使用对象的方法的程序。 ● 3. 编写一个显示当前日期和时间的程序。 ● 4. 编写不同成员变量修饰方法的程序。 ● 5. 编写不同成员方法修饰方法的程序。 ● 6. 编写体现类的继承性(成员变量、成员方法、成员变量隐藏)的程序。 ●7. 编写体现类的多态性(成员方法重载、构造方法重载)的程序。 实验步骤: ●双击桌面上的NetBeans IDE 6.5.1快捷方式或在文件菜单中打开它。 图1-1 点击文件,创建新项目,创建一个项目名:experiment1。

点击按钮下一步: 在项目名称处输入:experiment1 然后点击完成:

在experiment1 下实现 程序 项目experiment1

样例1:编写应用程序输出如下三角形。 * *** ***** ******* 【参考程序】 public class Star { public static void main(String a[]) { System.out.println(" *"); System.out.println(" ***"); System.out.println(" *****"); System.out.println("*******"); } } 程序运行结果如图1-2所示。 【编程技巧】 (1) main方法是应用程序执行入口; (2) 如何在命令控制台输出字符串。 (3) 输出杨辉三角的前10行;进一步用参数传递的方式输出,例如,shuchu(n)表示 输出杨辉三角的前n行。 样例2:编写Applet程序绘制一个红色三角形,三角形中央绘制兰色文字“三角形”。 【参考程序】

深入理解Java反射机制汇总

深入理解Java反射机制 本文较为详细的分析了Java反射机制。分享给大家供大家参考,具体如下: 一、预先需要掌握的知识(java虚拟机) java虚拟机的方法区: java虚拟机有一个运行时数据区,这个数据区又被分为方法区,堆区和栈区,我们这里需要了解的主要是方法区。方法区的主要作用是存储被装载的类的类型信息,当java虚拟机装载某个类型的时候,需要类装载器定位相应的class文件,然后将其读入到java虚拟机中,紧接着虚拟机提取class 中的类型信息,将这些信息存储到方法区中。这些信息主要包括: 1、这个类型的全限定名 2、这个类型的直接超类的全限定名 3、这个类型是类类型还是接口类型 4、这个类型的访问修饰符 5、任何直接超接口的全限定名的有序列表 6、该类型的常量池 7、字段信息 8、方法信息 9、除了常量以外的所有类变量 10、一个到class类的引用 等等(读者可以参考《深入java虚拟机》这本书的叙述) Class类: Class类是一个非常重要的java基础类,每当装载一个新的类型的时候,java虚拟机都会在java堆中创建一个对应于新类型的Class实例,该实例就代表此类型,通过该Class实例我们就可以访问该类型的基本信息。上面说到在方法区中会存储某个被装载类的类型信息,我们就可以通过Class实例来访问这些信息。比如,对于上面说到的信息Class中都有对应的方法,如下:

1、getName();这个类型的全限定名 2、getSuperClass();这个类型的直接超类的全限定名 3、isInterface();这个类型是类类型还是接口类型 4、getTypeParamters();这个类型的访问修饰符 5、getInterfaces();任何直接超接口的全限定名的有序列表 6、getFields();字段信息 7、getMethods();方法信息 等等(读者可以自己参看jdk帮助文档,得到更多的信息) 二、java反射详解 反射的概念:所谓的反射就是java语言在运行时拥有一项自观的能力,反射使您的程序代码能够得到装载到JVM中的类的内部信息,允许您执行程序时才得到需要类的内部信息,而不是在编写代码的时候就必须要知道所需类的内部信息,这使反射成为构建灵活的应用的主要工具。 反射的常用类和函数:Java反射机制的实现要借助于4个类:Class,Constructor,Field,Method;其中class代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method -类的方法对象,通过这四个对象我们可以粗略的看到一个类的各个组成部分。其中最核心的就是Class类,它是实现反射的基础,它包含的方法我们在第一部分已经进行了基本的阐述。应用反射时我们最关心的一般是一个类的构造器、属性和方法,下面我们主要介绍Class 类中针对这三个元素的方法: 1、得到构造器的方法 Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数,Constructor[] getConstructors() -- 获得类的所有公共构造函数 Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关) Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关) 2、获得字段信息的方法

Java的类一些常识

Java的类一些常识 “1、请解释Java语言的跨平台特性。 解析:虽然不知道什么是跨平台也可以使用Java语言进行编程,但是对于一个Java编程员来说,理解跨平台特性能够更深入掌握Java语言,所以企业中往往要求应聘者至少理解这个特性。 参考答案:Java的跨平台特性也被称为可移植性、平台无关性,或者一次编写处处运行。他的意思就是如果用Java语言编写一个应用,那么就可以在不同平台上运行,而不需要为不同平台单独运行开发。之所以能实现跨平台的特性。主要得益于Java虚拟机(JVM),JVM解释器在运行Java应用时根据当前平台进行解释,解释成符合当前平台规范的机器码,所以可以实现同样的应用在不同平台上都能运行。 “2、请列举JAVA语言的主要特点 解析:了解一门语言,往往从熟悉该语言的主要特点开始入手,所以企业也常常通过应聘者对JAVA语言特点的掌握程度而判断其语言基础是否扎实。 参考答案:JAVA语言有很多特点,主要包括:①跨平台性:一个应用可以不经过修改直接运行到不同的平台上。②面向对象:JAVA语言是一门面向对面的语言,可以使用对象的属性和行为,可以使用面向对象的思想进行分析设计,并实现整个应用。③解释执行JAVA应用时,JVM中的解释器将解释类文件,生成符合当前平台的字节码。④自动回收:JAVA 应用中的垃圾回收是自动进行的,JVM中的后台线程将监视内存中数据的使用,当内存中的数据不再被引用时,将被作为垃圾回收,而不需要程序员动手回收。 “3、请说明一个JAVA类中主要包含哪几个元素?并说明每种元素的作用。 解析:无论简单还是复杂的JAVA应用,都是由若干个类组成,所以类是JAVA应用的组成单位。了解一个类中包含的主要元素能够对类有一个清晰的认识。一个类中往往会有五种元素,即属性、方法、构造方法、块以及内部类、其实块和内部类比较少见。 参考答案:JAVA类中主要包含属性、方法、构造方法、块以及内部类。

java虚拟机的原理和作用

Java虚拟机 一、什么是Java虚拟机 Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现。Java虚拟机有自己想象中的硬件,如处理器、堆栈、寄存器等,还具有相应的指令系统。 1.为什么要使用Java虚拟机 Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。 2.谁需要了解Java虚拟机 Java虚拟机是Java语言底层实现的基础,对Java语言感兴趣的人都应对Java虚拟机有个大概的了解。这有助于理解Java语言的一些性质,也有助于使用Java语言。对于要在特定平台上实现Java虚拟机的软件人员,Java语言的编译器作者以及要用硬件芯片实现Java虚拟机的人来说,则必须深刻理解Java 虚拟机的规范。另外,如果你想扩展Java语言,或是把其它语言编译成Java语言的字节码,你也需要深入地了解Java虚拟机。 3.Java虚拟机支持的数据类型 Java虚拟机支持Java语言的基本数据类型如下: byte://1字节有符号整数的补码 short://2字节有符号整数的补码 int://4字节有符号整数的补码 long://8字节有符号整数的补码 float://4字节IEEE754单精度浮点数 double://8字节IEEE754双精度浮点数 char://2字节无符号Unicode字符 几乎所有的Java类型检查都是在编译时完成的。上面列出的原始数据类型的数据在Java执行时不需要用硬件标记。*作这些原始数据类型数据的字节码(指令)本身就已经指出了*作数的数据类型,例如iadd、ladd、fadd和dadd指令都是把两个数相加,其*作数类型别是int、long、 float和double。虚拟机没有给boolean(布尔)类型设置单独的指令。boolean型的数据是由integer指令,包括integer 返回来处理的。boolean型的数组则是用byte数组来处理的。虚拟机使用IEEE754格式的浮点数。不支持IEEE格式的较旧的计算机,在运行 Java数值计算程序时,可能会非常慢。 虚拟机支持的其它数据类型包括: object//对一个Javaobject(对象)的4字节引用 returnAddress//4字节,用于jsr/ret/jsr-w/ret-w指令 注:Java数组被当作object处理。 虚拟机的规范对于object内部的结构没有任何特殊的要求。在Sun公司的实现中,对object的引用是一个句柄,其中包含一对指针:一个指针指向该object的方法表,另一个指向该object的数据。用Java

java中的四个核心概念

Java已经成为一个庞大而复杂的技术平台,对于开发人员而言,要想更好的掌握Java技术,深入理解底层的技术处理细节必不可少。现在介绍下java的四个核心概念: 1.Java虚拟机 Java虚拟机的主要任务是装在class文件并且执行其中的字节码。Java 虚拟机包含一个类装载器,它可以从程序和 API中装载class文件。Java API中只有程序执行时需要的那些类才会被装载。字节码由执行引擎来执行。不同的Java虚拟机中,执行引擎可能实现得非常不同。在由软件实现的虚拟机中,最简单的执行引擎就是一次性解释字节码。 另一种执行引擎更快,但是也更消耗内存,叫做"即时编译器(just-in-time compiler)"。在这种情况下,第一次被执行的字节码会被编译成本地机器代码。编译出的本地机器代码会被缓存,当方法以后被调用的时候可以重用。 第三种执行引擎是自适应优化器。在这种方法里,虚拟机开始的时候解释字节码,但是会监视运行中程序的活动,并且记录下使用最频繁的代码段。程序运行的时候,虚拟机只把那些活动最频繁的代码编译成本地代码,其他的代码由于使用得不是很频繁,继续保留为字节码-由虚拟机继续解释它们。 一个自适应的优化器可以使得Java虚拟机在80%~90%的时间里执行被优化过的本地代码,而只需要编译10%~20%的对性能有影响的代码。 2.类装载器的体系结构 一个Java应用程序可以使用两种类装载器:"启动(bootstrap)"类装载器和用户定义的类装载器。启动类装载器(这是系统中唯一的)是 Java虚拟机实现的一部分。启动类装载器通常使用某种默认方式从本地磁盘中装载类,包括Java API类(启动类装载器也被称为原始类装载器、系统类装载器或者默认类装载器)。Java培训:https://www.360docs.net/doc/3518741369.html, Java应用程序能够在运行时安装用户定义的类装载器,这种类装载器能够使用自定义的方式来装载类。例如,从网络下载class文件。尽管启动类装载器是虚拟机实现的本质部分,而用户定义的类装载器不是,但用户定义的类装载器能够用Java来编写,能够被编译成class文件,能够被虚拟机装载,还能够像其它对象一样实例化。 3.Java class文件 Java class文件主要在平台无关性和网络移动性方面使Java更适合网络。它在平台无关性方面的任务是:为Java程序提供独立于底层主机平台的二进制形式的服务。这种途径途径打破了C或者C++等语言所遵循的传统,使用这些传统语言写的程序通常首先被编译,然后被连接成单

java虚拟机中的堆,栈和方法区

基础数据类型直接在栈空间分配,方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收。引用数据类型,需要用new来创建,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量。方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完成后从栈空间回收。局部变量new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。方法调用时传入的literal 参数,先在栈空间分配,在方法调用完成后从栈空间分配。字符串常量在方法区域分配,this 在堆空间分配。数组既在栈空间分配数组名称,又在堆空间分配数组实际的大小! 哦对了,补充一下static在方法区域分配。 从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。 每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。 <二> 这两天看了一下深入浅出JVM这本书,推荐给高级的java程序员去看,对你了解JAVA的底层和运行机制有 比较大的帮助。 废话不想讲了.入主题: 先了解具体的概念: JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method) 堆区: 1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令) 2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身 栈区: 1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中 2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。 3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。 方法区: 1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。 2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。 为了更清楚地搞明白发生在运行时数据区里的黑幕,我们来准备2个小道具(2个非常简单的小程序)。 AppMain.java

深入理解Java虚拟机(JVM)

深入理解Java虚拟机(JVM) 一、什么是Java虚拟机 当你谈到Java虚拟机时,你可能是指: 1、抽象的Java虚拟机规范 2、一个具体的Java虚拟机实现 3、一个运行的Java虚拟机实例 二、Java虚拟机的生命周期 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三个程序,就会有三个运行中的Java 虚拟机。 Java虚拟机总是开始于一个main()方法,这个方法必须是公有、返回void、直接受一个字符串数组。在程序执行时,你必须给Java虚拟机指明这个包换main()方法的类名。 Main()方法是程序的起点,他被执行的线程初始化为程序的初始线程。程序中其他的线程都由他来启动。Java中的线程分为两种:守护线程(daemon)和普通线程(non-daemon)。守护线程是Java虚拟机自己使用的线程,比如负责垃圾收集的线程就是一个守护线程。当然,你也可以把自己的程序设置为守护线程。包含Main()方法的初始线程不是守护线程。 只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止。如果有足够的权限,你可以调用exit()方法终止程序。 三、Java虚拟机的体系结构 在Java虚拟机的规范中定义了一系列的子系统、内存区域、数据类型和使用指南。这些组件构成了Java虚拟机的内部结构,他们不仅仅为Java虚拟机的实现提供了清晰的内部结构,更是严格规定了Java虚拟机实现的外部行为。 每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。 程序的执行需要一定的内存空间,如字节码、被加载类的其他额外信息、程序中的对象、方法的参数、返回值、本地变量、处理的中间变量等等。Java虚拟机将这些信息统统保存在数据区(data areas)中。虽然每个Java虚拟机的实现中都包含数据区,但是Java虚拟机规范对数据区的规定却非常的抽象。许多结构上的细节部分都留给了Java虚拟机实现者自己发挥。不同Java虚拟机实现上的内存结构千差万别。一部分实现可能占用很多内存,而其他以下可能只占用很少的内存;一些实现可能会使用虚拟内存,而其他的则不使用。这种比较精炼的Java虚拟机内存规约,可以使得Java虚拟机可以在广泛的平台上被实现。 数据区中的一部分是整个程序共有,其他部分被单独的线程控制。每一个Java虚拟机

相关文档
最新文档