Jvm内存分为哪几个主要部分?
堆
主要用来存储实例对象,现今HotSpot虚拟机将该区做了分代分区处理,分为Eden,2个Supervisor,和Old
方法区
存储编译后产生的信息,如class,静态变量和常量等,也可以叫Non-heap,或者在HotSpot虚拟机中称为永久代
jvm栈
用来执行方法
本地方法栈
执行本地方法
PC计数器
有哪些原因会产生内存溢出?
Java Heap Space堆溢出
内存泄漏leak
查看GCRoots引用链,可能是有对象一直持有不该持有的引用导致未被正常回收
内存溢出overflow
如果不存在leak说明对象都是必须存在的,但是内存确实不够,想办法优化代码结构,或者增大Java堆内存大小
StackOverFlow栈溢出
方法调用深度过高,简称栈帧太大或者JVM栈太小
unable to create new native thread无法创建线程
每个线程都有独立的方法区和JVM栈区
当线程过多,这两个区的内存会很快被瓜分干净
PermGen Space永久代溢出
JDK1.6之前 String.intern方法会将对象转移到方法区(现今HotSpot虚拟机的永久代),并返回实例引用,如果已经存在则会直接返回引用
1.7之后,intern只会将String类的首次实例放进方法区,之后的不会再放进,这就是所谓的“去永久化”
直接内存溢出
就是主机内存直接不够了
有关GC的三个问题
哪些内存需要回收?
通过算法明确哪些对象需要被回收,存在两种算法:
计数算法(JVM没有采用)
一个对象存在一个地方引用它时,他的计数器+1,即记录引用数量,当计数器为0说明不再被使用,可以回收。但是如果出现循环引用则无法回收,A->B,B->A,虽然都没用了但是都无法回收
可达性分析算法
GC Roots是什么?是指JVM栈中正在调用方法中引用的对象,永久代静态变量和常量引用的对象,本地方法栈中native方法引用的对象这四种对象的集合。
该算法可以通过遍历GC Roots来判断对象是否可达
这里回顾一下四种引用
什么时候回收?
当第一次扫描判断不可达的对象会被标记,并由虚拟机判断该对象是否有必要执行finalize方法,如果finalize方法被覆盖或者已经调用过则会被回收
如果没被调用过,则会被判定为有必要执行,然后该对象会被移动到F-Queue队列中,并由Finalizer线程去执行队列中每个元素的Finalize方法
这时会开启第二次标记,如果对象在finalize方法中成功抱上大腿拯救自己了,则会退出队列,如果没有拯救成功那就会真被回收了
方法区(永久代)会被回收吗?
会,但是回收效率低,需要触发Full GC,把废弃常量和无用类回收掉
什么是无用类?没有任何实例对象、对应的ClassLoader已经被回收、对应的Class类没有任何引用,且没有地方会通过反射访问它
如何回收?
四种回收算法
标记清除:只删除
标记整理:删除之后重新排列整理
复制:平分出2个区,删除当前区域之后两个区直接互换
分代回收:Eden新生代,2个Supervisor,Old老年代,PermGem永久代
集中收集器
Serial:复制算法,单线程,阻塞运行
ParNew:复制算法,Serial的多线程版本
Parallel Scavenge:复制算法,多线程,吞吐量大
Serial Old:标记整理算法,单线程,专用于回收Old
Parallel Old:标记整理算法,多线程,吞吐量大
CMS:标记清除,多线程并发收集,最低停顿
G1:分代收集,标记整理,低停顿,预测停顿时间
对象分配
大部分是朝生夕死,优先在Eden分配,不够时发起Minor GC
什么是Minor GC?
Eden+某1个Supervisor的GC
另一个用来存放对象,GC Eden和S0时,把剩余对象都放到S1
下一GC Eden和S1,然后剩余的放到S0
开始前要检查老年代最大连续空间是否足够,不够直接Full GC
新生区(Eden+2个Supervisor)采用复制算法尝试将GC后的存活对象放入另一个Supervisor(可能放不进去,部分进入Old),再看Eden够不够,不够就再尝试将这个对象直接放到Supervisor(可能放不进去),如果Supervisor再不够就要放到Old
长字符串或者数组这类大对象直接放入老年代,所以避免朝生夕死
Eden中经历一次Minor GC之后,依然存活则会被尝试放入Supervisor,放不进去直接进老年代,放的进就放进去,每经过一次Minor GC则会年龄增长一岁,当达到15岁时进入老年代(这个可以通过设置JVM参数改变)
类的加载
加载过程分为5个步骤
加载
通过全限定名(com.a.b.aclazz)来获取此类的二进制字节流
将这个常量,静态变量,编译后代码的运行区域放入方法区
在方法区生成对应的class对象(类的信息)
验证
保证类符合虚拟机要求的标准
准备
静态变量赋初值0,等待初始化阶段赋值,如果是常量则直接赋值
解析
将类的二进制数据中的符号引用转换为直接引用
初始化
静态变量赋初值
加载器有三种
BootStrap ClassLoader启动类加载器,用来加载JDK/lib类库
Extention ClassLoader扩展类加载器,用来加载JDK/lib/ext类库
Application ClassLoader应用类加载器,用来加载classpath下的用户自定义class
还可以加入自己定义的类加载器
双亲委派模型
先让父类加载器加载父类,然后逐步递归