1、内存泄露
(1)虚拟机中存在程序无法使用的内存区域
比如队列出队的时候,没有删除对改元素的引用,导致元素没用了,引用仍在,但无法使用
(2)程序中存在大量存活时间过长的对象
比如基于内存的缓存实现,hashmap中缓存对象大而缓存时间长
2、虚拟机中对象的三种可能状态
(1)可达状态
(2)可复活状态
(3)不可达状态
3、永久代内存不足
(1)String的intern方法(JDK7之前)
jdk7的话,是在年老代里头
(2)加载的java类过多
比如使用ASM创建代理类,或者使用Groovy等
4、JVM的作用
运行java程序时,需要指定一个main类,虚拟机启动之后,会从main方法开始执行,当main方法执行结束之后,java虚拟机会自动终止。每个java虚拟机在运行时是低层操作系统上的一个独立的进程。
虚拟机的作用主要有两个:一是为应用程序屏蔽低层操作系统的细节;二是为应用程序提供必要的运行时的支持能力。
5、字节码的执行
java源代码经过编译器编译之后,被转换为java字节码。虚拟机在执行字节码的时候,一般采用的是即时编译的方式,即Just-in-time(JIT)编译方式,在运行时把字节码中的指令直接转换为低层操作系统平台上的原生指令。这会带来一定的性能问题。
(1)对热点进行优化
把时间用在对重要代码的优化,减少代码优化的时间开销
(2)方法内联
把被调用方法的代码直接内联到调用的地方
6、垃圾回收器的运行方式
(1)串行
垃圾回收器运行时,程序的运行被暂停,执行起来简单(如果只有一个CPU只能串行)
(2)并行
垃圾回收器与程序同时运行,在绝大部分时候不会影响程序的运行,但是由于回收时程序仍然在运行,内存状态在变化,实现起来较为复杂。
7、GC时存活对象的处理方式
(1)压缩
把存活对象移动到内存区域的一端,使内存中的空闲区域变成连续的。
好处:分配内存时速度快(空间连续,分配快,判断是否有足够的空间区域也快)
坏处:移动操作需要额外时间
(2)不压缩
存活对象仍然被保留在原始位置。
好处:垃圾回收可以很快完成
坏处:分配内存过程会比较慢(空间不连续,需要逐个查找可用的内存块来满足当前分配请求)
(3)复制
把存活对象复制到另外一个区域中。
好处:复制完后,之前的内存区域全部可用,内存分配快
坏处:需要额外时间、空间进行复制
8、垃圾回收方式默认选择
Hotspot虚拟机会根据硬件平台的能力来选择最适合的垃圾回收方式。
硬件平台根据其性能被划分为服务器级别和非服务器级别两类。
(1)服务器级别
指有两个或两个以上的CPU以及2G以上物理内存的硬件平台,默认使用并行回收方式,堆内存大小的初始值和最大值分别是物理内存平台的1/64和1/4;
(2)非服务器级别
默认使用的是串行回收方式,堆内存大小的初始值和默认值分别是4MB和64MB。
9、目标驱动的参数配置法
(1)降低由于垃圾回收造成的程序的停顿时间
对于某些实时性要求很高的程序不允许出现较长时间的停顿。可以通过"-XX:MaxGCPauseMillis"来指定最长的停顿时间。虚拟机会调整垃圾回收器的其他参数来保证这个目标得以满足。(-XX:+UseConcMarkSweepGC)
(2)提高程序运行的吞吐量
吞吐量是指虚拟机花费在垃圾回收操作上的时间和程序实际运行时间的比例。虚拟机应该保证把尽可能多的时间花费在程序运行上,同时保证正常的垃圾回收操作不受影响。通过"-XX:GCTimeRatio"可以指定垃圾回收时间所占的比例,如果"-XX:GCTimeRatio=49",则指垃圾回收时间所占的比例是1/(1+49)=2%
(-XX:+UseParallelGC,-XX:+UseParallelOldGC)
(3)降低虚拟机占用的内存总量
当前面两个目标满足时,垃圾回收器会降低堆内存的大小,直到出现了前面两个目标不满足的情况。这样的作用是在满足前两个目标的前提下,尽可能地减少程序所占用的内存。
10、垃圾回收深入配置
(1)使用串行回收
-XX:+UseSerialGC
(2)使用并行回收
-XX:+UseParallelGC
并行回收方式相对于串行回收方式的改进体现在对年轻代进行回收时会利用多个CPU来并行完成,可以减少垃圾回收操作造成的停顿时间和整体的回收操作所占用的时间,提高吞吐量。
而对于年老代,并行回收的方式与串行回收的方式是一样的,这样的话性能会有所影响,可以采用并行压缩回收方式进行改进,即对年老代的回收也采用并行处理的方式。
"-XX:+UseParallelOldGC",这种回收方式会把年老代的内存区域划分为若干个固定大小的子区域。对每个子区域以并行的方式同时进行存活对象的标记工作。由于标记工作是并行进行的,执行的效率会比较高。在完成标记工作之后,下一步就是找出这些子区域中需要进行压缩操作的部分,这是因为某些子区域中存活的对象所占的比例可能比较大,不需要进行压缩,一般都在年老代的某一端。找到了需要进行压缩的子区域之后,就可以通过复制和移动存活对象等操作来压缩子区域,使存活对象密度较高的子区域都出现在年老代内存区域的某一端,方便后续的内存分配。
"-XX:+UseConcMarkSweepGC",如果想使得运行时因垃圾回收操作而造成的停顿时间比较短,可以采用并发标记清除的回收方式。这种回收方式对年轻代的做法和并行回收方式一样,而对于年老代的处理方式则比较复杂,分为几个具体的阶段。第一个阶段是初始的标记阶段,这个阶段会先暂停程序的运行,再标记出从程序的代码中直接可达的存活对象。完成这个阶段之后,程序可以继续运行。与此同时,垃圾回收器会从上一个阶段标记处的存活对象出发,递归标记可达的其他存活对象,这个标记过程与程序的运行并发进行,完成这个并发标记之后,下一个阶段会再次暂停程序的运行,因为在上一个标记阶段,由于程序仍然在运行,对象的存活状态可能已经发生了变化。这个阶段会对发生变化的对象重新标记,之后程序可以继续运行,同时并发对标记过程中发现的垃圾进行回收处理。这种回收方式所造成的程序停顿时间比较短,但是会要求比较大的堆内存,同时它不会对内存区域进行压缩操作,使得后续的内存分配操作比较耗时。
"-XX:+UseG1GC","-XX:+UnlockExperimentalVMOptions",垃圾优先方式,也采用了把内存区域划分为不同世代的做法,对年轻代进行更加频繁的回收操作,但是它没有把年轻代和年老代所占用的内存区域从物理上分隔开来,而是把整个堆内存划分成大小相同的子区域,每个子区域可以属于年轻代或年老代。垃圾回收的过程就是先找出需要进行回收的子区域和接收待回收子区域中存活对象的其他子区域。回收的过程是并行进行的,把待回收子区域中的存活对象移动到用来接收的其他子区域中,再把原始的子区域清空。回收操作比较多地发生在年轻代的子区域中,对于年老代的子区域也会顺带进行处理。
响应优先:采用并发垃圾回收,-XX:+UseConcMarkSweepGC,GC运行时用户程序同时也在运行,但是并发进行,会占用CPU,会使得GC时用户程序变慢。
吞吐量优先:采用并行垃圾回收,-XX:+UseParallelGC,-XX:+UseParallelOldGC,多线程GC并行进行垃圾回收,减少垃圾回收时间,提高系统吞吐量,用户程序在GC时,停顿时间比CMS可能要多一些。
11、启动参数
(1)标准参数
-D,设置系统属性的值;
-ea和-da分别用来启用和禁用程序类中的断言;
-esa和-dsa分别用来启用和禁用系统类中的断言;
-verbose用来要求虚拟机输出一些详细的信息;
-verbose:class用来输出加载Java类时的信息;
-verbose:gc用来在垃圾回收时输出相关信息;
-verbose:jni用来在使用原生方法时输出相关信息
(2)非标准参数
-X开头的参数是非标准参数,不是所有平台上的虚拟机都支持,一般常见的是设置虚拟机堆内存大小的参数,比如-Xms用来设置堆内存的初始值,-Xmx用来设置对内存的最大值
(3)不稳定参数
-XX开头的参数的实现不是很稳定,一般不推荐使用。
A、布尔型
-XX:+<option>打开
-XX:-<option>关闭
B、数值型
-XX:<option>=<number>,单位可以使用k,m,g等分别用来表示千、兆、千兆
C、字符串类型
-XX:<option>=<string>
(4)根据作用分类
A、与虚拟机行为相关
指定垃圾回收方式的参数都属于这一类,另外还包括:
"-XX:+DisableExplicitGC",禁止用过System.gc方法来显式要求运行垃圾回收器
"-XX:+ScavengeBeforeFullGC",要求垃圾回收器在整个内存空间进行回收之前先回收年轻代的内存空间,默认打开
"-XX:+UseGCOverheadLimit",限制虚拟机花费在垃圾回收上的时间,如何花费大量时间在垃圾回收上,说明虚拟机内存过小,不足以支持程序运行,持续一段时间后将抛出OutOfMemoryError错误。默认打开
B、与性能优化相关
主要用来处理字符串的优化,以及调整虚拟机堆内存各部分的大小。
"-XX:+AggressiveOpts",用来启用在当前虚拟机版本中处于试验性质的优化策略(可提升性能但可能造成一些运行时错误)
"-XX:+UseStringCache",用来启用对经常使用的字符串进行缓存的功能,可提升性能
"-XX:+UseCompressedStrings",用来启用对字符串的压缩处理,如果字符串仅包含ASCII字符,会使用byte[]替代char[],节省占用的内存空间
"-XX:+OptimizeStringConcat",用来启用对字符串连接操作的优化功能
"-XX:NewRatio",用来指定年轻代和年老代所占用内存的比例
"-XX:NewSize",用来指定年轻代所占用内存的大小
"-XX:MaxPermSize",用来指定永久代所占用内存的最大值
C、与程序调试相关
控制虚拟机在运行时输出一些调试信息。
"-XX:+CITime",控制输出JIT编译器所花费的时间
"-XX:+PrintGC",控制当前垃圾回收器运行时输出与回收操作相关的信息
"-XX:+PrintGCDetails",输出更详细的GC信息
"-XX:+PrintGCTimeStamps",输出增加时间戳
"-XX:+TraceClassLoading",控制Java类被加载时输出相关的调试信息
"-XX:+TraceClassUnloading",控制Java类被卸载时输出相关的调试信息
12、分析工具
(1)命令行和图形化工具
使用jmap在控制台输出程序中共享对象的映射关系或者dump文件
jhat是配合jmap使用的,用来分析jmap dump出来的文件,但不大好用,一般不怎么用(用VisualVM替代)
使用jstack查看线程堆栈信息
使用jinfo查看虚拟机中系统属性值和启动参数
(2)JMX
Java Management Extensions,Java管理扩展。
JMX API是Java平台上进行资源监控和管理的标准API,可以用来对应用程序、设备、服务和Java虚拟机本身进行监控和管理。包括在运行时动态获取和更新程序的配置信息、收集程序运行过程中的统计数据以及当程序内部状态发生变化或出现错误时发出相关通知等。
JMX的基础概念是MBean,它表示的是可以被管理的命名资源。管理接口的内容包括可以获取和修改值得命名属性、可以调用的命名方法、以及可以发出的事件通知。具体可以监控和管理的资源包括虚拟机中的缓冲区、类加载系统、代码编译、垃圾回收器、内存、低层操作系统、虚拟机运行时、线程和日志记录器等。
(3)JVM TI
JVM Tools Interface,Java虚拟机工具接口,通过C/C++原生代码来监控和管理JVM
JMX是面向应用程序的,而JVM TI是供开发、调试和监控工具使用的。