自己个人理解,如有错误请指教。
堆的模型
栈的模型
堆
堆是储存时的单位,这块是JVM管理最大的一块,属于线程共享,主要存放的是对象的实例和数组。通常GC都是发生在这一块。
栈
栈是方法执行的单位,这块是线程私有的,生命周期和线程一致。描述的是java方法只想的内存模型,每个方法在只想时都会创建一个栈帧用于存储局部变量表,方法返回值,操作数栈,动态链接,方法出口。每一个方法从调用到执行结束,都是一个栈帧进栈出栈的过程。
栈跟队列的区别 栈先进后出,队列先进先出。
局部变量表,8大基本类型,对象实例的地址。
方法区
方法区里都有啥:static,final,Class类信息,常量池。
在JDK7之前,习惯上把方法区称为永久代,JDK8开始,使用元空间替换了永久代
由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。
到了JDK8,完全废弃了永久代的概念,改用为在本地内存中实现的元空间代替。
JVM 调优
查看jvm内存
public static void main(String[] args) {
long max = Runtime.getRuntime().maxMemory();
long total = Runtime.getRuntime().totalMemory();
System.out.println("虚拟机最大内存"+max+"字节,"+(max/(double)1024/1024)+"MB");
System.out.println("虚拟机初始化总内存"+total+"字节,"+(total/(double)1024/1024)+"MB");
}
设置堆内存大小
-Xms512m -Xmx512m -XX:+PrintGCDetails // 命令
参数:
-Xmx:最大堆大小
-Xms:初始堆大小
-Xmn:年轻代大小
PrintGCDetails 输出GC的描述信息。
使用JProfiler工具
模拟内存溢出
public static void main(String[] args) {
ArrayList<TestJvm2> objects = new ArrayList<>();
int count = 0;
try {
while (true){
objects.add(new TestJvm2());
count = count++;
}
}catch (Exception e){
System.out.println("count="+count);
e.printStackTrace();
}
}
设置参数
-Xms5m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
dump下内存文件,使用工具分析。
也可查看线程
垃圾回收
轻GC,主要针对于新生区。
- 当先对象生成,要向新生区申请空间失败时,会触发,并对新生区进行GC,对from和to 幸存区进行复制算法,进行GC,保证to是空的
重GC(FGC),对整个堆进行整理
- 老年代被写满
- System.gc()被显示调用
垃圾回收算法
~ 标记清除算法
- 算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。
- 缺点是效率问题标记和清除的效率都不高,并且清除完会产生大量不连续的内存碎片,可能程序在运行中需要存储大对象没有足够的连续空间,导致提前进行GC。
~ 复制算法
- 它会把内存分为大小相等2块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
- 样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,降低了内存的利用率,持续复制长生存期的对象则导致效率降低,还有在分配对象较大时,该种算法也存在效率低下的问题。
~ 标记清除整理
- 先执行标记清除,然后把存活对象都向一端移动,最后清除边界以外的内存。