黄色部分是线程共享的 每个线程对应一个栈
我们的通过编译生成的.class文件,知道你的类名以后,通过classLoader首先把类加载到方法区里面,首先我們要知道,lei类加载它并不是实例化的过程,类加载时把类放到jvm的过程,而不是创建对象的过程,创建对象的过程涉及到栈区里面方法执行的过程,这是方法在执行过程用到那个类就去加载哪个类,
很多人不知道这个类是怎么被加载的即我圈的CC那个类,
当你运行java程序的时候,你在dos里运行时肯定要输入java com.changchang.cn.CC 肯定会指定这个类名的,类加载器首先就会把这个CC类加载进method area 里面,但是你要记得 在启动虚拟机的时候就把java核心类库(rt.jar)全部都加载进来了,
在执行main的时候,碰到没有被加载的类,就看下方法区有没有,如果没有就加载到方法区
类加载器的分类:Bootstrap类加载器,ExtClassLoader,AppClassLoader 每种加载器都设定好从哪里加载类
BootstrapClassLoader--------->JRE/lib/rt.jar
ExtClassLoader-------->JRE/lib/ext或者
AppClassLoader ------>CLASSPATH环境变量,由classpath或-cp选项定义
类加载器的工作原理有1.委托机制,加入你要加载Person.class这个类,首先加载这个类的请求由AppClassLoader 委托给它的父类ExtClassLoader,然后再委托给BootstrapClassLoader,BootstrapClassLoader先看看rt.jar里面有没有这个类,如果没有,就返回给ExtClassLoader 查看jre/lib/ext目录下有没有这个类,如果还没有就由AppClassLoader 从classpath中寻找, 记住
classpath定义的是类文件的加载目录,而PATH定义的是可执行程序如javac java等的执行路径
2.可见性机制,子类加载器可以看到父类加载器加载的类,而反之则不行,如果ABC.class已经被AppClassLoader 加载过了,再用
ExtClassLoader的话就会抛出java,lang.ClassNotFountException
3.单一性:父类加载器加载过的类不能被子类加载器加载第二次
由于方法区里面存放的是类类型(Class),运行时加载类是你用到哪个类先去method area区找如果没有,你就把他加载进来存进方法区,你想呀 我们从main方法进去的时候肯定会遇见没有加载过的类,jam发现这个类没有被加载,就开始查找某某类.class文件,从类文件中抽取类型信息并放在了方法区 ,这个类一旦被加载,它就常驻内存里面了,
方法区溢出是加载类太多导致的,堆溢出是加载对象太多导致的 栈溢出是方法太多导致的,如方法的死递归
类型信息:
对于每个加载的类型,jvm必须在方法区存储以下类型信息:
方法区是线程共享的,当两个线程同时要加载一个类型的时候,只有一个类会请求Classloader加载,另一个线程会等待。
1:这个类型的完整有效名
2.这个类型直接父类的完整有效名(除非这个类是interface或者是java.lang.Object,这两种情况都没有父类)
3.这个类型的修饰符(public,abstract,final)
4.即时编译后的代码等信息
除了这些基本信息外,jvm还要为每个类型保存一下信息:
类型的常量池:
field信息
方法信息
所有static变量 ,在这里提一点就是可以把全局变量放在静态代码块里面,
在这之前先讲一下Class这个类的概念
class Person{
}这里有个Person类,这个类的实例对象如何表示呢?下面这句话就是了
Person p = new Person(); p就是Person这个类的实例对象,
class Student{
}
Student student = new Student(); student就是Student这个类的实例对象,
由于java里面万事万物皆对象 这个Person,Student类 也是一个实例对象,它是大写Class类的实例对象 ,也就是说我们平常所见到的类都是Class的实例对象 那么改如何表示呢?有三种表示方法:我们就举上面的Person类吧
1:Class c1 = Person.class(即类名.class)
2: Class c2 = p.getClass(即对象.getClass) 这里的p是 大写Person类的实例对象
写到这里可能你们会有些混淆:我再重申一下 上面的 p是Person类的实例对象,c1 c2代表 Class类的实例对象,
但是这个c1实例对象说的是什么玩意呢,说的是Person这个类本身就是一个实例对象
官网给出的准确说法是: c1 c2是 Person类的类类型(class type) ,类的类类型怎么理解呢,你就这样理解,Person类本身就是Class类的一个实例对象 ,你要清楚一点Person这个类本身也有实例对象,就是p, 我们以后就可以这么叫了 p是Person类的实例对象,c1是Person类的类类型
3:c3=Class.forName("类的路径"); 这里c1==c2==c3的
我们完全可以通过类的类类型创建该类的实例--->通过c1,c2,c3创建Person类的实例对象
如果是Person类的类类型,创建出来的是Person对象,如果是Student类的类类型,创建出来的是Student对象,所以要进行强转
即:Person person=(Person)c1.newInstance(); 这里newInstance前提是要有要有无参构造的
上面讲到的Class.forName(); 不仅表示了类的类类型,还代表了动态加载类,大家要区分编译和运行,
编译时刻加载类是静态加载类,运行时刻加载类是动态加载类,这里说下静态加载类
我们在dos里面输入 javac Office.java 来进行编译,会报错,因为缺少Word类跟Excel类
我们如果建一个Word类,然后 进行编译后,再去编译javac.Office.java就会报只缺少Excel这个类的错误了,在这里我们已经建好了Word类,想用却因为没有Excel类的原因用不了,这显然不是我们想要的,我们要的是,我建一个Word类我就能用,大家想想,如果程序里有100个类,有一个用不了其余的99都用不了的话你将会有多么的崩溃呀,这就是静态加载类的弊端,new创建对象是静态加载类,在编译的时候就需要加载所有可能使用到的类,不管你用不用,
通过动态加载类我们可以解决此问题,Class.forName,就是动态加载类,就不会在编译时候报错了,它只会在你用到这个类的时候才会报没有类的错误,
java虚拟机栈就是java方法执行的内存模型,每调用一个方法,就会生成一个栈帧 用于存储方法的本地变量表,方法出口等信息
每次方法的调用都会对应栈帧的压栈跟出栈,如果请求的栈的深度过大,就会抛出stackOverflowError
热部署其实是也可以通过java的热加载来实现的
上面这个图的准备阶段做一下描述,假如说private static int num=80; 在这个准备阶段,它并不是把这个num初始化成为80 ,而是赋初始值,int类型的初始值为所以准备阶段是num的值是0,初始化阶段num的值才为80
什么时候初始化呢,5个初始化的时机,
需要先触发类初始化(加载-验证-准备自然需要在此之前):
1)1.使用 new 关键字实例化对象 2.读取或设置一个类的静态字段 3.调用一个类的静态方法。
2)使用 java.lang.reflect 包的方法对类进行反射调用时,若类没有进行初始化,则需要触发其初始化
3)当初始化一个类时,若发现其父类还没有进行初始化,则要先触发其父类的初始化。
4)当虚拟机启动时,用户需要制定一个要执行的主类(有 main 方法的那个类),虚拟机会先初始化这个类。
final 修饰的类会在编译期的时候把结果放在常量池,即使调用也不会触发初始化 ,final修饰的是个常量,它会把常量放在常量池中,调用常量它不会出发初始化的这个阶段