【JavaSE】之JVM入门(上)
【JavaSE】JVM入门
- 前言
- 一、JVM概述
- 1.JVM位置
- 2.JVM体系结构
- 二、类加载器
- 1.类加载的过程
- 2.类加载器的分类
- 3.双亲委派机制
- 5.沙箱安全机制
- 三、native关键字与方法区
- 1.native
- 2.PC寄存器
- 3.方法区
- 四、栈
- 1.栈的作用
- 2.栈存储的内容
- 3.栈运行原理
- 4.栈+堆+方法区的交互关系
- 后记
前言
本文为JVM入门基础知识,Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
一、JVM概述
1.JVM位置
- JVM是运行在操作系统之上的,它与硬件没有直接的交互
- JVM可以调用底层的硬件,用JIN (Java本地接口调用底层硬件接口,了解下就好,已经过时了)
2.JVM体系结构
jvm结构图:
jvm垃圾回收:
- 垃圾回收,指的的堆内存的垃圾回收
jvm调优:
二、类加载器
1.类加载的过程
当程序主动使用某个类时,如果这个类还未被加载到内存中,则系统会通过三个步骤对类进行初始化:
- 1、类的加载:将类的Class文件加载到内存中,并为其生成Java.lang.class对象(此过程由类加载器完成)
- 2、类的链接:将Java类的二进制数据合并到jvm运行状态中(jre)
- 2.1、验证:确保类符合JVM规范,保证安全性
- 2.2、准备:为类变量分配内存和初始化值
- 2.3、解析:JVM常量池中的符号引用(常量名)直接替换成直接引用(地址)
- 3、类的初始化:JVM对类进行初始化。执行类构造器方法(这个方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器))当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。JVM会保证一个类的()方法在多线程环境中被正确加锁和同步。
**作用:**加载Class文件——如果new Student();(具体实例在堆里,引用变量名放栈里) 。
获取类加载器代码示例:
package com.wang..JVM.Demo01;
public class Test01 {
public static void main(String[] args) {
Test01 test01 = new Test01();
Test01 test02 = new Test01();
Test01 test03 = new Test01();
System.out.println(test01.hashCode());
System.out.println(test02.hashCode());
System.out.println(test03.hashCode());
/*
1836019240
325040804
1173230247
*/
Class<? extends Test01> aClass1 = test01.getClass();
ClassLoader classLoader = aClass1.getClassLoader();
System.out.println(classLoader);
System.out.println(classLoader.getParent());
System.out.println(classLoader.getParent().getParent());
/*
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@330bedb4
null
*/
Class<? extends Test01> aClass2 = test02.getClass();
Class<? extends Test01> aClass3 = test03.getClass();
System.out.println(aClass1.hashCode());
System.out.println(aClass2.hashCode());
System.out.println(aClass3.hashCode());
/*
2133927002
2133927002
2133927002
*/
}
}
2.类加载器的分类
- Bootstrap ClassLoader:启动类加载器
- Extention ClassLoader:标准扩展类加载器
- Application ClassLoader:应用类加载器
- User ClassLoader:用户自定义类加载器
3.双亲委派机制
- 类加载器接收到一个加载请求时,他会委派给他的父加载器,实际上是去他父加载器的缓存中去查找是否有该类,如果有就加载返回,如果没有则继续委派给父类加载,直到顶层类加载器。
- 如果顶层类加载器也没有加载该类,则会依次向下查找子加载器的加载路径,如果有就加载返回,如果都没有,则会抛出异常。
代码示例:
package java.lang;
public class String {
/*
双亲委派机制:安全
1.APP-->EXC-->BOOT(最终执行)
BOOT
EXC
APP
*/
public String toString() {
return "Hello";
}
public static void main(String[] args) {
String s = new String();
System.out.println(s.getClass());
s.toString();
}
/*
1.类加载器收到类加载的请求
2.将这个请求向上委托给父类加载器去完成,一直向上委托,知道启动类加载
3.启动加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,适知子加载器进行加载
4.重复步骤3
*/
}
运行结果:
结果分析:在运行一个类之前,首先会在应用程序加载器(APP)中找,如果APP中有这个类,继续向上在扩展类加载器EXC中找,然后再向上,在启动类( 根 )加载器BOOT中找。如果在BOOT中有这个类的话,最终执行的就是根加载器中的。如果BOOT中没有的话,就会倒往回找有这个类的加载器。
双亲委派机制过程总结:
- 1.类加载器收到类加载的请求
- 2.将这个请求向上委托给父类加载器去完成,一直向上委托,直到启动类加载器
- 3.启动类加载器检查是否能够加载当前这个类,能加载就结束,使用当前的加载器,否则,抛出异常,一层一层向下,通知子加载器进行加载
- 4.重复步骤3
双亲委派机制作用:
- 防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
- 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
- 比如:如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
5.沙箱安全机制
- Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
- 所有的Java程序运行都可以指定沙箱,可以定制安全策略。
- 在]ava中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的ava实现中,安全依赖于沙箱(Sandbox)机制。如下图所示JDK1.0安全模型。
- 但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的Java1.1版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示JDK1.1安全模型。
- 在Java1.2版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示JDK1.2安全模型。
- 当前最新的安全机制实现,则引入了域(Domain)的概念。虚拟机会把所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。存在于不同域中的类文件就具有了当前域的全部权限,如下图所示最新的安全模型(jdk1.6)。
组成沙箱的基本组件: - 字节码校验器(bytecode
verifier)︰确保Java类文件遵循lava语言规范。这样可以帮助lava程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。 - 类装载器(class loader)
- 存取控制器(access controller)︰存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
- 安全管理器(security manager)︰是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
- 安全软件包(security package) :
java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:安全提供者,消息摘要,数字签名,加密,鉴别
其中类装载器在3个方面对Java沙箱起作用:
- 它防止恶意代码去干涉善意的代码;
- 它守护了被信任的类库边界;
- 它将代码归入保护域,确定了代码可以进行哪些操作。
虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。
类装载器采用的机制是双亲委派模式:1.从最内层VM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;2.由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
三、native关键字与方法区
1.native
凡是使用了native关键字的,说明Java的作用范围已经达不到了,它会去调用底层的C语言的库:
- 进入本地方法栈
- 调用本地方法接口——JNI
JNI的作用:扩展Java的使用,融合不同的语言为Java所用。(最初是为了融合C、C++语言)因为Java诞生的时候,C和C++非常火,想要立足,就有必要调用C、C++的程序。所以Java在JVM内存区域专门开辟了一块标记区域Native Method Area Stack,用来登记native方法。在最终执行(执行引擎执行)的时候,通过JNI来加载本地方法库中的方法。
目前该方法使用的越来越少了,除非是与硬件有关的应用,比如通过Java程序驱动打印机或者Java系统管理生产设备,在企业级应用中已经比较少见。因为现在的异构领域间通信很发达,比如可以使用Socket通信,也可以使用Web Service等等,不多做介绍!
代码示例:
// 编写一个多线程启动方法。
public class Test {
public static void main(String[] args) {
new Thread(()->{
},"MyThread").start();
}
}
// 查看start方法的实现
// Thread类中的start方法,底层是把线程加入到线程组,然后去调用本地方法start0
public class Thread implements Runnable {
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
}
2.PC寄存器
- 程序计数器:Program Counter Register
- 每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向像一条指令的地址,也即将要执行的指令代码),在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
3.方法区
- Method Area方法区(此区域属于共享区间,所有定义的方法的信息都保存在该区域)
- 方法区是被所有线程共享,所有字段、方法字节码、以及一些特殊方法(如构造函数,接口代码)也在此定义简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
- 静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关
四、栈
1.栈的作用
- 栈内存,主管程序的运行,存储一些基本类型的值、对象的引用、方法等,生命周期和线程同步;
- 线程结束,栈内存也就释放了,对于栈来说,不存在垃圾回收问题。
2.栈存储的内容
- 8大基本类型
- 对象引用
- 实例的方法
3.栈运行原理
栈的简单结构图:
栈的详细结构图:
Java栈的组成元素——栈帧
栈帧是一种用于帮助虚拟机执行方法调用与方法执行的数据结构。他是独立于线程的,一个线程有自己的一个栈帧。封装了方法的局部变量表、动态链接信息、方法的返回地址以及操作数栈等信息。
4.栈+堆+方法区的交互关系
- Person:存放在元空间,也可以说方法区
- person:存放在Java栈的局部变量表中
- new Person():存放在Java堆中
后记
本文下接:XXXX
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~