常用Java虚拟机参数
-XX:+PrintGC,只要遇到GC,就会打印日志。
-XX:+PrintGCDetails,可以打印出更加详细的信息。
-XX:+PrintHeapAtGC,会在每次GC前后分别打印堆的信息。
-XX:+PrintGCTimeStamps,会在每次GC发生时,额外输出GC发生的时间。
-Xloggc:aaaaaaaaaa.log 将gc日志打印到文件。
-verbose:class 用来跟踪类的加载和卸载。
当Java进程启动时,虚拟机就会分配一块初始堆空间,可以使用参数-Xms指定这块空间的大小。一般来说,虚拟机会尽可能维持在初始堆空间的范围内运行。但是如果初始堆空间耗尽,虚拟机将会对堆空间进行扩展,其扩展上限为最大堆空间,最大堆空间可以使用参数-Xmx指定。
-Xmn可以用于设置新生代的大小。设置一个较大的新生代会减小老年代的大小。
-XX:ServivorRatio=2,用来设置新生代中eden空间和from/to空间的比例关系。
-XX:+HeapDumpOnOutOfMemoryError,可以在内存溢出时导出整个堆信息。
-XX:HeapDumpPath,可以指定导出堆的存放路径。
-XX:OnOutOfMemoryError=aaaaaaaa,可以在堆溢出的时候执行一些脚本。
-XX:MaxMetaspaceSize 可以指定元数据区的最大可用值。
-Xss 可以指定Java栈的大小。
-XX:MaxDirectMemorySize 可以设定最大可用直接内存的大小。如不设置,默认值为最大堆空间,即-Xmx。当直接内存使用量达到-XX:MaxDirectMemorySize时,就会触发垃圾回收,如果垃圾回收不能有效释放足够空间,直接内存溢出依然会引起系统的OOM。
直接内存适合申请次数较少、访问较频繁的场合。如果内存空间本身需要频繁申请,则并不适合使用直接内存。
与Client模式相比,Server模式的启动比较慢,因为Server模式会尝试收集更多的系统性能信息,使用更复杂的优化算法对程序进行优化。因此,当系统完全启动并进入运行稳定期后,Server模式的执行速度会远远快于Client模式。所以,对于后台长期运行的系统,使用-server参数启动对系统的整体性能可以有不小的帮助。但对于用户界面程序,运行时间不长,又追求启动速度,Client模式也是不错的选择。
垃圾回收概念与算法
基础垃圾回收算法:引用计数法、标记清除法、标记压缩法、复制算法和分代分区的思想。
1. 引用计数法:
两个问题:(1)无法处理循环引用的情况。(2)引用计数法要求在每次引用产生和消除的时候,需要伴随一个加法操作和减法操作,对系统性能会有一定的影响。
2. 标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。标记清除算法可能产生的最大问题是空间碎片。
3. 复制算法的核心思想是:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
新生代分为eden空间、from空间和to空间3个部分。其中from和to空间可以视为用于复制的两块大小相同、地位相等、且可进行角色互换的空间块。from和to空间也称为survivor空间,即幸存者空间,用于存放未被回收的对象。
在垃圾回收时,eden空间中的存活对象会被复制到未使用的survivor空间中(假设是to),正在使用的survivor空间(假设是from)中的年轻对象也会被复制到to空间中(大对象,或者老年对象会直接进入老年代,如果to空间已满,则对象也会直接进入老年代)。此时,eden空间和from空间中的剩余对象就是垃圾对象,可以直接清空,to空间则存放此次回收后的存活对象。这种改进的复制算法,既保证了空间的连续性,又避免了大量的内存空间浪费。
复制算法比较适用于新生代。因为在新生代,垃圾对象通常会多于存活对象,复制算法的效果会比较好。
4. 标记压缩算法是一种老年代的回收算法。它在标记清除算法的基础上做了一些优化。和标记清除算法一样,标记压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不只是简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比较高。
分代分区思想:
它将内存区间根据对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法,以提高垃圾回收的效率。
一般来说,Java虚拟机会将所有的新建对象都放入称为新生代的内存区域,新生代的特点是对象朝生夕灭,大约90%的新建对象会被很快回收,因此,新生代比较适合使用复制算法。当一个对象经过几次回收后依然存活,对象就会被放入称为老年代的内存空间。在老年代中,几乎所有的对象都是经过几次垃圾回收后依然得以存活的。因此,可以认为这些对象在一段时期内,甚至在应用程序的整个生命周期中,将是常驻内存的。
为了支持高频率的新生代回收,虚拟机可能使用一种叫作卡表的数据结构。卡表为一个比特位集合,每一个比特位可以用来表示老年代的某一区域中的所有对象是否持有新生代对象的引用。这样在新生代GC时,可以不用花大量时间扫描所有老年代对象。
分代思想按照对象的生命周期长短划分为两个部分,分区思想将整个堆空间划分成连续的不同小区间。每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
对象的可触及性:
可触及的:从根节点开始,可以到达这个对象。
可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活(将this指针外泄出去)。
不可触及的:对象的finalize()函数被调用,并且没有复活,那么就会进入不可触及状态,不可触及的对象不可能被复活,因为finalize()函数只会被调用一次。
在Java中提供了4个级别的引用:强引用、软引用、弱引用和虚引用。除强引用外,其他3种引用均可以在java.lang.ref包中找到它们的身影。
强引用:
就是程序中一般使用的引用类型,强引用的对象是可触及的,不会被回收。
软引用:
在GC时,未必会回收软引用的对象,但是,当内存资源紧张时,软引用对象会被回收,所以软引用对象不会引起内存溢出。
弱引用:
在GC时,只要发现弱引用,不管系统堆空间使用情况如何,都会将对象进行回收。
虚引用:
虚引用相当于没有引用。虚引用主要用于跟踪一个对象被垃圾回收的过程。虚引用必须和引用队列联合使用。
为了让垃圾回收器可以正常且高效地执行,大部分情况下,会要求系统进入一个停顿的状态。停顿的目的是终止所有应用线程的执行,只有这样,系统中才不会有新的垃圾产生,同时停顿保证了系统状态在某一瞬间的一致性,也有益于垃圾回收器更好地标记垃圾对象。因此,在垃圾回收时,都会产生应用程序的停顿。停顿产生时,整个应用程序会被卡死,没有任何响应,因此这个停顿也叫做"Stop-The-World"(STW)。