文章目录
GC
元空间和永久带
java8之后,永久带被移除,取而代之是元空间。它们最大的区别在于,永久带使用JVM的堆内存,而元空间使用并不在堆内存中,而是使用本机的物理内存。
因此,默认情况下,元空间的大小受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由MaxPermSize控制,而是由系统实际可以空间控制。
GC过程
参数 | 默认值 |
---|---|
-Xms | 设置初始分配大小,默认为物理内存的1/64 |
-Xmx | 最大分配内存,默认为物理内存的1/4 |
-XX:+PrintGCDetails | 输出详细的GC处理日志 |
在edit configuration中设置VM Options:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
public class JVMParamSet {
public static void main(String[] args) {
long maxMemory = Runtime.getRuntime().maxMemory() ;//返回 Java 虚拟机试图使用的最大内存量。
long totalMemory = Runtime.getRuntime().totalMemory() ;//返回 Java 虚拟机中的内存总量。
System.out.println("-Xmx MAX_MEMORY = " + maxMemory + "(字节)、" + (maxMemory / (double)1024 / 1024) + "MB");
System.out.println("-Xms TOTAL_MEMORY = " + totalMemory + "(字节)、" + (totalMemory / (double)1024 / 1024) + "MB");
}
}
-Xmx MAX_MEMORY = 1029177344(字节)、981.5MB
-Xms TOTAL_MEMORY = 1029177344(字节)、981.5MB
Heap
PSYoungGen total 305664K, used 15729K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 6% used [0x00000000eab00000,0x00000000eba5c420,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
Metaspace used 3225K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 337K, capacity 388K, committed 512K, reserved 1048576KProcess finished with exit code 0
可以看到,JVM内存逻辑上由Young + Old + Metaspace构成,但物理上是Young + Old之和。
在实际开发中,一般把初始内存和最大内存设置相同,避免内存忽高忽低导致系统停顿。
GC过程分析
public class OOMTest {
public static void main(String[] args) {
String s = "www.tupobi.top";
while (true) {
s += s + new Random().nextInt(88888888) + new Random().nextInt(999999999);
}
}
}
在edit configuration中设置VM Options:-Xms10m -Xmx10m -XX:+PrintGCDetails
[GC (Allocation Failure) [PSYoungGen: 2048K->509K(2560K)] 2048K->860K(9728K), 0.0008281 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2353K->493K(2560K)] 2703K->1163K(9728K), 0.0013604 secs] [Times: user=0.03 sys=0.01, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2283K->504K(2560K)] 2953K->2440K(9728K), 0.0007520 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2530K->504K(2560K)] 4466K->3922K(9728K), 0.0008955 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 2543K->0K(2560K)] [ParOldGen: 5394K->1840K(7168K)] 7937K->1840K(9728K), [Metaspace: 3212K->3212K(1056768K)], 0.0041044 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 988K->0K(2560K)] 4804K->3816K(9728K), 0.0002794 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 3816K->3816K(8704K), 0.0002807 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 3816K->3815K(7168K)] 3816K->3815K(8704K), [Metaspace: 3212K->3212K(1056768K)], 0.0039079 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 3815K->3815K(9216K), 0.0008869 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 3815K->3796K(7168K)] 3815K->3796K(9216K), [Metaspace: 3212K->3212K(1056768K)], 0.0045989 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Exception in thread “main” java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:674)
at java.lang.StringBuilder.append(StringBuilder.java:208)
at tupobi.top.demoall.jvm.OOMTest.main(OOMTest.java:9)
Heap
PSYoungGen total 2048K, used 49K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 1024K, 4% used [0x00000000ffd00000,0x00000000ffd0c420,0x00000000ffe00000)
from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
ParOldGen total 7168K, used 3796K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 52% used [0x00000000ff600000,0x00000000ff9b51e8,0x00000000ffd00000)
Metaspace used 3243K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 340K, capacity 388K, committed 512K, reserved 1048576KProcess finished with exit code 1
[GC (Allocation Failure) [PSYoungGen: 2353K->493K(2560K)] 2703K->1163K(9728K), 0.0013604 secs] [Times: user=0.03 sys=0.01, real=0.00 secs]
抽取第二次GC,详细分析其过程。该GC为YoungGC,只会清理新生代,也就是2353->493,新生代总共占用JVM的1/3内存也就是2560K,老年代占2/3。2703->1163为此次GC,JVM堆内存总共清理了多少,9728位JVM总内存,0.0013604为此次GC耗时。
[Full GC (Ergonomics) [PSYoungGen: 2543K->0K(2560K)] [ParOldGen: 5394K->1840K(7168K)] 7937K->1840K(9728K), [Metaspace: 3212K->3212K(1056768K)], 0.0041044 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
抽取第一次FGC,详细分析其过程。新生代还是和之前一样 ,这里主要分析老年代。5394->1840为老年代清理的内存,老年代占2/3的堆空间,7937->1840为JVM总内存的清理,也就是新生代和老年代清理的总和。9728位虚拟机总内存。元空间中的数据一般不会被FGC清理。
GC算法
分代收集
- 次数上频繁收集Young区
- 次数上较少收集Old区
- 基本不动元空间
JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分回收是指新生代的YGC。因此GC按照回收的区域分为两种,一种是普通的GC(YoungGC/minorGC),一种是全局GC(majorGC/FullGC)
minorGC和FullGC的区别
- minorGC:只针对新生代区域的GC,因为大多数Java对象存活率都不高(生命周期短),所以minorGC非常频繁,一般回收速度也比较快。
- FullGC:指发生在老年代和新生代,出现FullGC一般会伴随至少一次的minorGC,FullGC一般比minorGC慢8-10倍。
四大GC垃圾回收算法:
-
引用计数法:
对没有被引用的对象进行回收,被引用一次计数器+1,反之-1。
缺点:
- 每次对对象赋值时均要维护引用计数器,且计数器本身也有一定的消耗。
- 较难处理循环引用
JVM一般不会采用这种算法。
-
复制算法(Copying):
新生代GC使用该算法。HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(from/to),默认比例为8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过一次Minor GC后,如果仍然存在的话,将会被移到Survivor区。对象在Survivor区每熬过一次Minor GC,年龄就会+1,当它的年龄加到一定程度后,就会被移动到老年代。因为年轻代中的对象基本上都是朝生夕死(90%以上),所以在年轻代的垃圾回收算法使用的是复制算法。
复制算法的基本思想是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的内存复制到另一块上面,复制算法不会产生内存碎片。
复制算法原理:
复制算法的优缺点:
- 优点:
- 速度快、吞吐量大
- 不会有内存碎片
- 缓存兼容,有引用关系的对象安排在较近的内存地址
- 缺点:
- 它多使用了一半的内存
- 如果对象存活率较高,相当于所有对象无效拷贝一遍。所以使用它的条件是对象存活率低,并克服50%的内存浪费。
- 优点:
-
标记清除(Mark-Sweep):
标记清除算法主要发生在老年代,主要是标记清除和标记整理混合实现。当程序运行时内存快耗尽了,GC线程就会触发并将程序暂停,随后将要回收的对象标记一遍,最终统一回收这些对象,完成标记清理工作接下来便让应用程序恢复运行。
缺点:
- 效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用提体验极其差劲。
- 清理后的空间不连续,会出现内存碎片,JVM不得不维持一个内存的空闲列表,这又是一种开销,并且寻找类似数组这样的连续的内存空间并不是太好找。
-
标记压缩(Mark-Compact):
在整理压缩阶段,不再对标记的对象做回收,而是通过所有存活的对象都向一端移动,然后直接清除边界以外的内存。可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。如此一来,当我们需要给新对象分配内存时,JVM只需持有一个连续内存的起始地址即可,这比维护一个空闲内存列表开销要小得多。
缺点:效率低,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上讲,标记清除、标记整理算法效率要低于复制算法。
GC算法总结
- 年轻代:年轻代的特点是区域相对较小,对象存活率低,所以适合用复制算法。
- 老年代:老年代的特点是区域大,对象存活率高,一般由标记清除与标记整理混合实现
所以GC算法是针对不同的区域,分代进行算法选择。GC算法(引用计数(已经不用)、复制、标清、标整)是内存回收的方法论,垃圾收集器就是算法具体的落地实现。
因为目前为止还没有完美的收集器出现,更没有万能的收集器,只是针对具体的应用场景选择最合适的收集器,即分代收集。
如何确定垃圾
-
引用计数法:会出现循环引用等问题,已经很少使用了
-
(枚举根节点做可达性分析)跟搜索路径算法:
哪些对象可作为GCRoot
- 虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Native方法)引用的对象。
JVM调参
查看进程参数配置:
常用参数配置
常见堆栈错误异常
栈溢出错误
@Test
public void test() {
stackOverFlowError();
}
private void stackOverFlowError() {
stackOverFlowError();
}
java.lang.StackOverflowError
at tupobi.top.demoall.SOFE_OOM.SOFETest.stackOverFlowError(SOFETest.java:13)
点进源码可以看到StackOverflowError其实是集成自Error的,Throwable有异常和Error,所以它不是异常是error。
OOM错误
Java heap space
/**
* -Xmx10m -Xms10m -XX:+PrintGCDetails
*/
@Test
public void test() {
String str = "a";
while (true) {
str += str + new Random().nextInt(1111111) + new Random().nextInt(111111);
str.intern();
}
}
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4349K->4349K(9216K), 0.0002376 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4349K->4292K(7168K)] 4349K->4292K(9216K), [Metaspace: 5276K->5272K(1056768K)], 0.0086028 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]java.lang.OutOfMemoryError: Java heap space
…
Process finished with exit code -1
GC回收时间过长
GC回收时间过长也会抛出OOM,GC overhead limit。
过长的定义是,超过98%的时间来做GC,而且多次GC只回收了不到2%的堆内存,就会抛出该错误。
假如不抛出GC overhead limit,那么GC清理的那一丢丢内存会被再次填满,迫使再次GC,这样恶性循环,CPU使用率一直居高不下 ,而GC却没有任何成果。
/**
* -Xmx10m -Xms10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*/
@Test
public void test2() {
int i = 0;
List<String> list = new ArrayList<>();
try {
while (true) {
list.add(String.valueOf(++i).intern());
}
} catch (Exception e) {
System.out.println("*************i" + i);
e.printStackTrace();
}
}
[Full GC (Ergonomics) [PSYoungGen: 1024K->1024K(2048K)] [ParOldGen: 7079K->7079K(7168K)] 8103K->8103K(9216K), [Metaspace: 5388K->5388K(1056768K)], 0.0206993 secs] [Times: user=0.03 sys=0.01, real=0.02 secs]
[Full GC (Ergonomics) [PSYoungGen: 1024K->0K(2048K)] [ParOldGen: 7100K->1301K(7168K)] 8124K->1301K(9216K), [Metaspace: 5388K->5388K(1056768K)], 0.0084147 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]java.lang.OutOfMemoryError: GC overhead limit exceeded
…
Process finished with exit code -1
Direct Buffer Memory Error
直接穿透jvm堆内存,干翻本地内存。
写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的 I/O方式。它可以使用native函数库直接分配堆外内存(本地内存),然后通过一个存储 在java 堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提升性能,因为避免了java 堆native堆来回复制数据。但是可能将本地内存干翻。
/**
* -Xmx10m -Xms10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*/
@Test
public void test3() {
ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
[Full GC (System.gc()) [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 1224K->1230K(7168K)] 1736K->1230K(9728K), [Metaspace: 5265K->5265K(1056768K)], 0.0073088 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694)
unable to create new native thread
导致原因:
- 应用程序创建太多线程了 ,超过系统负载。
- 服务器不允许应用程序创建这么多线程,Linux系统默认单个进程创建1024个线程。
解决办法:
- 想办法降低应用程序线程数。
- 修改Linux系统配置,扩大单个进程可以创建的最大线程数。
这里是在Windows系统下测试的:
@Test
public void test4() {
int i = 0;
while (true) {
System.out.println(i++);
new Thread(() -> {
try {
Thread.sleep(500000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
…
29075
29076java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
…Process finished with exit code -1073741571 (0xC00000FD)
OOM Metaspace
java8之后使用元空间来替代永久带。metaspace是方法区在HotSpot虚拟机中的实现,它与持久代最大的区别在于,元空间并不在虚拟机内存中,而是使用本地内存。
在java8中,classes metadata(the virtual machines presentation of java class),被存储在焦作metaspace的native memory中。它存放以下信息:
- 虚拟机加载的类信息
- 常量池
- 静态变量
- 即时编译后的代码
/**
* -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m -XX:+PrintGCDetails
*/
@Test
public void test5() {
}
只要限制元空间大小为8M,java程序RT包就可以占满:
[GC (Last ditch collection) [PSYoungGen: 0K->0K(73216K)] 1067K->1067K(854528K), 0.0007343 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(73216K)] [ParOldGen: 1067K->1067K(1072640K)] 1067K->1067K(1145856K), [Metaspace: 4731K->4731K(1056768K)], 0.0060031 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Exception in thread “main” java.lang.OutOfMemoryError: Metaspace
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:61)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:33)
at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:36)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:49)Heap
PSYoungGen total 73216K, used 2181K [0x000000076b580000, 0x0000000770200000, 0x00000007c0000000)
eden space 72704K, 3% used [0x000000076b580000,0x000000076b7a1600,0x000000076fc80000)
from space 512K, 0% used [0x0000000770180000,0x0000000770180000,0x0000000770200000)
to space 1536K, 0% used [0x000000076ff00000,0x000000076ff00000,0x0000000770080000)
ParOldGen total 1072640K, used 1067K [0x00000006c2000000, 0x0000000703780000, 0x000000076b580000)
object space 1072640K, 0% used [0x00000006c2000000,0x00000006c210acc0,0x0000000703780000)
Metaspace used 4750K, capacity 5034K, committed 5120K, reserved 1056768K
class space used 546K, capacity 594K, committed 640K, reserved 1048576KProcess finished with exit code 1
垃圾回收器
垃圾回收器分类
GC算法(引用计数(已经不用)、复制、标清、标整)是内存回收的方法论,垃圾收集器就是算法具体的落地实现。
因为目前为止还没有完美的收集器出现,更没有万能的收集器,只是针对具体的应用场景选择最合适的收集器,即分代收集。以java8为例,主要有以下四种垃圾回收器:
- Serial:串行垃圾回收器,它为单线程环境设计且只使用一个线程进行垃圾回收,启用的时候会暂停所有用户线程(STW stop the world)。所以不适合服务器环境。
- Parallel:并行垃圾回收器,多个垃圾收集线程并行工作,垃圾收集速度更快,但此时用户线程也是暂停的(STW),适用于科学计算、大数据处理、首台处理等弱交互场景。
- CMS:Concurrent Mark Sweep并发垃圾回收器,且使用标记清除算法,会产生内存碎片(所以产生了G1),用户线程和垃圾收集线程同时执行(不一定是并行,可能是CPU时间片轮转交替执行),不需要停顿用户线程,互联网公司多用它,适用于对响应时间有要求的场景。
- G1垃圾回收器:G1垃圾回收器将堆内存分割成不同的区域,然后并发的对其进行垃圾回收。
垃圾回收器调配
查看虚拟机默认的垃圾回收器:
java -XX+PrintCommandLineFlags -XX:+UseCompressedClassPointers
-XX:InitialHeapSize=266223488 -XX:MaxHeapSize=4259575808 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version “1.8.0_151”
Java™ SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot™ 64-Bit Server VM (build 25.151-b12, mixed mode)
修改程序使用的垃圾回收器,默认是并行的,配置JVM运行参数:
-XX:+UseSerialGC
GC参数说明
JVM Serve/Client模式说明,查看本机JVM模式 :
java -version
java version “1.8.0_151”
Java™ SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot™ 64-Bit Server VM (build 25.151-b12, mixed mode)
基本上只需要掌握Server模式即可,Clienr模式基本不会用。
操作系统区分:
- 32位Windows操作系统,不论硬件如何都默认使用Client的JVM模式。
- 32位其他操作系统,2G内存同时CPU2核以上用Server模式,低于该配置还是Client模式。
- 64位任何操作系统,Only Server模式
分代垃圾回收器
垃圾回收器细分
java的gc回收器类型主要细分一下几种,还有一种UseSerialOldGC废弃不用了 。
- UseSerialGC:
- UseParallelGC:
- UseConcMarkSweepGC:
- UseParNewGC:
- UseParallelOldGC:
- UseG1GC:
-
串行收集器
public class SerialGCTest { /** * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseSerialGC */ @Test public void test() { } }
[GC (Allocation Failure) [DefNew: 2752K->320K(3072K), 0.0027751 secs] 2752K->825K(9920K), 0.0028387 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 3072K->320K(3072K), 0.0023805 secs] 3577K->1201K(9920K), 0.0024174 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 3072K->276K(3072K), 0.0011404 secs] 3953K->1472K(9920K), 0.0011638 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap
def new generation total 3072K, used 1378K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 40% used [0x00000000ff600000, 0x00000000ff7137a8, 0x00000000ff8b0000)
from space 320K, 86% used [0x00000000ff900000, 0x00000000ff945360, 0x00000000ff950000)
to space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
tenured generation total 6848K, used 1195K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 17% used [0x00000000ff950000, 0x00000000ffa7ae60, 0x00000000ffa7b000, 0x0000000100000000)
Metaspace used 5271K, capacity 5440K, committed 5632K, reserved 1056768K
class space used 612K, capacity 659K, committed 768K, reserved 1048576KProcess finished with exit code 0
这样的话新生代和养老代都是用serial垃圾回收器。
-
并行收集器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KQv6K7fQ-1578637035962)(C:\Users\lizj\AppData\Roaming\Typora\typora-user-images\1575519709596.png)]
/** * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+UseParNewGC */ @Test public void test2() { }
[GC (Allocation Failure) [ParNew: 2752K->320K(3072K), 0.0012148 secs] 2752K->841K(9920K), 0.0012473 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 3072K->320K(3072K), 0.0008006 secs] 3593K->1281K(9920K), 0.0008237 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 3069K->319K(3072K), 0.0006381 secs] 4030K->1733K(9920K), 0.0006604 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap
par new generation total 3072K, used 1436K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 40% used [0x00000000ff600000, 0x00000000ff717408, 0x00000000ff8b0000)
from space 320K, 99% used [0x00000000ff900000, 0x00000000ff94fff0, 0x00000000ff950000)
to space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
tenured generation total 6848K, used 1413K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
the space 6848K, 20% used [0x00000000ff950000, 0x00000000ffab16e0, 0x00000000ffab1800, 0x0000000100000000)
Metaspace used 5273K, capacity 5440K, committed 5632K, reserved 1056768K
class space used 612K, capacity 659K, committed 768K, reserved 1048576K
Java HotSpot™ 64-Bit Server VM warning: Using the ParNew young collector with the Serial old collector is deprecated and will likely be removed in a future releaseProcess finished with exit code 0
可以看到ParNew和SerialOld已经不再被推荐结合使用了。
-
并行收集器(新生养老都用)
jdk1.8默认使用UseParallelGC
/** * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC */ @Test public void test3() { }
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->808K(9728K), 0.0015351 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2552K->504K(2560K)] 2856K->1054K(9728K), 0.0008498 secs] [Times: user=0.06 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2552K->504K(2560K)] 3102K->1345K(9728K), 0.0008833 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2552K->488K(2560K)] 3393K->1568K(9728K), 0.0009810 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap
PSYoungGen total 2560K, used 1618K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 55% used [0x00000000ffd00000,0x00000000ffe1a988,0x00000000fff00000)
from space 512K, 95% used [0x00000000fff80000,0x00000000ffffa020,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 1080K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 15% used [0x00000000ff600000,0x00000000ff70e218,0x00000000ffd00000)
Metaspace used 5275K, capacity 5440K, committed 5632K, reserved 1056768K
class space used 612K, capacity 659K, committed 768K, reserved 1048576KProcess finished with exit code 0
/** * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelOldGC */ @Test public void test4() { }
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelOldGC
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->800K(9728K), 0.0010616 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 2552K->504K(2560K)] 2848K->1034K(9728K), 0.0008229 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2552K->504K(2560K)] 3082K->1349K(9728K), 0.0007861 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2552K->504K(2560K)] 3397K->1575K(9728K), 0.0008630 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap
PSYoungGen total 2560K, used 1634K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 55% used [0x00000000ffd00000,0x00000000ffe1ab60,0x00000000fff00000)
from space 512K, 98% used [0x00000000fff80000,0x00000000ffffe010,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 1071K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 14% used [0x00000000ff600000,0x00000000ff70bd70,0x00000000ffd00000)
Metaspace used 5273K, capacity 5440K, committed 5632K, reserved 1056768K
class space used 612K, capacity 659K, committed 768K, reserved 1048576KProcess finished with exit code 0
-
CMS收集器(Concurrent Mark Sweep)
-
初始标记(initial mark):只是标记一下GC Roots能直接关联到的对象,速度很快,需要暂停所有的工作线程。
-
并发标记(concurrent mark):进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。主要标记过程,标记全部对象。
-
重新标记(remark):为了修成在并发标记期间,因用户线程继续运行而导致标记产生变动的那一小部分对象的标记记录,仍然需要暂停所有工作线程。确保新改动的数据标记无误。
-
并发清除(concurrent sweep):清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。基于标记结果,直接清除对象。
由于耗时最长的并发标记和并发清理过程中,垃圾收集线程可以和工作线程并发执行,所以总体上来讲,CMS收集器内存回收和用户线程是一起并发执行的。
优缺点:
-
优点:并发收集,低停顿,响应快。
-
缺点:1.并发执行,对CPU资源压力大。2.采用标记清除算法会导致大量碎片。
/** * -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC */ @Test public void test5() { }
-XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=3497984 -XX:MaxTenuringThreshold=6 -XX:NewSize=3497984 -XX:OldPLABSize=16 -XX:OldSize=6987776 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC [GC (Allocation Failure) [ParNew: 2752K->320K(3072K), 0.0017658 secs] 2752K->839K(9920K), 0.0021586 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 3072K->320K(3072K), 0.0011680 secs] 3591K->1243K(9920K), 0.0012088 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [ParNew: 3072K->320K(3072K), 0.0015191 secs] 3995K->1720K(9920K), 0.0015528 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap
par new generation total 3072K, used 1446K [0x00000000ff600000, 0x00000000ff950000, 0x00000000ff950000)
eden space 2752K, 40% used [0x00000000ff600000, 0x00000000ff719bc8, 0x00000000ff8b0000)
from space 320K, 100% used [0x00000000ff900000, 0x00000000ff950000, 0x00000000ff950000)
to space 320K, 0% used [0x00000000ff8b0000, 0x00000000ff8b0000, 0x00000000ff900000)
concurrent mark-sweep generation total 6848K, used 1400K [0x00000000ff950000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 5273K, capacity 5440K, committed 5632K, reserved 1056768K
class space used 612K, capacity 659K, committed 768K, reserved 1048576KProcess finished with exit code 0
-
-
Serial Old
理论指导即可,实际中JDK8已经被优化掉了。
/** * -Xms100m -Xmx100m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialOldGC */ @Test public void test6() { System.gc(); }
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Unrecognized VM option ‘UseSerialOldGC’
Did you mean ‘(+/-)UseSerialGC’?Process finished with exit code 1
可以看到,JDK8中已经某得了。
如何选择垃圾收集器
G1垃圾回收器
/**
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseG1GC
*/
@Test
public void testG1() {
System.gc();
}
-XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseG1GC -XX:-UseLargePagesIndividualAllocation [Full GC (System.gc()) 9216K->1563K(100M), 0.0080497 secs]
[Eden: 9216.0K(14.0M)->0.0B(14.0M) Survivors: 0.0B->0.0B Heap: 9216.0K(100.0M)->1563.5K(100.0M)], [Metaspace: 5264K->5264K(1056768K)]
[Times: user=0.03 sys=0.00, real=0.01 secs]
Heap
garbage-first heap total 102400K, used 1563K [0x00000000f9c00000, 0x00000000f9d00320, 0x0000000100000000)
region size 1024K, 1 young (1024K), 0 survivors (0K)
Metaspace used 5281K, capacity 5440K, committed 5632K, reserved 1056768K
class space used 612K, capacity 659K, committed 768K, reserved 1048576KProcess finished with exit code 0
可以看到,堆内存回收时已经不分年轻代还是养老代了,只有garbage-first heap和metaspace。
前面收集器的特点
- 年轻代和老年代是各自独立且连续的内存块
- 年轻代手机使用单eden+s0+s1进行复制算法
- 老年代收集必须扫描整个老年代区域
- 都是以尽可能少少而快速地执行GC为设计原则
G1垃圾收集器特点
G1是一种服务端的垃圾收集器,应用在多处理器和大内存容量内存环境中,在实现高吞吐量的同时,尽可能满足垃圾收集暂停时间的需求。另外,它具有以下特性:
- 像CMS收集器一样,能与应用程序线程并发执行。
- 整理空闲空间更快
- 需要更多时间来预测GC停顿时间
- 不希望牺牲大量的吞吐性能
- 不需要更大的java heap
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现更为出色:
- G1是有一个整理内存过程的垃圾收集器,不会产生很多地内存碎片
- G1的Stop the world(STW)更可控,G1在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
CMS垃圾收集器虽然减少了应用程序的运行时间,但是它还是存在着内存碎片问题。于是,为了去除内存碎片问题,同时又皮保留CMS垃圾收集器低暂停时间的优点,java7发布了一个新的垃圾收集器G1。
G1是2012年jdk1.7u4,oracle官方计划在jdk9中将G1变成默认垃圾收集器取代CMS。它是一款面相服务端的收集器,主要应用在多CPU和大内存服务器环境下,极大的减少了垃圾收集的停顿时间,全面提升服务器性能,逐步替换java8以前的CMS收集器。
主要改变是Eden, Suvivor和Tenured等内存区域不再是连续的了,而是变成一个个大小一样的region,每个region从1M到32M不等。一个region有可能属于Eden, Survivor或者Tenured内存区域。
- G1能充分利用多CPU、多核环境硬件优势,尽量缩短STW。
- G1整体上采用标记-整理算法,局部是通过复制算法,不会产生内存碎片。
- 宏观上看G1之中不再区分年轻代和老年代。把内存划分为多个独立的子区域(Region),可以近似理解为一个围棋棋盘。
- G1收集器里面将整个的内存区都混合在一起了,但其实本身依然在小范围内要进行年轻代和老年代的区分,保留了新生代和老年代,但它们不再是物理隔离的,而是一部分Region的 集合且不需要Region是连续的,也就是说依然会采用不同的GC方式来处理不同的区域。
- G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代和老年代的区别,也不需要完全独立的Survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换。
底层原理
GC算法将堆划分为若干个区域(Region),它仍然属于分代收集器。
这些区域的一部分包含新生代,新生代的垃圾收集器采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。
这些Region的一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成清理工作。这就意味着,在正常处理过程中,G1完成了堆的压缩(至少是部分堆压缩),这样就不会有CMS内存碎片问题的存在了。
在G1中,还有一种特殊的区域,叫Humougous(巨大的)区域,如果一个对象占用的空间超过了分区容量的50%以上,G1收集器就认为这是一个巨大对象。这些巨大对象默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了Humougous区,它用来专门存放巨型对象,如果一个H区还装不下这个对象,那么G1会寻找连续的H区来存储,为了能找到连续的H区,有时候JVM不得不Full GC.
回收步骤
G1参数配置
比起CMS主要有两个优势
- G1不会产生内存碎片
- 是可以精确控制停顿,该收集器把整个堆(新生代、老年代)划分为多个固定大小的区域,每次根据允许停顿的时间去手机垃圾最多的区域。