linux下怎样启动jvm提高jvm执行效率

Java虚拟机JVM性能优化(一):JVM知识总结
投稿:junjie
字体:[ ] 类型:转载 时间:
这篇文章主要介绍了Java虚拟机JVM性能优化(一):JVM知识总结,本文是系列文章的第一篇,后续篇章请继续关注脚本之家,需要的朋友可以参考下
Java应用程序是运行在JVM上的,但是你对JVM技术了解吗?这篇文章(这个系列的第一部分)讲述了经典Java虚拟机是怎么样工作的,例如:Java一次编写的利弊,跨平台引擎,垃圾回收基础知识,经典的GC算法和编译优化。之后的文章会讲JVM性能优化,包括最新的JVM设计——支持当今高并发Java应用的性能和扩展。
如果你是一个开发人员,你肯定遇到过这样的特殊感觉,你突然灵光一现,所有的思路连接起来了,你能以一个新的视角来回想起你以前的想法。我个人很喜欢学习新知识带来的这种感觉。我已经有过很多次这样的经历了,在我使用JVM技术工作时,特别是使用垃圾回收和JVM性能优化时。在这个新的Java世界中,我希望和你分享我的这些启发。希望你能像我写这篇文章一样兴奋的去了解JVM的性能。
这个系列文章,是为所有有兴趣去学习更多JVM底层知识,和JVM实际做了什么的Java开发人员所写的。在更高层次,我将讨论垃圾回收和在不影响应用运行的情况下,对空闲内存安全和速度上的无止境追求。你将学到JVM的关键部分:垃圾回收和GC算法,编译优化,和一些常用的优化。我同样会讨论为什么Java标记这样难,提供建议什么时候应该考虑测试性能。最后,我将讲一些JVM和GC的新的创新,包括Azul's Zing JVM, IBM JVM, 和Oracle's Garbage First (G1) 垃圾回收中的重点。
我希望你读完这个系列时对Java可扩展性限制的特点有更深的了解,同样的这样限制是如何强制我们以最优的方式创建一个Java部署。希望你会有一种豁然开朗的感受,并且能激发了一些好的Java灵感:停止接受那些限制,并去改变它!如果你现在还不是一个开源工作者,这个系列或许会鼓励你往这方面发展。
JVM性能和“一次编译,到处运行”的挑战
我有新的消息告诉那些固执的认为Java平台本质上是缓慢的人。当Java刚刚做为企业级应用的时候,JVM被诟病的Java性能问题已经是十几年前的事了,但这个结论,现在已经过时了。这是真的,如果你现在在不同的开发平台上运行简单静态和确定的任务时,你将很可能发现使用机器优化过的代码比使用任何虚拟环境执行的要好,在相同的JVM下。但是,Java的性能在过去10年有了非常大的提升。Java产业的市场需求和增长,导致了少量的垃圾回收算法、新的编译创新、和大量的启发式方法和优化,这些使JVM技术得到了进步。我将在以后的章节中介绍一些。
JVM的技术之美,同样是它最大的挑战:没有什么可以被认为是“一次编译,到处运行”的应用。不是优化一个用例,一个应用,一个特定的用户负载,JVM不断的跟踪Java应用现在在做什么,并进行相应的优化。这种动态的运行导致了一系列动态的问题。当设计创新时(至少不是在我们向生产环境要性能时),致力于JVM的开发者不会依赖静态编译和可预测的分配率。
JVM性能的事业
在我早期的工作中我意识到垃圾回收是非常难“解决”的,我一直着迷于JVMs和中间件技术。我对JVMs的热情开始于我在JRockit团队中时,编码一种新的方法用于自学,自己调试垃圾回收算法(参考 Resources)。这个项目(转变为JRockit一个实验性的特点,并成为Deterministic Garbage Collection算法的基础)开启了我JVM技术的旅程。我已经在BEA系统、Intel、Sun和Oracle(因为Oracle收购BEA系统,所以被Oracle短暂的工作过)工作过。之后我加入了在Azul Systems的团队去管理Zing JVM,现在我为Cloudera工作。
机器优化的代码可能会实现较好的性能(但这是以牺牲灵活性来做代价的),但对于动态装载和功能快速变化的企业应用这并不是一个权衡选择它的理由。大多数的企业为了Java的优点,更愿意去牺牲机器优化代码带来的勉强完美的性能。
1.易于编码和功能开发(意义是更短的时间去回应市场)
2.得到知识渊博的的程序员
3.用Java APIs和标准库更快速的开发
4.可移植性——不用为新的平台去重新写Java应用
从Java代码到字节码
做为一个Java程序员,你可能对编码、编译和执行Java应用很熟悉。例子:我们假设你有一个程序(MyApp.java),现在你想让它运行。去执行这个程序你需要先用javac(JDK内置的静态Java语言到字节码编译器)编译。基于Java代码,javac生成相应的可执行字节码,并保存在相同名字的class文件:MyApp.class中。在把Java代码编译成字节码后,你可以通过java命令(通过命令行或startup脚本,使用不使用startup选项都可以)来启动可执行的class文件,从而运行你的应用。这样你的class被加载到运行时(意味着Java虚拟机的运行),程序开始执行。
这就是表面上每一个应用执行的场景,但是现在我们来探究下当你执行java命令时究竟发生了什么。Java虚拟机是什么?大多数开发人员通过持续调试来与JVM交互——aka selecting 和value-assigning启动选项能让你的Java程序跑的更快,同时避免了臭名昭著的”out of memory”错误。但是,你是否曾经想过,为什么我们起初需要一个JVM来运行Java应用呢?
什么是Java虚拟机?
简单的说,一个JVM是一个软件模块,用于执行Java应用字节码并且把字节码转化到硬件,操作系统特殊指令。通过这样做,JVM允许Java程序在第一次编写后可以在不同的环境中执行,并不需要更改原始的代码。Java的可移植性是通往企业应用语言的关键:开发者并不需要为不同平台重写应用代码,因为JVM负责翻译和平台优化。
一个JVM基本上是一个虚拟的执行环境,作为一个字节码指令机器,而用于分配执行任务和执行内存操作通过与底层的交互。
一个JVM同样为运行的Java应用照看动态资源管理。这就意味着它掌握分配和释放内存,在每个平台上保持一致的线程模型,在应用执行的地方用一种适于CPU架构的方式组织可执行的指令。JVM把开发人员从跟踪对象当中的引用,和它们需要在系统中存在多长时间中解放出来。同样的它不用我们管理何时去释放内存——一个像C语言那样的非动态语言的痛点。
你可以把JVM当做是一个专门为Java运行的操作系统;它的工作是为Java应用管理运行环境。一个JVM基本上是一个虚拟的通过与底层的交互的执行环境,作为一个字节码指令机器,而用于分配执行任务和执行内存操作。
JVM组件概述
有很多写JVM内部和性能优化的文章。作为这个系列的基础,我将会总结概述下JVM组件。这个简短的阅览会为刚接触JVM的开发者有特殊的帮助,会让你更想了解之后更深入的讨论。
从一种语言到另一种——关于Java编译器
编译器是把一种语言输入,然后输出另一种可执行的语句。Java编译器有两个主要任务:
1. 让Java语言更加轻便,不用在第一次写的时候固定在特定的平台;
2. 确保对特定的平台产生有效的可执行的代码。
编译器可以是静态也可以是动态。一个静态编译的例子是javac。它把Java代码当做输入,并转化为字节码(一种在Java虚拟机执行的语言)。静态编译器一次解释输入的代码,输出可执行的形式,这个是在程序执行时将被用到。因为输入是静态的,你将总能看到结果相同。只有当你修改原始代码并重新编译时,你才能看到不同的输出。
动态编译器,例如Just-In-Time (JIT)编译器,把一种语言动态的转化为另一种,这意味着它们做这些时把代码被执行。JIT编译器让你收集或创建运行数据分析(通过插入性能计数的方式),用编译器决定,用手边的环境数据。动态的编译器可以在编译成语言的过程之中,实现更好的指令序列,把一系列的指令替换成更有效的,甚至消除多余的操作。随着时间的增长你将收集更多的代码配制数据,做更多更好的编译决定;整个过程就是我们通常称为的代码优化和重编译。
动态编译给了你可以根据行为去调整动态的变化的优势,或随着应用装载次数的增加催生的新的优化。这就是为什么动态编译器非常适合Java运行。值得注意的是,动态编译器请求外部数据结构,线程资源,CPU周期分析和优化。越深层次的优化,你将需要越多的资源。然而在大多数环境中,顶层对执行性能的提升帮助非常小——比你纯粹的解释要快5到10倍的性能。
分配会导致垃圾回收
分配在每一个线程基于每个“Java进程分配内存地址空间”,或者叫Java堆,或者直接叫堆。在Java世界中单线程分配在客户端应用程序中很常见。然而,单线程分配在企业应用和工作装载服务端变的没有任何益处,因为它并没有使用现在多核环境的并行优势。
并行应用设计同样迫使JVM保证在同一时间,多线程不会分配同一个地址空间。你可以通过在整个分配空间中放把锁来控制。但这种技术(通常叫做堆锁)很消耗性能,持有或排队线程会影响资源利用和应用优化的性能。多核系统好的一面是,它们创造了一个需求,为各种各样的新的方法在资源分配的同时去阻止单线程的瓶颈,和序列化。
一个常用的方法是把堆分成几部分,在对应用来说每个合式分区大小的地方——显然它们需要调优,分配率和对象大小对不同应用来说有显著的变化,同样线程的数量也不同。线程本地分配缓存(Thread Local Allocation Buffer,简写:TLAB),或者有时,线程本地空间(Thread Local Area,简写:TLA),是一个专门的分区,在其中线程不用声明一个全堆锁就可以自由分配。当区域满的时候,堆就满了,表示堆上的空闲空间不够用来放对象的,需要分配空间。当堆满的时候,垃圾回收就会开始。
使用TLABs捕获异常,是把堆碎片化来降低内存效率。如果一个应用在要分配对象时正巧不能增加或者不能完全分配一个TLAB空间,这将会有空间太小而不能生成新对象的风险。这样的空闲空间被当做“碎片”。如果应用程序一直保持对象的引用,然后再用剩下的空间分配,最后这些空间会在很长一段时间内空闲。
碎片就是当碎片被分散在堆中的时候——通过一小段不用的内存空间来浪费堆空间。为你的应用分配 “错误的”TLAB空间(关于对象的大小、混合对象的大小和引用持有率)是导致堆内碎片增多的原因。在随着应用的运行,碎片的数量会增加在堆中占有的空间。碎片导致性能下降,系统不能给新应用分配足够的线程和对象。垃圾回收器在随后会很难阻止out-of-memory异常。
TLAB浪费在工作中产生。一种方法可以完全或暂时避免碎片,那就是在每次基础操作时优化TLAB空间。这种方法典型的作法是应用只要有分配行为,就需要重新调优。通过复杂的JVM算法可以实现,另一种方法是组织堆分区实现更有效的内存分配。例如,JVM可以实现free-lists,它是连接起一串特定大小的空闲内存块。一个连续的空闲内存块和另一个相同大小的连续内存块相连,这样会创建少量的链表,每个都有自己的边界。在有些情况下free-lists导致更好的合适内存分配。线程可以对象分配在一个差不多大小的块中,这样比你只依靠固定大小的TLAB,潜在的产生少的碎片。
有一些早期的垃圾收集器拥有多个老年代,但是当超过两个老年代的时候会导致开销超过价值。另一种优化分配减少碎片的方法,就是创造所谓的新生代,这是一个专门用于分配新对象的专用堆空间。剩余的堆会成为所谓的老年代。老年代是用来分配长时间存在的对象的,被假定会存在很长时间的对象包括不被垃圾收集的对象或者大对象。为了更好的理解这种分配的方法,我们需要讲一些垃圾收集的知识。
垃圾回收和应用性能
垃圾回收是JVM的垃圾回收器去释放没有引用的被占据的堆内存。当第一次触发垃圾收集时,所有的对象引用还被保存着,被以前的引用占据的空间被释放或重新分配。当所有可回收的内存被收集后,空间等待被抓取和再次分配给新对象。
垃圾回收器永远都不能重声明一个引用对象,这样做会破坏JVM的标准规范。这个规则的异常是一个可以捕获的soft或weak引用 ,如果垃圾收集器将要将近耗尽内存。我强烈推荐你尽量避免weak引用,然而,因为Java规范的模糊导致了错误的解释和使用的错误。更何况,Java是被设计为动态内存管理,因为你不需要考虑什么时候和什么地方释放内存。
垃圾收集器的一个挑战是在分配内存时,需要尽量不影响运行着的应用。如果你不尽量垃圾收集,你的应用将耗近内存;如果你收集的太频繁,你将损失吞吐量和响应时间,这将对运行的应用产生坏的影响。
有许多不同的垃圾回收算法。稍后,在这个系列里将深入讨论几点。在最高层,垃圾收集两个最主要的方法是引用计数和跟踪收集器。
引用计数收集器会跟踪一个对象指向多少个引用。当一个对象的引用为0时,内存将被立即回收,这是这种方法的优点之一。引用计数方法的难点在于环形数据结构和保持所有的引用即时更新。
跟踪收集器对仍在引用的对象标记,用已经标记的对象,反复的跟随和标记所有的引用对象。当所有的仍然引用的对象被标记为“live”时,所有的不被标记的空间将被回收。这种方法管理环形数据结构,但是在很多情况下收集器应该等待直到所有标记完成,在重新回收不被引用的内存之前。
有不种的途径来被上面的方法。最著名的算法是 marking 或copying 算法, parallel 或 concurrent算法。我将在稍后的文章中讨论这些。
通常来说垃圾回收的意义是致力于在堆中给新对象和老对象分配地址空间。其中“老对象”是指在许多垃圾回收后幸存的对象。用新生代来给新对象分配,老年代给老对象,这样能通过快速回收占据内存的短时间对象来减少碎片,同样通过把长时间存在的对象聚合在一起,并把它们放到老年代地址空间中。所有这些在长时间对象和保存堆内存不碎片化之间减少了碎片。新生代的一个积极作用是延迟了需要花费更大代价回收老年代对象的时间,你可以为短暂的对象重复利用相同的空间。(老空间的收集会花费更多,是因为长时间存在的对象们,会包含更多的引用,需要更多的遍历。)
最后值的一提的算法是compaction,这是管理内存碎片的方法。Compaction基本来说就是把对象移动到一起,从来释放更大的连续内存空间。如果你熟悉磁盘碎片和处理它的工具,你会发现compaction跟它很像,不同的是这个运行在Java堆内存中。我将在系列中详细讨论compaction。
总结:回顾和重点
JVM允许可移植(一次编程,到处运行)和动态的内存管理,所有Java平台的主要特性,都是它受欢迎和提高生产力的原因。
在第一篇JVM性能优化系统的文章中我解释了一个编译器怎么把字节码转化为目标平台的指令语言的,并帮助动态的优化Java程序的执行。不同的应用需要不同的编译器。
我同样简述了内存分配和垃圾收集,和这些怎么与Java应用性能相关的。基本上,你越快的填满堆和频繁的触发垃圾收集,Java应用的占有率越高。垃圾收集器的一个挑战是在分配内存时,需要尽量不影响运行着的应用,但要在应用耗尽内存之前。在以后的文章中我们会更详细的讨论传统的和新的垃圾回收和JVM性能优化。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具JVM 数据存储介绍及性能优化
JVM 内存模式介绍Java 虚拟机内存模型是 Java 程序运行的基础。为了能使 Java 应用程序正常运行,JVM 虚拟机将其内存数据分为程序计数器、虚拟机栈、本地方法栈、Java 堆和方法区等部分。程序计数器 (Program Counter Register)程序计数器 (Program Counter
Register) 是一块很小内存空间,由于 Java 是支持线程的语言,当线程数量超过 CPU 数量时,线程之间根据时间片轮询抢夺 CPU 资源。对于单核 CPU 而言,每一时刻只能有一个线程在运行,而其他线程必须被切换出去。为此,每一个线程都必须用一个独立的程序计数器,用于记录下一条要运行的指令。各个线程之间的计数器互不影响,独立工作,是一块线程独有的内存空间。如果当前线程正在执行一个 Java 方法,则程序计数器记录正在执行的 Java 字节码地址,如果当前线程正在执行一个 Native 方法,则程序计数器为空。虚拟机栈虚拟机栈用于存放函数调用堆栈信息。Java 虚拟机栈也是线程私有的内存空间,它和 Java 线程在同一时间创建,它保存方法的局部变量、部分结果,并参与方法的调用和返回。Java 虚拟机规范允许 Java 栈的大小是动态的或者是固定的。在 Java 虚拟机规范中定义了两种异常与栈空间有关:StackOverflowError 和 OutOfMemoryError。如果线程在计算过程中,请求的栈深度大于最大可用的栈深度,则抛出 StackOverflowError;如果 Java 栈可以动态扩展,而在扩展栈的过程中没有足够的内存空间来支持栈的发展,则抛出 OutOfMemeoryError。可以使用-Xss 参数来设置栈的大小,栈的大小直接决定了函数调用的可达深度。下面的例子展示了一个递归调用的应用。计数器 count 记录了递归的层次,这个没有出口的递归函数一定会导致栈溢出。程序则在栈溢出时,打印出栈的当前深度。清单 1. 递归调用显示栈的最大深度public class TestStack {
private int count = 0;
//没有出口的递归函数
public void recursion(){
count++;//每次调用深度加 1
recursion();//递归
public void testStack(){
recursion();
}catch(Throwable e){
System.out.println("deep of stack is "+count);//打印栈溢出的深度
e.printStackTrace();
public static void main(String[] args){
TestStack ts = new TestStack();
ts.testStack();
}清单 2. 清单 1 运行结果java.lang.StackOverflowError
at TestStack.recursion(TestStack.java:7)
at TestStack.recursion(TestStack.java:7)
at TestStack.recursion(TestStack.java:7)
at TestStack.recursion(TestStack.java:7)
at TestStack.recursion(TestStack.java:7)
at TestStack.recursion(TestStack.java:7)
at TestStack.recursion(TestStack.java:7)deep of stack is 9013虚拟机栈在运行时使用一种叫做栈帧的数据结构保存上下文数据。在栈帧中,存放了方法的局部变量表、操作数栈、动态连接方法和返回地址等信息。每一个方法的调用都伴随着栈帧的入栈操作。相应地,方法的返回则表示栈帧的出栈操作。如果方法调用时,方法的参数和局部变量相对较多,那么栈帧中的局部变量表就会比较大,栈帧会膨胀以满足方法调用所需传递的信息。因此,单个方法调用所需的栈空间大小也会比较多。函数嵌套调用的次数由栈的大小决定。栈越大,函数嵌套调用次数越多。对一个函数而言,它的参数越多,内部局部变量越多,它的栈帧就越大,其嵌套调用次数就会减少。本地方法栈本地方法栈和 Java 虚拟机栈的功能很相似,本地方法栈用于存放函数调用堆栈信息。Java 虚拟机栈用于管理 Java 函数的调用,而本地方法栈用于管理本地方法的调用。本地方法并不是用 Java 实现的,而是使用 C 实现的。在 SUN 的 HotSpot 虚拟机中,不区分本地方法栈和虚拟机栈。因此,和虚拟机栈一样,它也会抛出 StackOverflowError 和 OutofMemoryError。Java 堆堆用于存放 Java 程序运行时所需的对象等数据。几乎所有的对象和数组都是在堆中分配空间的。Java 堆分为新生代和老生代两个部分,新生代用于存放刚刚产生的对象和年轻的对象,如果对象一直没有被回收,生存得足够长,老年对象就被移入老年代。新生代又可进一步细分为 eden、survivor
space0 和 survivor
space1。eden 即对象的出生地,大部分对象刚刚建立时都会被存放在这里。survivor 空间是存放其中的对象至少经历了一次垃圾回收,并得以幸存下来的。如果在幸存区的对象到了指定年龄仍未被回收,则有机会进入老年代 (tenured)。下面例子演示了对象在内存中的分配方式。清单 3. 进行一次新生代 GCpublic class TestHeapGC {
public static void main(String[] args){
byte[] b1 = new byte[];
byte[] b2 = new byte[];
b2 = new byte[];//进行一次新生代 GC
System.gc();
}清单 4. 清单 3 的配置-XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -Xms40M -Xmx40M -Xmn20M清单 5. 清单 3 的输出[GC [DefNew: 9031K-&661K(18432K), 0.0022784 secs] 9031K-&661K(38912K),
0.0023178 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
def new generation total 18432K, used 9508K [0xx35c1c10000)
eden space 16384K, 54% used [0xx350b3e58, 0x)
from space 2048K, 32% used [0x35a1abc10000)
to space 2048K, 0% used [0xxx35a10000)
tenured generation total 20480K, used 0K [0x35c1110000)
the space 20480K, 0% used [0x35c1c1c110000)
compacting perm gen total 12288K, used 374K [0xx37c1b010000)
the space 12288K, 3% used [0xxxx37c10000)
ro space 10240K, 51% used [0x3bx3bx3bx3ba10000)
rw space 12288K, 55% used [0x3ba1c0ae4f8, 0x3c0ae600, 0x3c610000)上述输出显示 JVM 在进行多次内存分配的过程中,触发了一次新生代 GC。在这次 GC 中,原本分配在 eden 段的变量 b1 被移动到 from 空间段 (s0)。最后分配的 8MB 内存被分配在 eden 新生代。方法区方法区用于存放程序的类元数据信息。方法区与堆空间类似,它也是被 JVM 中所有的线程共享的。方法区主要保存的信息是类的元数据。方法区中最为重要的是类的类型信息、常量池、域信息、方法信息。类型信息包括类的完整名称、父类的完整名称、类型修饰符和类型的直接接口类表;常量池包括这个类方法、域等信息所引用的常量信息;域信息包括域名称、域类型和域修饰符;方法信息包括方法名称、返回类型、方法参数、方法修饰符、方法字节码、操作数栈和方法栈帧的局部变量区大小以及异常表。总之,方法区内保持的信息大部分来自于 class 文件,是 Java 应用程序运行必不可少的重要数据。在 Hot
Spot 虚拟机中,方法区也称为永久区,是一块独立于 Java 堆的内存空间。虽然叫做永久区,但是在永久区中的对象同样也可以被 GC 回收的。只是对于 GC 的表现也和 Java 堆空间略有不同。对永久区 GC 的回收,通常主要从两个方面分析:一是 GC 对永久区常量池的回收;二是永久区对类元数据的回收。Hot
Spot 虚拟机对常量池的回收策略是很明确的,只要常量池中的常量没有被任何地方引用,就可以被回收。清单 6 所示代码会生成大量 String 对象,并将其加入常量池中。String.intern() 方法的含义是如果常量池中已经存在当前 String,则返回池中的对象,如果常量池中不存在当前 String 对象,则先将 String 加入常量池,并返回池中的对象引用。因此,不停地将 String 对象加入常量池会导致永久区饱和。如果 GC 不能回收永久区的这些常量数据,那么就会抛出 OutofMemoryError 错误。清单.6 GC 收集永久区public class permGenGC {
public static void main(String[] args){
for(int i=0;i&Integer.MAX_VALUE;i++){
String t = String.valueOf(i).intern();//加入常量池
}清单 7. 清单 6 的配置-XX:PermSize=2M -XX:MaxPermSize=4M -XX:+PrintGCDetails清单 8. 清单 6 的输出[Full GC [Tenured: 0K-&149K(10944K), 0.0177107 secs] 3990K-&149K(15872K),
[Perm : 4096K-&374K(4096K)], 0.0181540 secs] [Times: user=0.02 sys=0.02, real=0.03 secs]
[Full GC [Tenured: 149K-&149K(10944K), 0.0165517 secs] 3994K-&149K(15936K),
[Perm : 4096K-&374K(4096K)], 0.0169260 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
[Full GC [Tenured: 149K-&149K(10944K), 0.0166528 secs] 3876K-&149K(15936K),
[Perm : 4096K-&374K(4096K)], 0.0170333 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]每当常量池饱和时,FULL GC 总能顺利回收常量池数据,确保程序稳定持续进行。JVM 参数调优实例由于 Java 字节码是运行在 JVM 虚拟机上的,同样的字节码使用不同的 JVM 虚拟机参数运行,其性能表现可能各不一样。为了能使系统性能最优,就需要选择使用合适的 JVM 参数运行 Java 应用程序。设置最大堆内存JVM 内存结构分配对 Java 应用程序的性能有较大的影响。Java 应用程序可以使用的最大堆可以用-Xmx 参数指定。最大堆指的是新生代和老生代的大小之和的最大值,它是 Java 应用程序的堆上限。清单 9 所示代码是在堆上分配空间直到内存溢出。-Xmx 参数的大小不同,将直接决定程序能够走过几个循环,本例配置为-Xmx5M,设置最大堆上限为 5MB。清单 9 .Java 堆分配空间import java.util.V
public class maxHeapTest {
public static void main(String[] args){
Vector v = new Vector();
for(int i=0;i&=10;i++){
byte[] b = new byte[];
System.out.println(i+"M is allocated");
System.out.println("Max memory:"+Runtime.getRuntime().maxMemory());
}清单 10. 运行输出0M is allocated
1M is allocated
2M is allocated
3M is allocated
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at maxHeapTest.main(maxHeapTest.java:8)此时表明在完成 4MB 数据分配后系统空闲的堆内存大小已经不足 1MB 了。设置 GC 新生代区大小参数-Xmn 或者用于 Hot
Spot 虚拟机中的参数-XX:NewSize(新生代初始大小)、-XX:MaxNewSize 用于设置新生代的大小。设置一个较大的新生代会减小老生代的大小,这个参数对系统性能以及 GC 行为有很大的影响。新生代的大小一般设置为整个堆空间的 1/4 到 1/3 左右。以清单 9 的代码为例,若使用 JVM 参数-XX:+PrintGCDetails -Xmx11M -XX:NewSize=2M -XX:MaxNewSize=2M
-verbose:gc 运行程序,将新生代的大小减小为 2MB,那么 MinorGC 次数将从 4 次增加到 9 次 (默认情况下是 3.5MB 左右)。清单 11. 运行输出[GC [DefNew: 1272K-&150K(1856K), 0.0028101 secs] 1272K-&K),
0.0028504 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 1174K-&0K(1856K), 0.0018805 secs] 2198K-&K),
0.0019097 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
clearing....
[GC [DefNew: 1076K-&0K(1856K), 0.0004046 secs] 3274K-&K),
0.0004382 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 1024K-&0K(1856K), 0.0011834 secs] 3222K-&K),
0.0013508 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 1024K-&0K(1856K), 0.0012983 secs] 4246K-&K),
0.0013299 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
clearing....
[GC [DefNew: 1024K-&0K(1856K), 0.0001441 secs] 5270K-&K),
0.0001686 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 1024K-&0K(1856K), 0.0012028 secs] 5270K-&K),
0.0012328 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 1024K-&0K(1856K), 0.0012553 secs] 6294K-&K),
0.0012845 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
clearing....
[GC [DefNew: 1024K-&0K(1856K), 0.0001524 secs] 7318K-&K),
0.0001780 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
def new generation total 1856K, used 1057K [0xxx)
eden space 1664K, 63% used [0xx, 0x365b0000)
from space 192K, 0% used [0x365ee10000)
to space 192K, 0% used [0x365bbe0000)
tenured generation total 9216K, used 6294K [0xx36f110000)
the space 9216K, 68% used [0xx36c3c35a00, 0x36f10000)
compacting perm gen total 12288K, used 375K [0xx37c1b010000)
the space 12288K, 3% used [0xxxx37c10000)
ro space 10240K, 51% used [0x3bx3bx3bx3ba10000)
rw space 12288K, 55% used [0x3ba1c0ae4f8, 0x3c0ae600, 0x3c610000)设置持久代大小持久代 (方法区) 不属于堆的一部分。在 Hot
Spot 虚拟机中,使用-XX:MaxPermSize 参数可以设置持久代的最大值,使用-XX:PermSize 可以设置持久代的初始大小。持久代的大小直接决定了系统可以支持多少个类定义和多少常量。对于使用 CGLIB 或者 Javassit 等动态字节码生成工具的应用程序而言,设置合理的持久代大小有助于维持系统稳定。系统所支持的最大类与 MaxPermSize 成正比。一般来说,MaxPermSize 设置为 64MB 已经可以满足绝大部分应用程序正常工作。如果依然出现永久区溢出,可以设置为 128MB。这是两个很常用的永久区取值。如果 128MB 依然不能满足应用程序需求,那么对于大部分应用程序来说,则应该考虑优化系统的设计,减少动态类的产生,或者利用 GC 回收部分驻扎在永久区的无用类信息,以使系统健康运行。设置线程栈大小线程栈是线程的一块私有空间。在 JVM 中可以使用-Xss 参数设置线程栈的大小。在线程中进行局部变量分配,函数调用时都需要在栈中开辟空间。如果栈的空间分配太小,那么线程在运行时可能没有足够的空间分配局部变量或者达不到足够的函数调用深度,导致程序异常退出;如果栈空间过大,那么开设线程所需的内存成本就会上升,系统所能支持的线程总数就会下降。由于 Java 堆也是向操作系统申请内存空间的,因此,如果堆空间过大,就会导致操作系统可用于线程栈的内存减少,从而间接减少程序所能支持的线程数量。清单 12 所示代码尝试开设尽可能多的线程,并在线程数量饱和时,打印已经开设的线程数量。清单 12. 尝试开启尽可能多的线程public class TestXss {
public static class MyThread extends Thread{
public void run(){
Thread.sleep(10000);
}catch(InterruptedException e){
e.printStackTrace();
public static void main(String[] args){
int count=0;
for(int i=0;i&10000;i++){
new MyThread().start();
}catch(OutOfMemoryError e){
System.out.println(count);
System.out.println(e.getMessage());
}清单 13. 配置-Xss1M 时的运行输出1578
unable to create new native thread一共允许启动 1578 个线程。清单 14. 配置-Xss20M 时的运行输出69
unable to create new native thread实验证明如果改变系统的最大堆空间设定,可以发现系统所能支持的线程数量也会相应改变。Java 堆的分配以 200MB 递增,当栈大小为 1MB 时,最大线程数量以 200 递减。当系统物理内存被堆占据时,就不可以被栈使用。当系统由于内存空间不够而无法创建新的线程时会抛出 OOM 异常。这并不是由于堆内存不够而导致的 OOM,而是因为操作系统内存减去堆内存后剩余的系统内存不足而无法创建新的线程。在这种情况下可以尝试减少堆内存以换取更多的系统空间来解决这个问题。综上所述,如果系统确实需要大量线程并发执行,那么设置一个较小的堆和较小的栈有助于提高系统所能承受的最大线程数。设置堆的比例分配参数-XX:SurvivorRatio 是用来设置新生代中 eden 空间和 s0 空间的比例关系。s0 和 s1 空间又分别称为 from 空间和 to 空间。它们的大小是相同的,职能也是相同的,并在 Minor
GC 后互换角色。清单 15. 所示示例演示不断插入字符时使用的 GC 输出import java.util.ArrayL
import java.util.L
public class StringDemo {
public static void main(String[] args){
List&String& handler = new ArrayList&String&();
for(int i=0;i&1000;i++){
HugeStr h = new HugeStr();
ImprovedHugeStr h1 = new ImprovedHugeStr();
handler.add(h.getSubString(1, 5));
handler.add(h1.getSubString(1, 5));
static class HugeStr{
private String str = new String(new char[800000]);
public String getSubString(int begin,int end){
return str.substring(begin, end);
static class ImprovedHugeStr{
private String str = new String(new char[]);
public String getSubString(int begin,int end){
return new String(str.substring(begin, end));
}清单 16. 设置新生代堆为 10MB,并使 eden 区是 s0 的 8-XX:+PrintGCDetails -XX:MaxNewSize=10M -XX:SurvivorRatio=8清单 17. 运行输出[Full GC [Tenured: 233756K-&1904K), 0.0524229 secs] 233756K-&1120K),
[Perm : 377K-&372K(12288K)], 0.0524703 secs] [Times: user=0.06 sys=0.00, real=0.06 secs]
def new generation total 9216K, used 170K [0xx27a1a10000)
eden space 8192K, 2% used [0xxx)
from space 1024K, 0% used [0xxx27a10000)
to space 1024K, 0% used [0xxx)
tenured generation total 251904K, used 233743K [0x27a1110000)
the space 251904K, 92% used [0x27a1e53d00, 0x35e53e00, 0x)
compacting perm gen total 12288K, used 372K [0xx37c1b010000)
the space 12288K, 3% used [0xxxx37c10000)
ro space 10240K, 51% used [0x3bx3bx3bx3ba10000)
rw space 12288K, 55% used [0x3ba1c0ae4f8, 0x3c0ae600, 0x3c610000)修改参数 SurvivorRatio 为 2 运行程序,相当于设置 eden 区是 s0 的 2 倍大小,由于 s1 与 s0 相同,故有 eden=[10MB/(1+1+2)]*2=5MB。清单 18. 运行输出[Full GC [Tenured: 233756K-&1904K), 0.0546689 secs] 233756K-&9584K),
[Perm : 377K-&372K(12288K)],0.0547257 secs] [Times: user=0.05 sys=0.00, real=0.05 secs]
def new generation total 7680K, used 108K [0xx27a1a10000)
eden space 5120K, 2% used [0xx, 0x)
from space 2560K, 0% used [0xxx)
to space 2560K, 0% used [0xxx27a10000)
tenured generation total 251904K, used 233743K [0x27a1110000)
the space 251904K, 92% used [0x27a1e53d00, 0x35e53e00, 0x)
compacting perm gen total 12288K, used 372K [0xx37c1b010000)
the space 12288K, 3% used [0xxxx37c10000)
ro space 10240K, 51% used [0x3bx3bx3bx3ba10000)
rw space 12288K, 55% used [0x3ba1c0ae4f8, 0x3c0ae600, 0x3c610000)Java 堆参数总结Java 堆操作是主要的数据存储操作,总结的主要参数配置如下。与 Java 应用程序堆内存相关的 JVM 参数有:-Xms:设置 Java 应用程序启动时的初始堆大小;-Xmx:设置 Java 应用程序能获得的最大堆大小;-Xss:设置线程栈的大小;-XX:MinHeapFreeRatio:设置堆空间最小空闲比例。当堆空间的空闲内存小于这个数值时,JVM 便会扩展堆空间;-XX:MaxHeapFreeRatio:设置堆空间的最大空闲比例。当堆空间的空闲内存大于这个数值时,便会压缩堆空间,得到一个较小的堆;-XX:NewSize:设置新生代的大小;-XX:NewRatio:设置老年代与新生代的比例,它等于老年代大小除以新生代大小;-XX:SurvivorRatio:新生代中 eden 区与 survivor 区的比例;-XX:MaxPermSize:设置最大的持久区大小;-XX:TargetSurvivorRatio:
设置 survivor 区的可使用率。当 survivor 区的空间使用率达到这个数值时,会将对象送入老年代。结束语从所有这些参数描述信息和代码示例可以看到,没有哪一条固定的规则可以供程序员参考。性能优化需要根据您应用的实际情况来有选择性地挑选参数及配制值,没有完全绝对的最优方案,最优方案是基于您对 JVM 数据存储方式及自己代码的了解程度来作出的最佳选择。
下载资源 (SampleCode.zip | 3k)相关主题参考检索页面,查看 IBM 开发者论坛公布的关于 JVM 的相关文章。查看博客系列文章,作者对于 JVM 的理解颇为深刻。查看文章,作者对 JVM 的垃圾收集进行了详细解释。查看文章,作者对 JVM 的内存管理机制进行了详细解释。
添加或订阅评论,请先或。
有新评论时提醒我
static.content.url=http://www.ibm.com/developerworks/js/artrating/SITE_ID=10Zone=Java technologyArticleID=1002332ArticleTitle=JVM 数据存储介绍及性能优化publish-date=}

我要回帖

更多关于 win10怎样启用jvm 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信