本文已参与「新人创作礼」活动,一起开启掘金创作之路。
堆内存调优
//-Xms 设置初始化内存分配的大小 /164
//-Xmx 设置最大分配内存 默认 1/4
//-XX:PrintGCDetails //打印GC垃圾回收信息
//-XX:+HeapDumpOnOutOfMemoryError //OOM Dump
//-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
public class demo02 {
byte[] array = new byte[1*1024*1024];
public static void main(String[] args) {
ArrayList<demo02> list = new ArrayList<>();
int count = 0;
try {
while (true){
list.add(new demo02());//问题所在
count++;
}
} catch (Error e) {
System.out.println("count"+count);
e.printStackTrace();
}
}
}
13、垃圾回收算法GC
JVM在进行GC时,并不是对这三个区域(jdk1.8以后不存在永久区,改名元空间,不在JVM,是在本地内存中的)统一回收。 大部分时候,回收都是新生代~ ●新生代 ●幸存区(form区,to区) ●老年区
GC两种类:轻GC (普通的GC), 重GC (全局GC)
●JVM的内存模型和分区~详细到每个区放什么?
●堆里面的分区有哪些? Eden, form, to, 老年区,说说他们的特点!
●GC的算法有哪些? 标记清除法,标记整理,复制算法,引用计数器
●轻GC和重GC分别在什么时候发生?
1.引用计数法
在JVM中几乎不用,每个对象在创建的时候,就给这个对象绑定一个计数器(有消耗)。每当有一个引用指向该对象时,计数器加一;每当有一个指向它的引用被删除时,计数器减一。这样,当没有引用指向该对象时,该对象死亡,计数器为0,这时就应该对这个对象进行垃圾回收操作。
优点:
- 简单
- 计算代价分散
- “幽灵时间”短(幽灵时间指对象死亡到回收的这段时间,处于幽灵状态)
缺点:
- 不全面(容易漏掉循环引用的对象)
- 并发支持较弱
- 占用额外内存空间(计数器消耗)
2.复制算法
将可用内存划分为两块,每次只是用其中的一块,当半区内存用完了,仅将还存活的对象复制到另外一块上面,然后就把原来整块内存空间一次性清理掉。
这样使得每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就行,实现简单,运行高效。
优点: 空间连续,没有内存碎片,运行效率高。
缺点: 每次运行,总有一半内存是空的,导致可使用的内存空间只有原来的一半。复制收集算法在对象存活率高的时候,效率有所下降, 所以复制算法主要用在新生代幸存者区中的from区和to区,因为新生代对象存活率低。
3.标记-清除算法
为每个对象存储一个标记位,记录对象的状态(活着或是死亡)。分为两个阶段,一个是标记阶段,这个阶段内,为每个对象更新标记位,检查对象是否死亡;第二个阶段是清除阶段,该阶段对死亡的对象进行清除,执行 GC 操作。
优点:
- 实现简单,标记—清除算法中每个活着的对象的引用只需要找到一个即可,找到一个就可以判断它为活的。
- 此外,这个算法相比于引用计数法更全面,在指针操作上也没有太多的花销。更重要的是,这个算法并不移动对象的位置。
缺点:
- 需要进行两次动作,标记获得的对象和清除死亡的对象,所以效率低。
- 死亡的对象被GC后,内存不连续,会有内存碎片,GC的次数越多碎片越严重。
4.标记-压缩/整理算法
总结
内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存利用率:标记压缩算法=标记清除算法>复制算法
思考一个问题:难道没有最优算法吗?
答案: 没有,没有最好的算法,只有最合适的算法----->GC:分代收集算法
年轻代:
- 存活率低
- 复制算法!
老年代:
- 区域大:存活率
- 标记清除(内存碎片不是太多)+标记压缩混合实现