一般的高级语言假设要在不同的平台上执行。至少须要编译成不同的目标代码。
而引入Java语言虚拟机后。Java语言在不同平台上执行时不须要又一次编译。Java语言使用Java虚拟机屏蔽了与详细平台相关的信息。使得Java语言编译程序仅仅需生成在Java虚拟机上执行的目标代码(字节码),就行在多种平台上不加改动地执行。Java虚拟机在执行字节码时。把字节码解释成详细平台上的机器指令执行。这就是Java的可以“一次编译。到处执行”的原因。
-
Java源代码编译机制
-
类载入机制
-
类运行机制
-
分析和输入到符号表
-
注解处理
-
语义分析和生成class文件
-
结构信息。包含class文件格式版本及各部分的数量与大小的信息
-
元数据。相应于Java源代码中声明与常量的信息。包括类/继承的超类/实现的接口的声明信息、域与方法声明信息和常量池
-
方法信息。相应Java源代码中语句和表达式相应的信息。包括字节码、异常处理器表、求值栈与局部变量区大小、求值栈的类型记录、调试符号信息
堆被划分为新生代和旧生代。新生代又被进一步划分为Eden和Survivor区,最后Survivor由From Space和To Space组成,结构图例如以下所看到的:
-
新生代。
新建的对象都是用新生代分配内存,Eden空间不足的时候。会把存活的对象转移到Survivor中。新生代大小能够由-Xmn来控制,也能够用-XX:SurvivorRatio来控制Eden和Survivor的比例
-
旧生代。用于存放新生代中经过多次垃圾回收仍然存活的对象
-
持久带(Permanent Space)实现方法区,主要存放全部已载入的类信息,方法信息,常量池等等。
可通过-XX:PermSize和-XX:MaxPermSize来指定持久带初始化值和最大值。Permanent Space并不等同于方法区,仅仅只是是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没有Permanent Space而用其它机制来实现方法区。
-
-Xmx:最大堆内存,如:-Xmx512m
-
-Xms:初始时堆内存,如:-Xms256m
-
-XX:MaxNewSize:最大年轻区内存
-
-XX:NewSize:初始时年轻区内存.通常为 Xmx 的 1/3 或 1/4。新生代 = Eden + 2 个 Survivor 空间。实际可用空间为 = Eden + 1 个 Survivor。即 90%
-
-XX:MaxPermSize:最大持久带内存
-
-XX:PermSize:初始时持久带内存
-
-XX:+PrintGCDetails。
打印 GC 信息
-
-XX:NewRatio 新生代与老年代的比例。如 –XX:NewRatio=2。则新生代占整个堆空间的1/3。老年代占2/3
-
-XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值。
默认值为 8。即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10
算法每次仅仅处理正在使用中的对象,因此复制成本比較小。同一时候复制过去以后还能进行对应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是非常明显的。就是须要两倍内存空间。
也是分两阶段。第一阶段从根节点開始标记全部被引用对象,第二阶段遍历整个堆,把清除未标记对象而且把存活对象“压缩”到堆的当中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同一时候也避免了“复制”算法的空间问题。
当连续分配对象时。对象会逐渐从eden到survivor,最后到旧生代。
|
特别要关注Full GC,由于它会对整个堆进行整理,导致Full GC一般由于下面几种情况:
这个值可由-XX:MaxGCPauseRatio=n来设置
如:为3。表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
1
|
CATALINA_OPTS= "-Xms1024m -Xmx1024m -XX:NewRatio=4 -XX:PermSize=192m -XX:MaxPermSize=192m -Xss256k -XX:SurvivorRatio=4" |
1
2
3
4
5
6
7
8
9
10
11
|
< Connector port = "8080" protocol = "HTTP/1.1" maxHttpHeaderSize = "8192" maxThreads = "1000" minSpareThreads = "100" maxSpareThreads = "1000" enableLookups = "false" connectionTimeout = "10000" URIEncoding = "utf-8" acceptCount = "1000" redirectPort = "8443" disableUploadTimeout = "true" /> |
调优续:
要了解Java垃圾收集机制,先理解JVM内存模式是很重要的。今天我们将会了解JVM内存的各个部分、怎样监控以及垃圾收集调优。
Java(JVM)内存模型
正如你从上面的图片看到的,JVM内存被分成多个独立的部分。
广泛地说,JVM堆内存被分为两部分——年轻代(Young Generation)和老年代(Old Generation)。
年轻代
年轻代是全部新对象产生的地方。
当年轻代内存空间被用完时。就会触发垃圾回收。这个垃圾回收叫做Minor GC。年轻代被分为3个部分——Enden区和两个Survivor区。
年轻代空间的要点:
- 大多数新建的对象都位于Eden区。
- 当Eden区被对象填满时,就会运行Minor GC。
并把全部存活下来的对象转移到当中一个survivor区。
- Minor GC相同会检查存活下来的对象,并把它们转移到还有一个survivor区。这样在一段时间内。总会有一个空的survivor区。
- 经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完毕的。
年老代
年老代内存里包括了长期存活的对象和经过多次Minor GC后依旧存活下来的对象。一般会在老年代内存被占满时进行垃圾回收。
老年代的垃圾收集叫做Major GC。
Major GC会花费很多其它的时间。
Stop the World事件
全部的垃圾收集都是“Stop the World”事件,由于全部的应用线程都会停下来直到操作完毕(所以叫“Stop the World”)。
由于年轻代里的对象都是一些暂时(short-lived )对象,运行Minor GC很快,所以应用不会受到(“Stop the World”)影响。
由于Major GC会检查全部存活的对象,因此会花费更长的时间。应该尽量降低Major GC。由于Major GC会在垃圾回收期间让你的应用反应迟钝,所以假设你有一个须要高速响应的应用发生多次Major GC,你会看到超时错误。
垃圾回收时间取决于垃圾回收策略。
这就是为什么有必要去监控垃圾收集和对垃圾收集进行调优。从而避免要求高速响应的应用出现超时错误。
永久代
永久代或者“Perm Gen”包括了JVM须要的应用元数据,这些元数据描写叙述了在应用里使用的类和方法。注意,永久代不是Java堆内存的一部分。
永久代存放JVM执行时使用的类。永久代相同包括了Java SE库的类和方法。
永久代的对象在full GC时进行垃圾收集。
方法区
方法区是永久代空间的一部分。并用来存储类型信息(执行时常量和静态变量)和方法代码和构造函数代码。
内存池
假设JVM实现支持,JVM内存管理会为创建内存池,用来为不变对象创建对象池。字符串池就是内存池类型的一个非常好的样例。内存池能够属于堆或者永久代,这取决于JVM内存管理的实现。
执行时常量池
执行时常量池是每一个类常量池的执行时代表。
它包括了类的执行时常量和静态方法。执行时常量池是方法区的一部分。
Java栈内存
Java栈内存用于执行线程。它们包括了方法里的暂时数据、堆里其他对象引用的特定数据。你能够阅读栈内存和堆内存的差别。
Java 堆内存开关
Java提供了大量的内存开关(參数),我们能够用它来设置内存大小和它们的比例。
以下是一些经常使用的开关:
VM 开关 | VM 开关描写叙述 |
---|---|
-Xms | 设置JVM启动时堆的初始化大小。 |
-Xmx | 设置堆最大值。 |
-Xmn | 设置年轻代的空间大小。剩下的为老年代的空间大小。 |
-XX:PermGen | 设置永久代内存的初始化大小。 |
-XX:MaxPermGen | 设置永久代的最大值。 |
-XX:SurvivorRatio | 提供Eden区和survivor区的空间比例。比方。假设年轻代的大小为10m而且VM开关是-XX:SurvivorRatio=2,那么将会保留5m内存给Eden区和每一个Survivor区分配2.5m内存。默认比例是8。 |
-XX:NewRatio | 提供年老代和年轻代的比例大小。默认值是2。 |
大多数时候,上面的选项已经足够使用了。可是假设你还想了解其它的选项,那么请查看JVM选项官方网页。
Java垃圾回收
Java垃圾回收会找出无用的对象。把它从内存中移除并释放出内存给以后创建的对象使用。Java程序语言中的一个最大长处是自己主动垃圾回收,不像其它的程序语言那样须要手动分配和释放内存,比方C语言。
垃圾收集器是一个后台执行程序。它管理着内存中的全部对象并找出没被引用的对象。全部的这些未引用的对象都会被删除。回收它们的空间并分配给其它对象。
一个主要的垃圾回收过程涉及三个步骤:
- 标记:这是第一步。
在这一步,垃圾收集器会找出哪些对象正在使用和哪些对象不在使用。
- 正常清除:垃圾收集器清会除不在使用的对象,回收它们的空间分配给其它对象。
- 压缩清除:为了提升性能。压缩清除会在删除无用的对象后,把全部存活的对象移到一起。
这样能够提高分配新对象的效率。
简单标记和清除方法存在两个问题:
- 效率非常低。由于大多数新建对象都会成为“没用对象”。
- 经过多次垃圾回收周期的对象非常有可能在以后的周期也会存活下来。
上面简单清除方法的问题在于Java垃圾收集的分代回收的。并且在堆内存里有年轻代和年老代两个区域。我已经在上面解释了Minor GC和Major GC是如何扫描对象,以及如何把对象从一个分代空间移到另外一个分代空间。
Java垃圾回收类型
这里有五种能够在应用里使用的垃圾回收类型。仅须要使用JVM开关就能够在我们的应用里启用垃圾回收策略。
让我们一起来逐一了解:
- Serial GC(-XX:+UseSerialGC):Serial GC使用简单的标记、清除、压缩方法对年轻代和年老代进行垃圾回收。即Minor
GC和Major GC。Serial GC在client模式(客户端模式)非常实用。比方在简单的独立应用和CPU配置较低的机器。这个模式对占有内存较少的应用非常管用。
- Parallel GC(-XX:+UseParallelGC):除了会产生N个线程来进行年轻代的垃圾收集外。Parallel GC和Serial GC差点儿一样。
这里的N是系统CPU的核数。我们能够使用 -XX:ParallelGCThreads=n 这个JVM选项来控制线程数量。并行垃圾收集器也叫throughput收集器。
由于它使用了多CPU加快垃圾回收性能。Parallel GC在进行年老代垃圾收集时使用单线程。
- Parallel Old GC(-XX:+UseParallelOldGC):和Parallel GC一样。不同之处。Parallel Old GC在年轻代垃圾收集和年老代垃圾回收时都使用多线程收集。
- 并发标记清除(CMS)收集器(-XX:+UseConcMarkSweepGC):CMS收集器也被称为短暂停顿并发收集器。它是对年老代进行垃圾收集的。CMS收集器通过多线程并发进行垃圾回收,尽量降低垃圾收集造成的停顿。
CMS收集器对年轻代进行垃圾回收使用的算法和Parallel收集器一样。
这个垃圾收集器适用于不能忍受长时间停顿要求高速响应的应用。可使用 -XX:ParallelCMSThreads=n JVM选项来限制CMS收集器的线程数量。
- G1垃圾收集器(-XX:+UseG1GC) G1(Garbage First):垃圾收集器是在Java 7后才干够使用的特性,它的长远目标时取代CMS收集器。G1收集器是一个并行的、并发的和增量式压缩短暂停顿的垃圾收集器。G1收集器和其它的收集器执行方式不一样。不区分年轻代和年老代空间。它把堆空间划分为多个大小相等的区域。当进行垃圾收集时,它会优先收集存活对象较少的区域,因此叫“Garbage
First”。
你能够在Oracle Garbage-FIrst收集器文档找到很多其它具体信息。
Java垃圾收集监控
我们能够使用命令行和图形工具来监控监控应用垃圾回收。
比如,我使用Java SE下载页中的一个demo来实验。
假设你想使用相同的应用,能够到Java SE下载页面下载JDK 7和JavaFX演示和演示样例。
我使用的演示样例应用是Java2Demo.jar,它位于 jdk1.7.0_55/demo/jfc/Java2D 文件夹下。这仅仅是一个可选步骤。你能够执行GC监控命令监控不论什么Java应用。
我打开演示应用使用的命令是:
1
|
pankaj@Pankaj:~ /Downloads/jdk1 .7.0_55 /demo/jfc/Java2D $
java -Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar Java2Demo.jar |
jsat
能够使用jstat命令行工具监控JVM内存和垃圾回收。标准的JDK已经附带了jstat,所以不须要做不论什么额外的事情就能够得到它。
要执行jstat你须要知道应用的进程id。你能够使用 ps -eaf | grep java 命令获取进程id。
1
2
3
|
pankaj@Pankaj:~$
ps
-eaf | grep
Java2Demo.jar 501
9582 11579 0 9:48PM ttys000 0:21.66 /usr/bin/java
-Xmx120m -Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Java2Demo.jar 501
14073 14045 0 9:48PM ttys002 0:00.00 grep
Java2Demo.jar |
从上面知道,我的Java应用进程id是9582。如今能够执行jstat命令了。就像以下展示的一样:
1
2
3
4
5
6
7
8
9
|
pankaj@Pankaj:~$
jstat -gc 9582 1000 S0C
S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT 1024.0
1024.0 0.0 0.0 8192.0 7933.3 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0
1024.0 0.0 0.0 8192.0 8026.5 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0
1024.0 0.0 0.0 8192.0 8030.0 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0
1024.0 0.0 0.0 8192.0 8122.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0
1024.0 0.0 0.0 8192.0 8171.2 42108.0 23401.3 20480.0 19990.9 157 0.274 40 1.381 1.654 1024.0
1024.0 48.7 0.0 8192.0 106.7 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656 1024.0
1024.0 48.7 0.0 8192.0 145.8 42108.0 23401.3 20480.0 19990.9 158 0.275 40 1.381 1.656 |
jstat命令的最后一个參数是每一个输出的时间间隔。每隔一秒就会打印出内存和垃圾收集数据。
让我们一起来对每一列的意义进行逐一了解:
- S0C和S1C:这一列展示了Survivor0和Survivor1区的当前大小(单位KB)。
- S0U和S1U:这一列展示了当前Survivor0和Survivor1区的使用情况(单位KB)。注意:不管不论什么时候,总会有一个Survivor区是空着的。
- EC和EU:这些列展示了Eden区当前空间大小和使用情况(单位KB)。注意:EU的大小一直在增大。并且仅仅要大小接近EC时,就会触发Minor GC并且EU将会减小。
- OC和OU:这些列展示了年老代当前空间大小和当前使用情况(单位KB)。
- PC和PU:这些列展示了Perm Gen(永久代)当前空间大小和当前使用情况(单位KB)。
- YGC和YGCT:YGC这列显示了发生在年轻代的GC事件的数量。YGCT这列显示了在年轻代进行GC操作的累计时间。注意:在EU的值因为minor GC导致下降时,同一行的YGC和YGCT都会添加。
- FGC和FGCT:FGC列显示了发生Full GC事件的次数。FGCT显示了进行Full GC操作的累计时间。注意:相对于年轻代的GC使用时间,Full GC所用的时间长非常多。
- GCT:这一列显示了GC操作的总累计时间。注意:总累计时间是YGCT和FGCT两列所用时间的总和(GCT=YGCT+FGCT)。
jstat的长处,我们相同能够在没有GUI的远程server上执行jstat。
注意:我们是通过 -Xmn10m 选项来指定S0C、S1C和EC的总和为10m的。
Java VisualVM及Visual GC插件
假设你想在GUI里查看内存和GC,那么能够使用jvisualvm工具。
Java VisualVM相同是JDK的一部分,所以你不须要单独去下载。
在终端执行jvisualvm命令启动Java VisualVM程序。一旦启动程序,你须要从Tools->Plugins选项安装Visual GC插件,就像以下图片展示的。
安装完Visual GC插件后。从左边栏打开应用并把视角转到Visual GC部分。
你将会得到关于JVM内存和垃圾收集详情,例如以下图所看到的。
Java垃圾回收调优
Java垃圾回收调优应该是提升应用吞吐量的最后一个选择。在你发现应用因为长时间垃圾回收导致了应用性能下降、出现超时的时候,应该考虑Java垃圾收集调优。
假设你在日志里看到 java.lang.OutOfMemoryError: PermGen space错误。那么能够尝试使用 -XX:PermGen 和 -XX:MaxPermGen JVM选项去监控并添加Perm Gen内存空间。
你也能够尝试使用-XX:+CMSClassUnloadingEnabled并查看使用CMS垃圾收集器的运行性能。
假设你看到了大量的Full GC操作。那么你应该尝试增大老年代的内存空间。
全面垃圾收集调优要花费大量的努力和时间。这里没有一尘不变的硬性调优规则。
你须要去尝试不同的选项而且对这些选项进行对照,从而找出最适合自己应用的方案。
这就是全部的Java内存模型和垃圾回收内容。
希望对你理解JVM内存和垃圾收集过程有所帮助。
原文链接: journaldev 翻译: ImportNew.com - 进林译文链接: http://www.importnew.com/14086.html