本文仅为个人一个学习笔记
类加载器
1.相关概念
1.1 类从编译到执行的过程
1.编译器将.java源文件转为.class字节码文件
2.classLoader将字节码(byte数组)转换为jvm中的class对象
3.jvm利用class对象实例化对象。
1.2 ClassLoader
classLoader主要工作在class装载的加载阶段,主要作用是从系统外部获得的class二进制数据流。
它是java核心组件,所有的class都有classLoader进行加载,classLoader负责通过将class文件的二进制数据流装载进系统
然后交由jvm进行链接、初始化等操作。
classloader的种类
BootStrapClassLoader | c++编写,加载核心库java.* |
ExtClassLoader | java编写,加载扩展库javax.*(jre\lib\ext) |
AppClassLoader | java编写,加载程序所在目录 |
自定义ClassLoader | java编写,定制化加载 |
自定义类加载器的例子,桌面生成了Test.class即字节码文件供自定义classLoader加载
public class Test{
static {
System.out.println("hello");
}
}
package reflect;
import java.io.*;
/**
* @author lulu
* @Date 2019/7/12 19:26
*/
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path, String classLoaderName) {
this.path = path;
this.classLoaderName = classLoaderName;
}
//用于寻找类文件
@Override
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b,0, b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return out.toByteArray();
}
public static void main(String[] args) throws Exception {
MyClassLoader m=new MyClassLoader("C:\\Users\\lulu\\Desktop\\","myclassLoader");
Class c=m.loadClass("Test");
System.out.println(c.getClassLoader());
c.newInstance();
}
}
结果
1.3 双亲委派机制
这样的好处是可以避免字节码重复加载
1.4 类加载方式
隐式加载:new
显式加载:loadClass,forName
1.5 类装载过程
1.加载
通过classLoader通过loadClass加载class文件字节码到内存,并将静态数据转化成运行时数据区域中方法区的类型数据,在运行时,数据区堆中代表该类的java.lang.class对象作为方法区内数据的访问入口
2.链接(classLoader中的resolveClass方法)
2.1校验:检查加载class的正确性和安全性(比如校验class格式)
2.2准备:为类变量(static变量)分配存储空间并设置类变量初始值(类变量类型的默认值),类变量随类型信息存放在方法区中,生命周期很长,使用不当容易造成内存泄漏
2.3解析:jvm将常量池内的符号引用转为直接引用
3.初始化
执行类变量赋值和静态代码块
Class.forName得到的class是已经完成初始化的。
Classloader.loadClass得到的class是还没有链接的(classPath,做到延迟加载)。
2. jdk8内存模型
2.1 相关概念
OOM:Out Of Memory
SOF:StackOverFlow
字面量:实际值(如文本字符串、被声明为final的常量值、基本数据类型的值、其他)
符号引用量:用于表示非字面量的字符串,在类第一次加载时会解析成指针(如类和结构的完全限定名、字段名称和描述符、方法名称和描述符)
元数据:例如类结构信息,对应的运行时常量池、字段、方法代码等
2.2 模型图(比例是默认情况下)
这里的图有点错误,code cache(jit本地编译代码,jni的c代码)和ccs(32位指针的class,启用压缩指针后会存放)属于元空间(Class Package Method Field 字节码、常量池、符号引用等)
线程私有:程序计数器(字节码指令 no OOM)、虚拟机栈(java方法SOF&OOM)、本地方法栈(native方法SOF&OOM)
线程共享:方法区(元空间是其实现,类加载信息,OOM)、java 堆(数组和类对象 OOM)、堆里有一个常量池(字面量和符号引用量,OOM)
Code Cache JIT编译的热点代码
2.3 私有部分
2.3.1 程序计数器
jvm支持多线程同时执行,每个线程都有自己的计数器,线程在执行的方法叫做当前方法,如果是java代码,里面存放的就是当前正在执行的指令的地址,如果为c代码,则为空
当前线程所执行的字节码行号指示器
改变计数器的值来选取下一条需要执行的字节码指令
和线程关系是一对一的关系即“线程私有”
对java方法计数方法,如果是native方法计数器值为undefined
不会发生内存泄露
2.3.2 java虚拟机栈(stack)
java虚拟机栈是线程私有的,生命周期与线程相同。虚拟机栈描述的是java方法执行的内存模型:每个方法在执行时会创建栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用直到执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
1.java方法执行的内存模型
2.包含多个栈帧,每个栈帧包含(局部变量表、操作数栈、动态链接、方法出口等每个方法执行中对应虚拟机栈帧从入栈到出栈的过程),方法调用结束时,栈帧会被销毁
局部变量表和操作数栈
局部变量表:包含方法执行过程中的所有变量,this引用,所有方法参数,其他基本类型引用
操作数栈:入栈、出栈、复制、交换、产生消费变量
递归由于过深,栈帧数超出虚拟机深度而出现SOF
2.3.3 native方法栈
虚拟机栈相似,只不过里面执行的是native方法
2.4 共享部分
1.7以后位于方法区(永久代是其hotspot实现jvm定义的方法区,就像类和接口的关系)的运行时常量池被移动到java堆中
1.8以后,永久代被元空间替换,元空间使用本地内存,永久代使用jvm内存,好处:
1.运行时常量池存在永久代中,容易出现性能问题和内存溢出
2.类与方法信息大小难以确定,给永久代的大小指定带来困难,太小则容易导致永久代溢出,太大则容易导致老年代溢出
3.永久代会为gc带来不必要的复杂性,回收效率偏低,在永久代中元数据可能会随着每一次full gc发生而进行移动,hotspot虚拟机每种类型垃圾回收器都需要特殊处理永久代的数据,分离出来以后可以简化fullgc以及对以后的并发隔离数据等进行优化
4.方便hotspot与其他jvm集成
2.4.1 java堆
java堆是java虚拟机所管理的内存中最大的一块。堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。java堆可以处于物理不连续的内存空间中,只要逻辑上市连续的即可
对象实例的分配区域
GC管理的主要区域
调优参数-Xms -Xmx -Xss
-Xss:规定每个线程虚拟机栈(堆栈)的大小
-Xms:堆的初始值
-Xmx:堆能达到的最大值
一般来说-Xms、-Xmx设置为一样,当heap不够用,会扩容,引起内存抖动,影响程序稳定性
2.4.2方法区(1.8由元空间实现)
方法区域java堆一样,是各个线程共享区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器后的代码等数据,虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但它又别名称为非堆,目的与java堆区分开来
保存在着被加载过的每一个类的信息,这些信息是由类加载器在加载类的时,通过读取class文件从二进制数据流转换而来的,
包括类型信息,运行时常量池,字段信息,方法信息,类变量(static变量)等等
运行时常量池:
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
2.4.3 程序运行时内存分配策略
静态存储:编译时确定每个数据目标在运行时的存储空间需求,在编译时可以分配固定的内存空间,不适用于动态变化如递归
栈式存储:数据区需求在编译时未知,运行时模块入口前确定,在编译时未知,在运行时进入程序模块时必须知道所在模块的数据区大小才能分配其内存,先进后出分配
堆式存储:编译时或运行时模块入口都无法确定,动态分配
2.5 堆和栈
2.5.1 联系
引用对象、数组时,栈定义引用变量保存堆中的目标的首地址
2.5.2 区别
栈自动释放,堆需要GC
栈比堆小
栈产生的碎片远小于堆
栈支持静态和动态分配,堆仅支持动态分配
栈的效率比堆高,堆比较灵活
2.6监控和故障处理工具
名称 | 作用 |
jps | 显示指定系统内所有的hotspot虚拟机进程 |
jstat | 用于收集hotspot虚拟机各方面的运行数据 |
jinfo | 显示虚拟机配置信息 |
jmap | 生成虚拟机的内存转储快照(heapdump文件) |
jhat | 用于分析heapdump文件,可以让用户在浏览器查看分析结果 |
jstack | 显示虚拟机线程快照 |
2.7常用调优指令
-Xmx -Xms 调整最大/最小堆内存
-XX:NewSize=32M -XX:NewMaxSize=56M (新生代的大小为32M,新生代的最大大小为56M)
-XX:NewRatio=4 -XX:SurvivorRatio=6(即新生代:老年代=1:4,from+to:surivivor=2:6)\
-XX:MetaspaceSize -XX:MaxMetaspaceSize (元空间的初始大小,元空间最大大小),调大也会使得下面的空间变大
-XX:+UseCompressedClassPointers(启用压缩指针)
-XX:CompressedClassSpaceSize(ccs的空间大小)
-XX:InitialCodeCacheSize(codecache初始大小)
-XX:ReservedCodeCacheSize(codecache最大大小)