class文件
一个Java文件通过编译工具javac编译成class字节码文件,通过JVM进行加载运行。因为JVM屏蔽底层操作系统的差异,所以一次编译到处运行。
JVM内存结构
JVM分为外部和内部,内部就是数据区,其他部分就是外部
- 线程私有数据区随着线程结束而结束,没有安全问题;
- 线程公有数据区存在安全问题,内存优化和溢出都需要关注这个地方。
类加载子系统
-
加载:通过类的完全限定名来找到类文件所在位置,根据字节码创建java.lang.class对象
1.通过一个类的完全限定名获取定义这个类的二进制字节流
2.将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
3.在内存中生成一个java.lang.class对象,作为方法区这个类的访问入口 -
验证:确保加载的字节码符合虚拟机要求,是一种自我保护机制,不让危害虚拟机安全。包括四种验证:字节码验证、文件格式验证、元数据验证、符号引用验证。
1.文件格式验证:验证字节流是否符合class文件格式的规范
2.元数据验证:对字节码描述的信息进行语义分析,以确保符合java语言的规范要求
3.字节码验证:通过数据流和控制流分析,确定程序语义是合法和符合逻辑的
4.符号引用验证:确保解析动作能正确执行 -
准备:为类变量分配地址和初始化值,分配到方法区。初始值是数值类型默认的初始值。而实例变量在对象实例化时随着对象一起分配到Java堆中。
-
解析:把符号引用转换为直接引用。
-
初始化:为变量赋予正确值,java虚拟机规定有五种情况必须初始化
-
1.使用new创建类的实例,使用getstatic、putstatic读取或设置一个静态字段的值,调用一个静态方法
-
2.通过java.lang.reflect包的方法对类进行反射调用
-
3.初始化一个类,父类没有初始化,先初始化父类
-
4.虚拟机启动时,首先初始化一个主类,主类就是包含main()方法的类
-
5.使用jdk1.7的动态语言支持时,java.lang.invoke.MethodHandle实例的解析结构是REF_getStatic、REF_putStatic、RE_invokeStatic的方法句柄,方法句柄对应的类没有初始化
双亲委派方式
自底向上检查是否加载成功,自顶向下尝试加载。
收到类加载请求后,先加载父类,父类加载完才加载子类。如果父类以上还存在父类,那么继续加载上面的父类,直到最高的父类加载完才往下加载。
类加载的三种方式
以下示范代码中,A是类,a1、a2、a3是A实例化的对象
A a1=new A();
静态加载,通过new关键字实例化对象
Class clazz=Class.forName("A");
Object a2=a.newInstance();
动态加载,通过forName来加载类,调用类的newInstance方法实例化对象
Class clazz=classLoader.loadClaass("A");
Object a3=clazz.newInstance();
动态加载,继承ClassLoader实现自定义类加载器
程序计数器
cpu进行切换的时候,指向当前需要获取指令的位置,线程私有,记录程序执行位置,不会发生内存溢出,只占一小块区域。
虚拟机栈
作用是执行方法。栈的顺序,先进后出。先执行的方法在下面,按顺序放入,执行完毕从上往下退出。执行方法叫做压栈,结束方法叫做出栈(销毁栈帧)。
栈帧是虚拟机栈的单位,一个方法一个栈帧,一个栈帧分为四部分:
- 局部变量表:存放方法参数或内部定义的变量列表,比如对象;
- 操作数栈:在执行方法的时候使用;
- 动态链接:和上面的解析一样,将符号引用转换为直接引用
- 返回地址:不论方法成功失败,返回方法被调用的地址
栈溢出
当栈的深度大于虚拟机,报StackOverflowError,-Xss可设置大小
内存溢出
当栈需要扩展而无法申请空间,报OutOfMemoryError
本地方法栈
和虚拟机栈类似。虚拟机栈为JVM执行字节码服务,本地方法栈为本地(native)方法服务。
- Sun HotSpot将虚拟机栈和本地方法栈合并;
- 有StackOverflowError和OutOfMemoryError;
方法区(元空间)
加载类的时候,将类的一些元数据信息保存在这里,比如变量,方法。方法区是线程共享的。
- jdk1.7合并方法区到堆里
- jdk1.8保留方法区,称之为元空间
- HotSpot虚拟机中称之为永久代
- 存在OutOfMemoryError,可以通过-XX:MaxPermSize设置大小
堆
用于存放所有实例化对象和数组,是JVM分配内存最大的区域,在虚拟机启动时创建,用垃圾回收器管理,分为新生代(1/3)和老年代(2/3)。
新生代还可以分为Eden、From Survivor、To Survivor空间,比例为8:1:1,通过-Xmx,-Xms设置大小
新生代
新生代大部分对象不需要太长的存活时间,会频繁触发Minor GC进行回收
老年代
老年代存放的是存活时间较久或内存较大的对象,不会频繁执行Full GC
Minor GC
新生代的垃圾回收机制,采用复制算法(扫描存活对象,复制到新内存区域)。
发生时,Eden区和一个Survivor区把存活的对象放到另一个Survivor区,然后清理自己。当达到一定年龄以后,把对象放入老年代。每发生一次Minor GC增加一岁,默认15岁。
Full GC
老年代的垃圾回收机制,采用标记清除(标记存活对象,清除未标记对象)。
发生的频率比Minor GC低,但一次的时间会更长。