源代码 .Java文件编译成 . Class 文件,然后进入到类加载器Class Loader,最后由类加载器加载到JVM
双亲委派机制:保证安全的机制
1.类加载器收到类加载的请求
2.将这个请求向上委托给父类加载器去完成,一直向上委托,知道启动类加载器
3.启动加载器检查是否能够启动这个类,能加载就结束,使用当前的类加载器,否则抛出异常,通知子类加载器进行加载
4.重复步骤 3
中心思想就是 自身的类不进行加载,让自身类的父类进行加载,这样就会到顶层的类,最后都会到启动类加载器,如果启动类加载器没有这个类不能进行加载,然后再一步一步地向下加载
这样一来,如果用户代码中自定义了一个全包名和自带的类一样,比如Java.lang.String 的类,就会加载Java本身的String类,而不是自定义的类。
这样做是为了避免出现过多的重复字节码,避免原始类被覆盖的问题
如何打破双亲委派机制
1.在自定义的类加载器中重写loadClass方法,因为ClassLoader中有一段双亲委派的核心代码,用来不断地找类加载器的父类加载器,直到父类加载器为空,如果重写这段代码就可打破机制
2.使用线程上下文类加载器
Java中的类加载器
Bootstrap ClassLoader (启动类加载器)
Bootstrap ClassLoader,启动类加载,默认加载的是jdk\lib目录下jar中诸多类;
这个路径可以使用 -Xbootclasspath参数指定。
Extension ClassLoader (扩展类加载器)
Extension ClassLoader,扩展类加载器,默认加载jdk\lib\ext\目录下jar中诸多类;
这个路径可以使用 java.ext.dirs系统变量来更改。
Application ClassLoader (应用程序类加载器)
Application ClassLoader,应用程序类加载器,负责加载开发人员所编写的诸多类。
User ClassLoader (自定义类加载器)
自定义类加载器,当存在上述类加载器解决不了的特殊情况,或存在特殊要求时,可以自行实现类加载逻辑。
Native关键字
1.凡是带了native关键字的方法,都是Java的作用范围达不到了,要调用底层的C语言的库
2.被native修饰的方法都会走本地方法栈
3.调用本地方法接口JNI
4.JNI的作用:扩展Java的使用,融合不同的编程语言为Java所用,Java诞生的时候C和C++横行,想要立足就必须要有C和C++的方法
5.它在内存区域中专门开辟了一快标记区域:Native Method Area 本地方法栈,登记 Native 方法
6.在最终执行时候,通过JNI加载本地方法库中的方法
方法区 Method Area
1.静态变量,常量,类信息,(构造方法,接口定义)、运行时常量池存在方法区中,但是,实例变量存在堆内存中,与方法区无关
Static Final Class模板 常量池
栈 Stack
1.栈内存,主管程序的运行,生命周期和线程同步,线程结束,栈内存也就释放,所以不存在垃圾回收
2.栈 满了之后就会报 StackOverFlowError 栈内存溢出错误
3.栈 + 堆 + 方法区 的交互关系 对象实例化的过程
堆 Heap
Heap,一个JVM只有一个堆内存,堆内存的大小是可以调节的
类加载器读取文件之后,一般会把什么东西放到堆中?类、方法、常量、变量,保存我们所有引用类型的真实对象
堆内存还要细分为三个区域 新生区、养老区、永久区 , GC垃圾回收主要发生在伊甸园区和养老区
假设堆内存满了,OOM,就会 发生堆内存溢出错误
jdk 1.8 之后永久存储区改名为元空间,并且本质有一些不同
新生区 :类诞生和成长的地方,甚至是死亡的地方
其中分为两个部分,伊甸园区 所有的对象都是在伊甸园区new 出来的 和 幸存者区 又分为 0 和 1区(from 和 to)
元空间:这个区域常驻内存的,方法区也就是位于这一部分,用于存放JDK自身携带的Class对象,Interface元数据,存储的是Java运行的一些环境或者类信息,这个区域不存在垃圾回收!当JVM关闭的时候也就释放这个区域的内存。
一个启动类,如果加载了大量的第三方jar包,或者Tomcat部署了太多的应用,带量动态生成反射类,不断地被加载,知道内存满,就会导致OOM
元空间的发展历史,在JDK 1.6以及之前的时候叫做永久代,常量池在方法区
JDK 1.7 也叫做永久代,但是慢慢的退化,并且已经有了去永久代的想法,常量池在堆中
JDK 1.8 没有永久代,取而代之的是元空间,常量池也在元空间中
下图中所展示的结构,元空间逻辑上是存在的,也是堆的一部分,但是物理上是不存在的,
GC 垃圾回收
JVM在进行GC的时候,并不是对这三个区域统一回收。大部分的回收都是新生代
GC算法 : 标记清除算法、标记压缩法、复制算法、引用计数器
引用计数器方法,为每一个对象都创建一个引用计数器,每当对象被引用一次,计数器就加一次,最后根据引用次数,清除垃圾,因为每个计数器也有消耗,同时性能很差,所以几乎不用
1.标记清除算法 : 回收时扫描这些对象,并对活着的对象进行标记,然后清除,对没有标记的对象进行清除
优点:不需要额外的空间!
缺点:两次扫描,严重浪费时间,会产生记忆碎片
2.标记压缩算法 : 是对标记清除算法的优化,在标记清除的基础上,为了防止产生记忆碎片,再次扫描,向一端移动存活的对象,多了一个移动成本
3.复制算法 : 谁空谁是to 幸存0区和幸存1区 之间的from 和 to 的关系是随着多次GC的过程而不断交换的,简单理解为谁空谁是to
1.每次GC 都会将Eden活的对象移到幸存区中,一旦Eden被GC后就会变成空的
2.当一个对象经历了15次的GC都还没有死的情况下,就会进入到老年代(养老区)
优点:没有内存碎片
缺点:浪费了内存空间,有一般空间永远是空 的 to。假设对象100%存活,成本比较高
最佳使用场景:对象存活率低,也就是新生代
算法总结:内存效率 : 复制算法 > 标记清除算法 > 标记压缩算法
内存整齐度 : 复制算法 = 标记压缩 > 标记清除
内存利用率从 : 标记压缩 = 标记清除 > 复制算法
没有最好的算法,只有最合适算法