4. 运行时数据区
关于运行时数据官方解释:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
4.1 堆 (heap)
- 堆在虚拟机中是一块共享区域, 存放 对象实例 和数组;
- 堆在虚拟机启动的时候创建。
- 可调整堆的大小
- 堆内存不够的时候会发生OOM
-
java.lang.OutOfMemoryError: Java heap space
代码演示/** * 演示堆溢出 * 设置堆 大小为:-Xms10m -Xmx10m */ public class HeapDemo { private static byte[] arry = new byte[1024*1024*12]; public static void main(String[] args) { System.out.println(HeapDemo.arry); } }
4.1.1 堆图解
由前面介绍我们了解到,一般对象和数组的创建会在堆中分配内存,但是具体存放在堆中的那个区域呢?
4.1.2 堆的组成部分
-
Young区 (新生代)
Eden区 ,s0区,s1区大小比例默认为:8:1:1
-
Eden
新建对象会分配在Eden区,除非特别大的对象会直接分配在Old去
假如有 A B C三个对象要在Eden区分配内存空间,但其实Eden区是有具体大小的,比如Eden区大小为100M,此时还剩余1M,已经不够给对象分配内存空间了,这个时候Eden区就会做一次 垃圾回收,我们称之为 Minor GC(也就是Yong Gc) 。 经过GC之后,有些对象就会被清理,有些对象还存活着,将存活着的对象复制到 Survivor区,然后将Eden区这些对象再清理
-
Survivor区(S0+S1)
由图解可知,Survivor区分为 S0,S1,也称为 from , to 区;
同一个时间点,S0和S1 只能有一个有数据,另一个是空的;
接着上述GC逻辑,Young GC的时候,Yong区对象年龄会+1,Eden区存活的对象会复制到 to区(空的Survivor区域),from区的数据有两个去处:
对象年龄没到设置的阈值(默认是15,也可设置-XX:MaxTenuringThreshold=6),from区中的对象会复制到 to区;到了设置阈值,会复制到 Old区。
Minor GC 会一直重复这样的过程,直到 to 区被填满,然后将所有对象复制到Old区
-
-
Old 区(老年代)
通过以上分析了解到,Old区域存放的对象都是年龄比较大的对象或者是超过了阈值。在Old区也是会发生GC的,我们称之为 Major GC。
4.1.3 对象的一生理解
我是一个对象,我出生在Eden区,在这里我还看到很多和我长得差不多的小兄弟我们在Eden区玩了很长时间,慢慢的Eden区的人越来越多 ,我被迫去了Survivor 区,自从去了Suvivor区 ,我就开始了漂泊的生涯,一会 在 from 区 ,一会在 to 区 ;直到我到了18岁,妈妈说我说成人了,是时候出去社会上闯闯了。
于是我就去了Old区,Old区有许多许多的人,比 Survivor区人多多了,这里的人年龄都比较大,我也认识了许多人。就这样在我在Old区生活了好久好久,突然有一天我就被GC回收了,很不舍得离开了这个世界。
4.2 虚拟机栈(Java Virtual Machine Stack)
-
虚拟机栈属于线程私有,与线程同时创建
-
有固定大小,也可以动态更改
-
如果需要计算用的栈空间不够,会报
StackOverflowError
; -
如果没有足够的内存来为新线程创建初始Java虚拟机堆栈,则Java虚拟机机器抛出一个
OutOfMemoryError
-
每一个栈帧对应一个被调用的方法,栈帧中存放:
-
局部变量表(local variables):方法中定义的局部变量
-
操作数栈( operand stack):以压栈和出栈的方式存储操作数
-
指向运行时常量的引用(a reference to the run-time constant pool)
-
动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
-
返回地址:方法执行开始后,有两种方式可以退出,第一种方式是方法正常执行结束;第二种方式是方法执行过程中出现异常而在方法体内没有进行异常处理。
-
代码演示虚拟机栈中内容
public class Test { public static void main(String[] args) { test1(1,2); } // 针对 test1方法 public static int test1(int a,int b){ a = 2; int result = a + b ; return result; } }
进入到Test.class文件目录下,通过
javap -c Test.class >Test.txt
命令进行反编译生成汇编代码命令官网地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.iload
Compiled from "Test.java" public class com.yg.threadpool.jvm.Test { public com.yg.threadpool.jvm.Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_1 1: iconst_2 2: invokestatic #2 // Method test1:(II)I 5: pop 6: return public static int test1(int, int); Code: 0: iconst_2 //把int类型的数值2压入 【操作数栈】 1: istore_0 // 把int类型的值存入【局部变量0,也就是a】 2: iload_0 //从【局部变量0】中装载int类型入栈 3: iload_1 //从【局部变量1,b】中装载int类型入栈 4: iadd //将栈顶元素弹出栈,执行add操作 5: istore_2 //把int类型的值存入【局部变量2,也就是result】 6: iload_2 //从【局部变量2】中装载int类型入栈 7: ireturn //从方法中返回int类型的数据 }
栈帧示例图如下:
-
-
每个被线程执行的方法,为栈中的栈帧,即每个方法对应一个栈帧,调用一个方法就有压栈;调用完成一个方法,就会弹栈
-
java.lang.StackOverflowError
代码演示/** * 演示 StackOverFlow * 设置线程堆栈大小:Xss10k */ public class StackOverFlowDemo { public static long count = 0; public static void main(String[] args) { //递归调用 test(); } public static void test (){ System.out.println(count++); test(); } }
4.3 方法区(Method Area)
- 方法区属于线程共享区域,是在虚拟机启动时创建的
- 存储已被 加载的类信息(加载第2步),常量,静态变量 ,方法数据,方法以及构造器的代码等
- 方法区中内存不够时会报
OutOfMemoryError
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uEGxajIl-1583804456044)(深入学习JVM.assets/image-20200310023611651.png)]
4.4 程序计数器(The pc Register)
- 每个线程都有自己的程序计数器
- 执行java 方法,程序计数器会记录指令地址;如果执行native方法,程序计数器是没有记录的
4.5 本机方法栈(Native Method Stack)
-
如果执行方法是native 方法,则方法就会在本地方法栈中执行;
-
如果线程需要计算所需要内存不够,也会报错
StackOverflowError
-
如果没有足够的内存来为新线程创建本地方法栈,则Java虚拟机机器抛出一个
OutOfMemoryError
4.6 堆,栈,方法区数据指向
4.6.1 栈指向堆
- 如果在栈中有一变量,类型为引用类型,比如:Object obj = new Object() ; 这时候就是栈中元素指向堆中对象
4.6.2 堆指向方法区
- 方法区中包含有类的信息,堆中会有对象
4.6.3 方法区指向堆
- 方法区中会保存静态变量,常量等数据。如果是下面这种情况,就是方法区指向堆;
private static Object obj = new Object();