JVM内存区域介绍
- 前言
- 方法区
- 虚拟机栈
- 程序计数器
- 本地方法栈
- 堆区
前言
- 首先说JVM内存区域划分为下面的5个部分,5个区域的划分只是Java虚拟机规范里概念性的说法,具体的实现在不同版本的JDK中会有区别
- 我们大多数使用的高版本的JDK虚拟机都是HotSpot(当然不排除 一些大厂开发定制适合自己业务的JVM),它的特点是热点代码探测技术,能够把频繁调用,对程序运行效率影响大的代码动态编译成本地代码运行
-
参数参考
-Xms设置堆的最小空间大小。
-Xmx设置堆的最大空间大小。
-XX:NewSize设置新生代最小空间大小。
-XX:MaxNewSize设置新生代最大空间大小。
-XX:PermSize设置永久代最小空间大小。
-XX:MaxPermSize设置永久代最大空间大小。
-Xss设置每个线程的堆栈大小。
方法区
- 方法区是java虚拟机规范中的概念描述,具体实现可以是JDK1.7中永久代也可以是JDK1.8中的元空间,JDK1.8中用元空间取代了JDK7堆空间里的永久代
-
方法区中存放类的元信息和运行时常量池,运行时常量池是class文件常量池表在运行时的表现形式,
通过类加载器将字节码文件的常量池信息加载到内存中,存储在方法区的运行时常量池中。class文件的常量池表(Constant Pool Table)存储了类在编译期间生成的字面量、符号引用,字符串,基本类型,finanl类型,类和方法的全限定名,字段和方法的名称和描述符等
-
我们编译生成的.class文件被类加载器加载到内存并解析成Class对象后存放在元空间中
关于类加载器:我们给类加载器指定一个类全限定名和一个二进制字节流就可以完成类的加载,生成我们业务代码中正常使用的Class对象,而这个二进制的字节流可以通过很多渠道获取如jar包 、jsp文件、网络获取的Applet程序、动态生成的典型应用动态代理
-
类的加载
类加载器的类型:
1) BootStrap ClassLoader:启动类加载器,加载核心库(会加载常用的String int等核心类),由C实现,所有类加载器的最终父类,加载路径是JAVA_HOME/jre/lib目录下的类库,JVM启动时就会加载,我们无法直接使用该加载器
2) Extension ClassLoader: 扩展类加载器,加载路径是JAVA_HOME/jre/lib/ext,它的父加载器是BootStrap但无法通过getParent()方法获取到,该方法返回null
3) Application ClassLoader: 应用程序加载器(也叫系统加载器)加载CLASSPATH指定的类库,它由BootStrap加载器加载但调用getParent()方法获取的父加载器是Extension,也就是说我们日常业务代码中使用的业务类就是由这个加载器加载的,可以通过ClassLoader.getSystemClassLoader获取
双亲委派:当加载器加载一个类时都是先委托给父加载器加载,同样父加载器也会委托给它的父加载器加载,当父类加载器加载不了时再自己加载,同一个类不同加载器加载出来的Class对象用instanceof关键字 isinstance方法比较是不同的。我们自定义一个类加载器继承ClassLoader类只重写findClass方法可以保持双亲委派,如果要打破双亲委派可以重写loadClass方法,源码中loadClass方法会向上调父加载器的loadClass方法,如果父加载器加载失败再调自己的findClass方法查找类
双亲委派的作用:这种机制使某个类只能由唯一的某个加载器加载,避免类的重复加载,避免java的核心API被篡改
类的加载过程:加载——将class文件加载到方法区中生成Class对象;连接——文件格式验证,元数据验证,字节码验证,符号引用验证:验证模数、版本号等,类继承关系,验证方法重写等合法性的语义,验证该类符号引用的类能否被找到,被引用的类,方法,属性能否被该类正确访问,准备:静态变量分配内存赋默认值,解析:将类中符号引用解析为直接引用;初始化——给静态变量赋初始值,静态代码块的执行;卸载
ClassLoader.loadClass和class.forName加载class文件的区别 : Class.forName(类全限定名)默认需要初始化,会触发静态代码块执行,静态变量被初始化。ClassLoader.loadClass 不进行初始化,静态代码块和静态对象不会执行,只有在后面的newInstance才会去执行静态代码块
虚拟机栈
- 虚拟机栈也就是我们常说的堆区栈区方法区中的栈区,该区域线程隔离
- 它的主要作用就是完成方法执行的一个生命周期,每一个方法调用时都会生成一个栈桢入栈,方法执行结束后出栈
-
栈桢包含4个部分:局部变量表 操作数栈 动态链接 方法返回地址
局部变量表:方法执行时的局部变量存储,其中this指针就存放在这
操作数栈:协调变量操作,如加减赋值等的操作就是变量进栈出栈的过程
动态链接:某些类的方法如静态方法在类加载阶段就能够从符号引用(Class文件中使用符号来表示要引入哪个类或方法或变量,并不是内存地址,直接引用就是运行时的地址)转换为直接引用叫静态链接,而某些方法如重写的方法需要在运行时才能确定叫动态链接,这一部分保存的就是自己这个方法在运行时常量池中的直接引用地址 。(当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值,而经过解析之后,符号引用替换为直接引用)
方法返回地址:方法执行过程中又去调用了其他方法的话就会将局部变量表和操作数栈的执行指针指向新方法的栈桢,执行结束后又恢复到上一个栈桢的局部变量表和操作数栈,并且恢复程序计数器的指令指向上一个方法,返回值压入上一个方法的操作数栈,清空已经完成的方法的栈桢
- 我们常见的的栈溢出异常更容易理解了,当方法调用链过长或者无限递归时会使大量栈桢压栈造成栈内存溢出,局部变量过多使局部变量表过大也会造成栈溢出
程序计数器
- 仿照操作系统中的指令寄存器来实现的一个区域,就是执行当前指令。调用native方法时程序计数器的值为null,native方法已经不是Java字节码的方法
- 该区域没有OutOfMemery
- 这一区域是线程隔离的
本地方法栈
功能上与虚拟机栈类似,执行的是native方法,这一区域是线程隔离的
堆区
- 没有使用参数配置的默认情况下,最小是物理内存的64分之一,最大是物理内存的4分之一
- 该区域存储实例对象,虚拟机栈中只存有指向实例对象的引用
-
新生代:老年代 默认大小1:2
新生代中Eden:S0(from,survivor区) : S1(to,survivor区) 默认大小8:1:1
- 新建对象存放在Eden区,Young GC时存活的对象放到S0,下一次Young GC时从Eden和S0查找存活对象放到S1,下一次Young GC时从Eden和S1查找存活对象放到S0。经历15次Young GC不死的对象或者eden区放不下的对象会放到老年代