1.jvm位置分布
2.JVM的体系结构
3.类加载器
步骤:
加载-->连接(验证,准备,解析)-->初始化
作用:加载Class文件~
*类是抽象的,对象是具体的
Class<test> testClass = test.class;
//类是抽象的是模板
Class<? extends Class> aClass = testClass.getClass();
Class<? extends Class> bClass = testClass.getClass();
Class<? extends Class> cClass = testClass.getClass();
System.out.println(aClass);
System.out.println(bClass);
System.out.println(cClass);
//对象的具体的
test aObject = new test();
test bObject = new test();
System.out.println(aObject.hashCode());
System.out.println(bObject.hashCode());
输出:
class java.lang.Class
class java.lang.Class
135721597
142257191
(1)虚拟机加载器
(2)启动类(根)加载器 BootStrap /jre/lib/rt.jar
//先创建对象
test test = new test();
//获取对象的类
Class<? extends com.hyq.sxsb.controller.test> aClass = test.getClass();
//获取该类通过那个加载器加载
ClassLoader classLoader = aClass.getClassLoader();
System.out.println(classLoader);
//再次获取该加载器的父类
ClassLoader classLoaderParent = classLoader.getParent();
System.out.println(classLoaderParent);
//再次获取该加载器的祖父类
ClassLoader classLoaderParentParent = classLoaderParent.getParent();
System.out.println(classLoaderParentParent);
输出:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@816f27d
null 这里的启动类加载器java程序获取不到,是使用C编写的
(3)扩展类加载器ExtClassLoader /jre/lib/ext
//先创建对象
test test = new test();
//获取对象的类
Class<? extends com.hyq.sxsb.controller.test> aClass = test.getClass();
//获取该类通过那个加载器加载
ClassLoader classLoader = aClass.getClassLoader();
System.out.println(classLoader);
//再次获取该加载器的父类
ClassLoader parent = classLoader.getParent();
System.out.println(parent);
输出:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@816f27d
(4)应用程序加载器AppClassLoader
//先创建对象
test test = new test();
//获取对象的类
Class<? extends com.hyq.sxsb.controller.test> aClass = test.getClass();
//获取该类通过那个加载器加载
ClassLoader classLoader = aClass.getClassLoader();
System.out.println(classLoader);
输出:sun.misc.Launcher$AppClassLoader@18b4aac2
4.双亲委派机制
先向上委派:
AppClassLoader-->ExtClassLoader-->BootStrap最终执行
当BootStrap加载器中不能加载这个类时。
然后从BootStrap向下查找;
BootStrap-->ExtClassLoader(也不能)-->AppClassLoader
当AppClassLoader中也不能时就会报ClassNotFoundException异常
5.沙箱安全机制
Java安全模式的核心就是沙箱(sandbox),Java中将执行程序分为本地代码和远程代码两种。本地代码默认视为可信任的,而远程代码则是不被信任的。
最新的安全机制实现,加入了域(Domain)的概念。JDK1.6
(1)组成沙箱的基本组件
①字节码校验器:确保java类文件遵循java语言规范,帮助java程序实现内存保护,但不是所有的类文件都会经过字节码校验,比如核心类。
Class test{
public static void main(String[] args){
System.out.println() 当这里缺少”;”号时就会编译错误。
}
}
②类加载器
其中类加载器在3个方面对java起作用。
防止恶意代码区干涉善意的代码; (双亲委派机制)
守护被信任的类库边界
将代码归入保护域,确定了代码可以进行哪些操作
6.Native方法区(本地方法区)
大致看一下Thread中的Start()方法。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
//首先会判断一下该线程是不是已经存在的线程
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
//不是的话添加到线程组中去
group.add(this);
//刚开始线程的的状态为false
boolean started = false;
try {
//当执行start0()方法后才会将线程的状态置为true,也就是创建线程
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 */
}
}
}
然后再来解析一下start0()方法
private native void start0();
首先需要注意的是Thread是一个类,因此直接写方法体是会报错的。
eg:private void lala();会直接报错
但是在Thread类中是使用native关键字修饰的方法体就不会报错吗?
事实上只要是使用native修饰的方法,说明java的作用范围达不到,因此就会去调用底层C语言的库。也就是下图中的本地方法接口(JNI=>java native interface),会进入本地方法栈。
所以说在使用Native修饰的方法时,会在JVM的本地方法栈中开辟一块区域来登记native方法,再通过JNI(本地方法接口)来加载本地方法库。也就可以访问java中没有的东西,例如C,C++等。也就体现出了java的跨平台性。
7.PC寄存器
每个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),然后在执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
8.方法区
方法区是被所有线程共享的,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域是属于共享区间。
静态变量,常量,类信息(构造方法,接口定义),运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。
以上,只是逻辑上的定义。在HotSpot中,方法区仅仅只是逻辑上的独立,实际上还是包含在Java堆中,也是就说,方式区在物理上属于Java堆区中的一部分,而永久区(Permanent Generation)就是方法区的实现。
9.栈:数据结构
先进后出,后进先出(弹夹)
生命周期和线程同步。一旦线程结束,栈就Over,因此不存在垃圾回收问题。
程序 = 数据结构+算法
程序 = 框架+业务逻辑~ ......渣渣
StackOverflowError栈溢出
栈中存储8大基本类型,对象引用地址,实例的方法引用
运行原理:栈中的数据都是以栈帧的格式存在的。
栈 + 堆 + 方法区的调用关系:
10.堆和HotSpot
(1)三种JVM:
Sun公司的HotSpot:Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
BEA JRockit:最快的JVM适用于财务前端,军工等
IBM J9:
(2)堆
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到栈中?
类,方法,常量,变量~(实例对象的信息),保存我们所有引用的真实对象:
堆中还要细分为三个区域:
GC垃圾回收主要是在伊甸园区和养老区。假设内存满了,出现OOM(堆溢出),堆内存不够!
OutOfMemoryError:java heap space
JDK1.8之后,永久存储区改为(元空间)
①新生区(伊甸园区 Eden Space)
New的对象都是在伊甸园区。
②养老区
③永久区 Perm
这个区域常驻内存,用来存放JDK自身携带的Class对象,Interface元数据,存储的是java运行时的一些环境或类信息,这个区域不存在垃圾回收!关闭JVM虚拟机就会释放这个区域的内存~
一个启动类,加载了大量的第三方jar包,Tomcat部署了太多的应用,大量动态生成的反射类,不断的被加载,直到内存满,就会出现OOM
(1)JDK1.7之前:
运行时常量池(包含 字符串常量池 )存放在 方法区,此时 hotspot 虚拟机对方法区的实现为永久代。(2)JDK1.7:
字符串常量池 被从方法区拿到了堆中;
运行时常量池 剩下的东西还在 方法区, 也就是hotspot中的永久代。(3)JDK1.8:
hotspot移除了 永久代,用 元空间(Metaspace) 取而代之。这时候,
字符串常量池还在堆,
运行时常量池 还在方法区, 只不过方法区的实现从永久代变成元空间(Metaspace)。
默认情况下:分配的总内存是电脑内存的1/4,初始化内存是1/64。
调整jvm分配的内存和初始化内存
在Configurations中的VM options:-Xms1024m -Xmm1024m -XX:+PrintGCDetails。
进行配置-XX:+PrintGCDetails打印信息。