JVM原理
1、JVM的体系结构
jvm:java虚拟机 java语言之所以可以实现跨平台(不同的操作系统) 就是因为有jvm虚拟机
虚拟机 :基于操作系统上的虚拟的主机 java之所以可以跨平台 也是因为每个操作系统都可以安装虚拟机
2、双亲委派机制
在介绍双亲委派机制的时候,不得不提ClassLoader(类加载器)。说ClassLoader之前,我们得先了解下Java的基本知识。
Java是运行在Java的虚拟机(JVM)中的,但是它是如何运行在JVM中了呢?我们在IDE中编写的Java源代码被编译器编译成**.class**的字节码文件。然后由我们得ClassLoader负责将这些class文件给加载到JVM中去执行。
JVM中提供了三层的ClassLoader:
- Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
- ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
- AppClassLoader:主要负责加载应用程序的主函数类
那如果有一个我们写的Hello.java编译成的Hello.class文件,它是如何被加载到JVM中的呢?别着急,请继续往下看。
public class Car {
public static void main(String[] args) {
Car c1 = new Car();
Car c2 = new Car();
Car c3 = new Car();
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c1.getClass()); //class JVM原理.双亲委派机制.Car
System.out.println(c2.getClass()); //class JVM原理.双亲委派机制.Car
System.out.println(c3.getClass()); //class JVM原理.双亲委派机制.Car
System.out.println(c1.getClass().getClassLoader());
System.out.println(c1.getClass().getClassLoader().getParent()); //sun.misc.Launcher$AppClassLoader@18b4aac2
System.out.println(c1.getClass().getClassLoader().getParent().getParent()); //sun.misc.Launcher$ExtClassLoader@677327b6
}
}
- JVM通过类加载器Class Loader加载出一个Car 模板 后面new出来的car1 2 3 都是按照这个模板实例出来的
代码中通过getClass输出的结果可以看出
- getClassLoader得到的是类的类加载器 上面说到的三层
- Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
- ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
- AppClassLoader:主要负责加载应用程序的主函数类
最开始是AppClassLoader -->ExtClassLoader–>Bootstrap classLoader一步一步找 到boot根层开始 如果有就用根层的 不然就ext 再到 app
3、native
- native :凡是带了native关键字的,说明java的作用范围达不到了,回去调用底层c语言的库!
- 会进入本地方法栈
- 调用本地方法本地接口 JNI (Java Native Interface)
- JNI作用:开拓Java的使用,融合不同的编程语言为Java所用!最初: C、C++
- Java诞生的时候C、C++横行,想要立足,必须要有调用C、C++的程序
- 它在内存区域中专门开辟了一块标记区域: Native Method Stack,登记native方法
- 在最终执行的时候,加载本地方法库中的方法通过JNI
- 例如:Java程序驱动打印机,管理系统,掌握即可,在企业级应用比较少
- 调用其他接口:Socket. . WebService~. .http~
Native Method Stack
它的具体做法是Native Method Stack中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。[本地库]
Native Interface本地接口
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序, Java在诞生的时候是C/C++横行的时候,想要立足,必须有调用C、C++的程序,于是就在内存中专门开辟了块区域处理标记为native的代码,它的具体做法是在Native Method Stack 中登记native方法,在( Execution Engine )执行引擎执行的时候加载Native Libraies。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍!
4、方法区
方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间;
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
public class Test {
private int a;
private String name = "gyy";
public String getName() {
return name;
}
public static void main(String[] args) {
Test test1 = new Test();
test1.a = 1;
String getname = test1.getName();
test1.name = "usount";
System.out.println(test1.a);
System.out.println(getname);
System.out.println(test1.name);
}
}
5、栈
- 栈:先进后出 -->手枪弹夹
- 栈:栈内存,主管程序的运行,生命周期和线程同步;
线程结束,栈内存也就是释放,对于栈来说,不存在垃圾回收问题
一旦线程结束,栈就Over! - 栈内存中:
8大基本类型+对象引用+实例的方法
栈帧
栈帧位置
JVM 执行 Java 程序时需要装载各种数据到内存中,不同的数据存放在不同的内存区中(逻辑上),这些数据内存区称作运行时数据区(Run-Time Data Areas)–> 栈 堆 pc寄存器 本地方法栈 方法区
其中 JVM Stack(Stack 或虚拟机栈、线程栈、栈)中存放的就是 Stack Frame(Frame 或栈帧、方法栈)。
对应关系
一个线程对应一个 JVM Stack。JVM Stack 中包含一组 Stack Frame。线程每调用一个方法就对应着 JVM Stack 中 Stack Frame 的入栈,方法执行完毕或者异常终止对应着出栈(销毁)。
当 JVM 调用一个 Java 方法时,它从对应类的类型信息中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧内存,然后压入 JVM 栈中。
在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。
6、堆
Heap, 一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到堆中?
类, 方法,常量,变量~,保存我们所有引用类型的真实对象;
堆内存中还要细分为三个区域:
●新生区(伊甸园区) Young/New
●养老区old
●永久区Perm
- 轻GC首先从新生区开始回收垃圾
- 幸存下来的去到幸存区
- 重型GC幸存区后活下来的又到养老区
- 回收养老区的垃圾
- 永久区不GC
- 永久区存放的是java自带的类 比如java.lang 不可被清除
新生区
- 类诞生成长甚至死亡的地方
- 所有的对象都是在新生区new出来的
- 根据调查 99%的类都在对象都在新生区被干掉
永久区
- 这个区域常驻内存的。用来存放jdk自身携带的Class对象 接口元数据 储存一下java运行时的环境或类信息 ,不存在垃圾回收 关闭虚拟机就会释放内存
- 逻辑上存在,物理上不存在 (因为存储在本地磁盘内) 所以最后并不算在JVM虚拟机内存中
堆内存调优
运行结果: 首先轻GC清理垃圾 清理不急后堆满了新生区 进入幸存区通过重GC(full GC)清理
直到最后 所有区域都被堆满 报oom错误
7、GC
JVM在进行GC时,并不是对这三个区域统一回收。 大部分时候,回收都是新生代~
●新生代
●幸存区(form,to)
●老年区
GC两种类:轻GC (普通的GC), 重GC (全局GC)
算法
引用计数器:
现在用的很少
复制算法:
- 好处:没有内存的碎片~
- 坏处:浪费了内存空间~ :多了一半空间永远是空to。假设对象100%存活(极端情况)
- 复制算法最佳使用场景:对象存活度较低的时候;
标记压缩清除算法:
没有最好的GC算法 只能做到分代
- 新生区:存活量低: 复制算法
- 老年区:存活量高: 标记压缩清除算法