jvm的执行过程:1.加载.class文件 2.管理并分配内存 3.执行垃圾会收集
jvm在运行过程中会把它所管理的内存划分为若干个不同的数据区域 :
1.线程私有:1> 程序计数器 2>本地方法栈 3>虚拟机站
2.线程共享:1>堆 2>方法区
上面的线程私有和线程共享被统称为运行时数据区(内存)
程序计数器:指向当前线程正在执行的字节码指令地址(行号)
为什么需要程序计数器?
答:java是多线程的。这就意味着线程之间需要切换,为了确保多线程的情况下的程序正常执行。
程序计数器会不会报内存溢出?
答:不会
虚拟机栈(-Xss):存储当前线程运行方法所需的 数据 指令 返回地址
虚拟机栈中包含很多栈帧
每个栈帧包括:1.局部变量表 2.操作数栈 3.动态链接 4.返回地址
虚拟机栈异常(StockOverFlowError)
原因:执行的虚拟机栈深度(-Xss)大于虚拟机栈允许的最大深度(方法的递归调用)
如何处理?
让栈的深度变小
例子:当深度是 128K 1000线程 =128M
当深度是 1M 1000线程 =1G
方法区:1.类信息 2.常量 3.静态变量 4.即使编译期编译的代码
java堆: 1.对象实例 2.数组
JMM(jvm存模型)
JMM和上面的内存(运行时数据区)不是一回事
JMM包括: 1.新生代 2.老年代 3.永久代(JDK1.8之前叫做永久代,之后叫元空间。为什么变成元空间?因为可以使用本地内存)
新生代:新的对象 占三分之一的空间
新生代的Eden区和F交换区以及T交换区的比例是8:1:1
正常情况下新的对象会先经过E然后是F S
老年代:旧的对象 占三分之二的空间
对象都是有生命周期的,所以JVM中需要垃圾回收机制,所以就有了垃圾回收的依据(机制)
垃圾回收的依据:可达性分析算法
看到可达性分析算法我们就会想到 GC roots
GC roots引用了ob1,ob1引用了ob2,ob3......
然而GC roots没有引用ob5
这样相互引用就是可达性
在java中,可作为GC roots的对象包括:
1.虚拟机栈(局部变量表)中引用的对象。
2.方法区:类静态属性引用的对象(static)
3.方法区:常量引用的对象(final)
对象的分配
新生代是怎么到达老年代
1.正常情况下对象在Eden区和交换区没有被垃圾回收器回收,当他的“年龄”大于15的时候就会进入老年代
注:1.年龄只有等对象进入交换区的时候才会有
2.对象进入两个交换区的时候,年龄会依次增加(相互交换)
2.当对象的空间大于新生代的空间时,就会直接进入老年代
3.当多个对象没有被垃圾回收器回收,且年龄总和大于正常年龄,也会提前进入老年代
4.当对象的空间大于老年代的空间时,会内存溢出
5.当将要进入Eden区的新对象和已经在Eden区的老对象空间大小总和大于Eden区空间的时候,垃圾回收器会回收老对象留下新对象。
新生代:Minor GC —>采用 复制回收算法
老年代:Full GC—>采用 标记清除算法和标记整理算法
在永久代会不会发生垃圾回收?
答:会,但效率低下
总结:
虚拟机栈的生命周期是跟随线程的
堆是垃圾回收的重点区域
GC的发展趋势:有色指针,加载屏障
JVM常用问题处理方式:
1.保存堆栈快照日志(在重启前)
2.分析内存泄漏
内存泄漏原因:内存中的某个或是几个对象始终是可达的,这样就会使内存可使用空间减小。例子: 对象空间 1M 内存 3M 那么可使用空间就是3-1=2M。怎么处理?查源码看它的GC回收方法
3.调整内存设置。
4.控制垃圾回收频率。
5.选择合适的垃圾回收器。