JVM内存结构
JAVA夸平台的原因
Java虚拟机是一个可以执行Java字节码的虚拟机进程。
Java源文件被编译成能被Java虚拟机执行的字节码文件。
Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。
Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
JVM内存结构
程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。
Java虚拟栈:存放基本数据类型、对象的引用、方法出口等,线程私有。
Native方法栈:和虚拟栈相似,只不过它服务于Native方法,线程私有。
Java堆:java内存最大的一块,所有对象实例、数组都存放在java堆,GC回收的地方,线程共享。
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等,即永久带。
- 回收目标主要是常量池的回收和类型的卸载,各线程共享
- 它有个别命叫Non-Heap(非堆)。当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
运行期间也可以将新的常量放入池中,String类的intern()方法就是这样的。
JAVA栈
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。
局部变量表又包含基本数据类型,对象引用类型。
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果。
如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)
参数 -Xss 去调整JVM栈的大小
JAVA堆:垃圾收集器管理的内存区域
共享内存区 = 持久带 + 堆
持久带 = 方法区 + 其他
Java堆 = 老年代 + 新生代 新生代与老年代的比例的值为1:2,可以通过参数 –XX:NewRatio 配置
新生代 = Eden + S0 + S1 Edem : from : to = 8 : 1 : 1 可以通过参数 –XX:SurvivorRatio 来设定
Survivor区中的对象被复制次数为15进入老年代
当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出 OutOfMemoryError异常,也就是内存溢出。
Survivor的意义:
- 如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发MajorGC.
老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比MinorGC长得多,所以需要分为Eden和Survivor。16次进入老年代 - 设置两个Survivor区最大的好处就是解决了碎片化
对象分配规则:
- 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
- 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象,数组和大的字符串)。
- 长期存活的对象进入老年代
- 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
- 空间分配担保。 每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC
常见异常:
java.lang.OutOfMemoryError:PermGenspace
java.lang.OutOfMemoryError:Java heap space
StackOverflowError
OutOfMemoryError