GC(Garbage Collection)
GC背景
创建对象会消耗内存,如果不回收对象占用的内存,内存使用率会越来越高,最终出现OutOfMemoryError(OOM)
在C++中专门有一个"析构函数"来回收对象占用的内存,Java有一个专门的GC线程,定时执行清理对象占用的内存
从JDK1.3开始到现在:GC经历了4个阶段的变化
1、串行垃圾收集
2、并行垃圾收集
3、并发垃圾收集
4、G1垃圾收集(Garbage First)
内存分配
JVM执行程序,将内存分为5块:
程序计数器:记录当前程序里面的指令执行的状态。
JVM栈:每个方法一旦调用,就会将该方法入栈,每个方法都有自己的局部变量列表,局部方法列表存储了对象的引用。
注意:一旦方法不停的入栈,占满了栈内存的空间就会出现StackOverflowError错误。
本地方法栈:Java由C++ 演变而来,建立在C++基础上的,C++提供了很多dll(动态链接库)给Java调用,主要加载C++的DLL,和我们程序没有多大关联。
堆栈:所有new产生的对象都在堆中,GC主要就是回收堆栈内存对象占用的内存空间。
方法区:当一个类成功加载到JVM,JVM就会存储该类的Class信息(package、类型信息class、属性信息field、方法信息method、构造方法信息、静态属性和方法信息)。
堆内存配置
-Xmx 最大堆内存 默认值:整个计算机物理内存的1/4 4G 最大值64G
建议不要超过计算机物理内存的1/2
-Xms 初始堆内存 默认值:整个计算机物理内存的1/64 256M
注意:工作中上面两个参数的配置最好一致(可以减少垃圾收集的频率)
-Xmn 设置堆内存年轻代大小 整个堆内存3/8
-Xss 占内存每个线程的大小 默认1M 128k
-XX:+PrintGCDetails 打印GC信息
以上JVM参数在哪里配置???
1. 可以在Eclipse启动之前配置 Eclipse.ini(软件启动项的根目录)
-XX:+UseG1GC 使用G1方式进行垃圾收集
XX:+UseStringDeduplication主要是用来消除长时间存活的重复字符串对象 ,注意它只能用在G1垃圾收集
2. Eclipse配置JDK的时候设置
串行:
-Xmx4096m -Xms4096m -Xmn1536m -XX:+PrintGCDetails
并行:
-Xmx4096m -Xms4096m -Xmn1536m -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=2 -XX:+PrintGCDetails
设置垃圾收集为并行收集,通常用于服务器端,场景:多个客户端连接一个服务器,此时服务器会采用并行垃圾收集。
并发:
-XX:+UseConcMarkSweepGC 并发垃圾收集
-Xmx4096m -Xms4096m -Xmn1536m -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails
G1:
-XX:+UseG1GC 使用G1作为垃圾收集
XX:G1HeapRegionSize=16m 设置每个区块大小
-Xmx4096m -Xms4096m -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:+PrintGCDetails
将堆内存划分为不连续分配的空间 ,将你的堆内存划分为1个或者多个区块(Region),每个区块分配的内存大小在(1m、2m、4m、8m、16m、32m)之间
@Test
public void testStringBuffer() {
for(;;) {
byte[] by = new byte[1*1024*1024];
}
}
@Test
public void testString() {
String str="";
for(int i=0;i<500000;i++) {
str+="i";
}
}
/**
* -Xmx16m -Xms16m -Xmn6m -XX:+PrintGCDetails
* 会出现OutOfMemoryError:因为垃圾收集的频率赶不上创建对象的频率
* 随便编写一个程序,如何让他永远不死(OutOfMemoryError,StackOverflowError),随便编写一个程序让他立马死掉
* javac命令编译程序 java命令运行程序 在bin目录下
* 你平时在工作中除了使用上面两个命令,还使用过哪些命令?
* javap 反编译字节码
* jconsole 监控程序
* jvisualvm 监控程序
* javadoc命令
*/
@Test
public void testByte() {
for(;;) {
//每次循环分配10MB的内存
byte[] by =new byte[1024*1024*1024];
}
}
小结:eden(伊甸区永远都是存储最新鲜的对象,也就是刚刚new出的对象),一旦伊甸区的使用率到达一个阀值(85%),启动GC,回收年轻代的对象将伊甸区不活动的对象转移到幸存区,当幸存区到达阀值,再次做垃圾回收,如果回收失败,将对象放入老年代,如果老年代到达阀值,会做full gc(全量回收),如果回收失败OOM(OutOfMemory)错误.
G1垃圾收集
[GC (Allocation Failure) [PSYoungGen: 1179648K->1270K(1376256K)] 1179648K->1278K(3997696K), 0.0011735 secs][Times: user=0.00 sys=0.00, real=0.00 secs]
GC启动垃圾收集
Allocation Failure: 分配空间失败
PSYoungGen: GC回收年轻代的内存
1179648K->1270K(1376256K)
1376256K 年轻代分配的内存空间
1179648K 垃圾收集之前年轻代使用的空间
年轻代内存使用率86% 将进行垃圾收集(minor 最轻量级的垃圾收集)
1270K 垃圾收集之后年轻代使用的空间
1179648K->1278K(3997696K)
3997696K:表示整个堆内存(年轻代+老年代)的总空间
1179648K:表示整个堆内存(年轻代+老年代)的在垃圾收集之前的使用空间
1278K:表示整个堆内存(年轻代+老年代)的在垃圾收集之后的使用空间
Heap
//年轻代总空间 1571328K ,使用空间837686K
PSYoungGen total 1571328K, used 837686K [0x0000000760000000, 0x00000007c0000000, 0x00000007c0000000)
//表示 eden space年轻代的伊甸区使用空间 1569792K ,使用率837686/1569792=53%
eden space 1569792K, 53% used [0x0000000760000000,0x0000000793143430,0x00000007bfd00000)
//from space表示 年轻代的幸存1区分配了1536k,使用率52%
from space 1536K, 52% used [0x00000007bfd00000,0x00000007bfdca510,0x00000007bfe80000)
// to space 表示 年轻代的幸存2区分配了1536k,使用率0%
to space 1536K, 0% used [0x00000007bfe80000,0x00000007bfe80000,0x00000007c0000000)
//ParOldGen total老年代分配的空间2621440K 使用了952K
ParOldGen total 2621440K, used 952K [0x00000006c0000000, 0x0000000760000000, 0x0000000760000000)
object space 2621440K, 0% used [0x00000006c0000000,0x00000006c00ee0a0,0x0000000760000000)
Metaspace used 4511K, capacity 4990K, committed 5248K, reserved 1056768K
class space used 532K, capacity 562K, committed 640K, reserved 1048576K
//Full GC 表示全量垃圾收集[年轻代和老年代的内存]
// 40369/40960=98% 老年代使用率到达98% 危险:老年代如果回收失败就会出现OutOfMemoryError
[Full GC (Ergonomics) [PSYoungGen: 404K->0K(24064K)] [ParOldGen: 40369K->1397K(40960K)] 40774K->1397K(65024K), [Metaspace: 4497K->4497K(1056768K)], 0.0029315 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
3. 在你编写程序的启动类上设置