- String s = new String("abc"),这里创建了两个对象,"abc"存在常量池,new String("abc")存在堆上,s引用存在栈中,指向堆内存的对象
- 堆(新生代,老年代[调用次数多,大对象])
- 一切new出来的对象
- 方法区(类信息,常量池,静态变量,jit编译后的代码)
- java栈
- 对象引用和基本数据类型
- 本地方法栈(调用native方法,执行其他底层语言)
- 程序计数器(线程私有,为了切换线程能正常执行)
- 在JDK1.8版本废弃了f方法区,替代的是元空间,元空间并不在JVM中,而是使用本地内存
- 一般策略是分代回收,分为新生代(Eden,存活区0,存活区1,默认比例8:1:1),老年代
- 一般是直接分配在Eden区,Eden区满后,minorGC,没gc掉的放在存活区0
- 当存活区0满了后,将还活着的分配到存活区1
- 以后Eden区执行minorGC,把没gc的放到存活区1
- 存活区的数据来回切换n次,移到老年区,对应jvm参数:-XX
- MaxTenuringThreshold控制,大于该值进入老年代
- 标记-清除
- 复制算法(新生代)
- 标记-压缩(老年代)
- 标记清除
- 遍历所有gc root,标记所有可达对象,将未被标记的对象清除
- 缺点:清除后内存分散
- 复制算法
- 新生代使用的算法
- 将内存分为两块,每次只使用一块,在回收时,将正在使用的存活对象复制到未使用的内存块中,之后清除正在使用的内存块所有对象,交换两个内存的角色,完成垃圾回收
- 缺点:需额外的空闲内存
- 标记整理
- 老年代使用的算法
- 和标记清除法类似,不过回收时,会对所有存活的对象按地址排序,其他的清除
- 弥补了标记清除内存分散的特点,同时也比复制算法节约空间,但有个排序整理过程,所以比较慢
- Java heap space(堆溢出)
- 虚拟机分配的到堆内存空间已经用满
- 是否有死循环或不必要地重复创建大量对象
- 调整jvm参数,xms,xmx
- PermGen space(方法区溢出)
- 载入的class等信息超过了阀值
- -XX:PermSize和-XX:MaxPermSize设置永久代大小即可
- Thread Stack space(栈溢出)
- 检查程序中递归调用是否未控制好出口导致递归死循环
- jvm参数调整xss
- 内存泄漏
- 无效对象不能被释放
- 借助MAT等工具检查是否泄漏
- 单列造成的内存泄漏
- 如果一个对象不再需要被使用,而单例还持有该对象的引用,造成不能被正常回收
- 各种连接
- io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的
- 内部类造成的内存泄漏
- 非静态内部类会持有它们所属外部类的引用,一旦没释放可能导致一系列的后继类对象没有释放
- 静态集合类引起内存泄漏,引用的对象不能被释放
- 调优策略:不要定义太多常量,占内存。存放在方法区,不会被gc
- 堆内存 Xms初始值和Xmx最大值设成一致 因为gc会使内存趋向于初始值,减少gc次数
- -XX:+PrintGC 每次触发GC的时候打印相关日志
- -Xmn 新生代堆最大可用值
- -Xss 设置最大调用深度(解决栈溢出)
- MaxTenuringThreshold控制,大于该值进入老年代
- 最大堆内存最小堆内存设置为一致,因为gc会靠向最小堆内存,导致平凡的full gc
- Perm Space(永久代),超出大小会OutOfMemory,方法区(方法区是jvm规范,永久代和元空间是实现),保存class、运行时常量池、字段、方法、代码、JIT代码等,在jdk1.7以前,在永久代,1.8元空间,在物理内存上
- jstack(跟踪线程堆栈,执行到哪一步)jmap(看内存情况)jvm调优工具