首先看一下这张图
运行时数据区--简单介绍:
本地方法栈:登记native方法,在执行引擎执行时加载本地方法库
程序计数器:就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
方法区(元数据区):类的所有字段和方法字节码,以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,静态变量+常量+类信息(构造方法/接口定义)+运行时常量池都存在方法区中,虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。(元数据区取代了永久代(jdk1.8以前),本质和永久代类似,都是对JVM规范中方法区的实现,区别在于元数据区并不在虚拟机中,而是使用本地物理内存,永久代在虚拟机中,永久代逻辑结构上属于堆,但是物理上不属于堆,堆大小=新生代+老年代。元数据区也有可能发生OutOfMemory异常。)
Jdk1.6及之前: 有永久代, 常量池在方法区
Jdk1.7: 有永久代,但已经逐步“去永久代”,常量池在堆
Jdk1.8及之后: 无永久代,常量池在元空间
Java栈: Java线程执行方法的内存模型,一个线程对应一个栈,每个方法在执行的同时都会创建一个栈帧(用于存储局部变量表,操作数栈,动态链接,方法出口等信息)不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致。
Java堆:虚拟机启动时创建,用于存放对象实例,几乎所有的对象(包含常量池)都在堆上分配内存,当对象无法再该空间申请到内存时将抛出OutOfMemoryError异常。同时也是垃圾收集器管理的主要收集对象
总结:
java栈,本地方法栈,程序计数器这个三个为每个在jvm中的线程独占资源。 java堆和方法区为所有线程共享的资源
有关于JVM的内存模型其实如果我们能手绘出来就基本能记住了。
类加载子系统:类加载器将class文件加载到虚拟机的内存;
步骤有哪些?
1:加载:在硬盘上查找并通过IO读入字节码文件
2:校验:校验字节码文件的正确性
3:准备:给类的静态变量分配内存,并赋予默认值
4:解析:类装载器装入类所引用的其他所有类
5:初始化:对类的静态变量初始化为指定的值,执行静态代码块
2:类加载器种类: 1:启动类加载器:负责加载JRE的核心类库,如jre目标下的rt.jar,charsets.jar等
2:扩展类加载器:负责加载JRE扩展目录ext中JAR类包
3:系统类加载器:负责加载ClassPath路径下的类包
类加载机制:
1:双亲委派机制:指先委托父类加载器寻找目标类,在找不到的情况下在自己的路径中查 找并载入目标类
:当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException
双亲委派模式优势:
1:沙箱安全机制:自己写的String.class类不会被加载,这样便可以防止核心API库被随意篡改
2:避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再 加载一次