【JVM】总结篇-运行时内存篇
文章目录
- JVM内存模型(内存结构)
- 程序计数器 pc
- 虚拟机栈
- 本地方法栈 native
- 堆
- 堆空间
- 堆中一些JVM参数
- 堆中垃圾回收过程
- MinorGC MajorGC FullGC
- 年轻代GC(Minor GC)触发机制:
- 老年代GC(Major GC/Full GC)触发机制:
- Full GC触发机制:
- OOM解决?
- 方法区
- 直接内存
- StringTable
JVM内存模型(内存结构)
程序计数器 pc
它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。执行引擎的字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。
-
它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。不会随着程序的运行需要更大的空间。
-
在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
-
它是唯一一个在Java 虚拟机规范中没有规定任何OutOtMemoryError 情况的区
多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复。
虚拟机栈
栈和堆的区别
角度一:GC;OOM
角度二:栈、堆执行效率
角度三:内存大小;数据结构
角度四:栈管运行;堆管存储。
栈中存在垃圾回收吗?不存在GC ; 存在OOM
Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个 StackOverflowError 异常。
如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出—个 OutOfMemoryError 异常。
什么情况下会发生栈内存溢出?
一、局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。
二、递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。
如何设置栈内存的大小? -Xss size (即:-XX:ThreadStackSize)
如 -Xss1024k
栈内部的单位是栈帧
每个栈帧中存储着:
局部变量表(Local Variables)
操作数栈(Operand Stack)(或表达式栈)
动态链接(DynamicLinking) (或指向运行时常量池的方法引用)
方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
一些附加信息
局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
动态链接:Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
动态链接引出 ①虚方法 ②invokedynamic指令 ③虚方法表
public class LocalVariableThreadSafe {
//s1的声明方式是线程安全的,因为线程私有,在线程内创建的s1 ,不会被其它线程调用
public static void method1() {
//StringBuilder:线程不安全
StringBuilder s1 = new StringBuilder();
s1.append("a");
s1.append("b");
//...
}
//stringBuilder的操作过程:是线程不安全的,
// 因为stringBuilder是外面传进来的,有可能被多个线程调用
public static void method2(StringBuilder stringBuilder) {
stringBuilder.append("a");
stringBuilder.append("b");
//...
}
//stringBuilder的操作:是线程不安全的;因为返回了一个stringBuilder,
// stringBuilder有可能被其他线程共享
public static StringBuilder method3() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("a");
stringBuilder.append("b");
return stringBuilder;
}
//stringBuilder的操作:是线程安全的;因为返回了一个stringBuilder.toString()相当于new了一个String,
// 所以stringBuilder没有被其他线程共享的可能
public static String method4() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("a");
stringBuilder.append("b");
return stringBuilder.toString();
/**
* 结论:如果局部变量在内部产生并在内部消亡的,那就是线程安全的
*/
}
}
本地方法栈 native
和虚拟机栈类似,只不过它是管理native本地方法。
堆
几乎”所有的对象实例都在这里分配内存。
所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer, TLAB)。
堆空间
JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor
JVM的内存结构,Eden和Survivor比例。
java对象的生命周期长短
几乎所有的Java对象都是在Eden区被new出来的。
绝大部分的Java对象的销毁都在新生代进行了。
堆中一些JVM参数
什么是空间分配担保策略?
堆中垃圾回收过程
什么时候对象会进入老年代?(渣打银行)
什么时候对象会进入老年代?(顺丰)
问什么幸存者区15次进入老年区,懂原理吗?(58)
JVM的伊甸园区,from区,to区的比例是否可调?(花旗银行)
JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代(字节跳动)
什么时候对象会进入老年代? (字节跳动)
对象在堆内存创建的生命周期 (蚂蚁金服)
重点讲讲对象如何晋升到老年代,几种主要的JVM参数 (蚂蚁金服)
新生代和老年代的内存回收策略 (蚂蚁金服)
什么时候对象可以被收回? (蚂蚁金服)
一次完整的GC流程
内存分配策略(或对象提升(promotion)规则):
如果对象在Eden 出生并经过第一次MinorGC 后仍然存活,并且能被Survivor 容纳的话,将被移动到Survivor 空间中,并将对象年龄设为1 。对象在Survivor 区中每熬过一次MinorGC , 年龄就增加1岁,当它的年龄增加到一定程度(默认为15 岁,其实每个JVM、每个GC都有所不同)时,就会被晋升到老年代中。
/** 测试:大对象直接进入老年代
* -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
*/
public class YoungOldAreaTest {
public static void main(String[] args) {
byte[] buffer = new byte[1024 * 1024 * 20];//20m
}
}
MinorGC MajorGC FullGC
Minor GC 与 Full GC 分别在什么时候发生?(腾讯)
老年代的垃圾回收机制什么时候触发,自动触发的阈值是多少(蚂蚁金服)
新生代的垃圾回收什么时候触发(蚂蚁金服)
简述 Java 内存分配与回收策略以及 Minor GC 和Major GC(国美)
什么时候发生Full GC(58)
简述 Java 内存分配与回收策略以及 Minor GC 和Major GC (百度)
JVM垃圾回收机制,何时触发Minor GC等操作 (蚂蚁金服)
JVM的一次完整的GC流程(从ygc到fgc)是怎样的(蚂蚁金服)
描述JVM中一次full gc过程 (腾讯)
什么情况下触发垃圾回收? (阿里)新生代的垃圾回收什么时候触发(花旗银行) 老年代的垃圾回收机制什么时候触发,自动触发的阈值是多少(花旗银行)
年轻代GC(Minor GC)触发机制:
当年轻代空间不足时,就会触发Minor GC。这里的年轻代满指的是Eden区满,Survivor满不会引发GC。(每次 Minor GC 会清理年轻代的内存。)
因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。这一定义既清晰又易于理解。
Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。
老年代GC(Major GC/Full GC)触发机制:
Full GC触发机制:
OOM解决?
为什么需要把Java堆分代?不分代就不能正常工作了吗?
其实不分代完全可以,分代的唯一理由就是优化GC性能。如果没有分代,那所有的对象都在一块,就如同把一个学校的人都关在一个教室。GC的时候要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而很多对象都是朝生夕死的,如果分代的话,把新创建的对象放到某一地方,当GC 的时候先把这块存储“朝生夕死”对象的区域进行回收,这样就会腾出很大的空间出来。
为什么有TLAB(Thread Local Allocation Buffer)?
什么是TLAB?
从内存模型而不是垃圾收集的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Eden空间内。
选项“-XX:+/-UseTLAB”设置是否开启TLAB空间。
方法区
栈 堆 方法区
在jdk7及以前,习惯上把方法区,称为永久代。jdk8开始,使用元空间取代了永久代。元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。
方法区jvm参数设置
方法区存了什么
注意运行时常量池:
为什么要用元空间替代永久代?
1)为永久代设置空间大小是很难确定的。
2)对永久代进行调优是很困难的。
StringTable为什么要调整?
jdk7中将StringTable放到了堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。
这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。
方法区的垃圾收集
主要回收两部分内容:常量池中废弃的常量和不再使用的类型。
在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。
直接内存
StringTable
1.通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中。
2.字符串常量池中是不会存储相同内容的字符串的。