测试 :
public class ClassLoaderDemo01 {
public static void main(String[] args) {
ClassLoaderDemo01 demo01 = new ClassLoaderDemo01();
System.out.println(demo01.getClass().getClassLoader().getParent().getParent()); //Bootstrap根加载器
System.out.println(demo01.getClass().getClassLoader().getParent()); //扩展类加载器
System.out.println(demo01.getClass().getClassLoader()); //应用类加载器
}
}
//null 由于跟加载器是c++写的所以,我们的java访问不到!
//sun.misc.Launcher$ExtClassLoader@1b6d3586
//sun.misc.Launcher$AppClassLoader@18b4aac2
类加载流程 (双亲委派机制):
从图中可以看出,每个类加载器都有一个父加载器(有引用指向父加载器,而不是继承),在加载类时,首先check本身是否已加载,如果已加载,则返回,如果未加载,如果有父加载器则交给父加载器,如果没有父加载器则使用根加载器,如果仍没找到,则本加载器加载类,每一级加载器都执行相同的操作,这种机制称为委托机制,英语是parents delegation model
,翻译过来是双亲委派机制
由于双亲委派机制,加载java.lang.String
时会一直往上委派,直到根加载器,而根加载器只会加载jre/lib/rt.jar
中的java.lang.String
,从而确保自定义的java.lang.String
不会加载到jvm中,而不会让jvm错乱。
二、类加载器的分类
1、启动类加载器
也称根加载器(Bootstrap)
- 根加载器主要是用来加载
java_home/jre/lib
下的jar包,比如rt.jar
(含有全部java api的类),根加载器用C/C++实现,用null表示,在java代码中无法获取到根加载器。
Object o = new Object();
System.out.println(o.getClass().getClassLoader()); //根加载器
- 根加载器会提前加载好我们rt.jar中的所有基础类,作为我们的基础模板在方法区当中
2、扩展类加载器
拓展类加载器(Extension),于JDK1.9更名为 platform class loader
用来加载System.getproperty("java.ext.dirs")
也就是java_home/jre/lib/ext
下的jar包,扩展类加载器的父加载器是根加载器。
3、应用程序类加载器
应用类加载器 (Application)
用来加载System.getproperty(“java.class.path”)也就是我们常说的classpath下的类,此路径下都是应用程序的类,所以也可称为应用程序类加载器,它的父加载器是扩展类加载器,classLoader.getSystemClassLoader()返回的就是系统类加载器
以上就是java的核心类 + 对核心类补丁 + 用户自定的class,构成了我们java的基本盘!
4、用户自定义加载器
不满意
在程序运行时,如需自定义类加载器,通常继承java.net.URLClassLoader
,重写findClass
方法,这样符合双亲委派机制。
总结:当我们的一个类需要加载的时候,会将其分为给我们的应用类加载器,我们的应用类加载器会向上委托,直到委托给根加载器,判断自己是否加载过这个类,加载过直接返回,没加载过则继续的自顶向下的进行判断,如果都没加载过然后应用类加载器再对其进行加载!
二、native本地方法栈
一个方法被native修饰,标识java管不到这个方法了,这个方法输入c的势力范围!
-
本地方法栈(Native Method Stacks)与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它
-
Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法。当线程调用 Java 方法时,虚拟机会创建一个栈帧并压入 Java 虚拟机栈。然而当它调用的是 native 方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java 虚拟机栈中压入新的栈帧,虚拟机只是简单地动态连接并直接调用指定的 native 方法。
-
本地方法栈是一个后入先出(Last In First Out)栈。
-
由于是线程私有的,生命周期随着线程,线程启动而产生,线程结束而消亡。
-
本地方法栈会抛出 StackOverflowError 和 OutOfMemoryError 异常。
三、PC寄存器
程序计数器 Pragrom Counter Register
PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。(行号指示器)
-
它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域
-
在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
-
任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。
-
程序计数器会存储当前线程正在执行的Java方法的JVM指令地址,若在执行native方法,则是未指定值(undefined)
也可以理解为记录线程执行状态!
四、方法区
Method Area
方法区,在JDK1.8之前 又称永久代(Permanent Generation),常称为PermGen,位于非堆空间,又称非堆区(Non-Heap space)。
在在JDK1.8开始,又被称为元空间 ;我们根加载器加载rt.jar中的类,就是加载到这个元空间
永久代和元空间的区别 :
-
永久代:存储包括类信息、常量、字符串常量、类静态变量、即时编译器编译后的代码等数据。可以通过
-XX:PermSize
和-XX:MaxPermSize
来进行调节。当内存不足时,会导致 OutOfMemoryError 异常。JDK8 彻底将永久代移除出 HotSpot JVM,将其原有的数据迁移至 Java Heap 或 Native Heap(Metaspace),取代它的是另一个内存区域被称为元空间(Metaspace)。 -
元空间(Metaspace):元空间是方法区的在 HotSpot JVM 中的实现,方法区主要用于存储类信息、常量池、方法数据、方法代码、符号引用等。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。,理论上取决于32位/64位系统内存大小,可以通过
-XX:MetaspaceSize
和-XX:MaxMetaspaceSize
配置内存大小。
1、方法区保存的数据
方法区是所有线程共享的内存,在java8中移除了永久代的内容,方法区由元空间(Meta Space)实现并直接放到了本地内存中,不受JVM参数的限制并且将原来放在方法区的字符串常量池和静态变量都转移到了Java堆中,方法区与其他区域不同的地方在于,方法区在编译期间和类加载完成后的内容有少许不同,不过总的来说分为这两部分:
类元信息(Klass)
-
类元信息在类编译期间放入方法区,里面放置了类的基本信息,包括类的版本、字段、方法、接口以及常量池表(Constant Pool Table)常
-
量池表(Constant Pool Table)存储了类在编译期间生成的字面量、符号引用,这些信息在类加载完后会被解析到运行时常量池中
运行时常量池(Runtime Constant Pool)
-
运行时常量池主要存放在类加载后被解析的字面量与符号引用,包含但不限于。运行时常量池具备动态性,可以添加数据比较多的使用就是String类的intern()方法
-
运行时常量池都存储在方法区,而字符串常量池在jdk7时就已经从方法区迁移到了java堆中。
五、类加载顺序
分清主次,静态先行,静态代码块是在类加载的时候执行的,初始化在Class模板中,然后找入口main,然后执行构造代码块,执行完构造代码块之后,执行构造方法!
//测试:
public class ClassLoaderTest {
{
System.out.println(“ClassLoaderTest的代码块444444”);
}
public static void main(String[] args) {
System.out.println(“ClassLoaderTest的main方法77777”);
new code() ; //6,7,3,1,2
System.out.println(“-==============”);
new code() ; //1,2
System.out.println(“===============”);
new ClassLoaderTest() ; //4,5
}
public ClassLoaderTest(){
System.out.println(“ClassLoaderTest的构造方法555555”);
}
static {
System.out.println(“ClassLoaderTest的静态代码块66666”);
}
}
class code{
{
System.out.println(“code的代码块11111”);
}
public code(){
System.out.println(“code的构造方法22222”);
}
static {
System.out.println(“code的静态代码块3333”);
}
}
六、栈
在介绍JVM栈之前,我先了解一下 栈帧 概念
栈帧:一个栈帧随着一个方法的调用开始而创建,这个方法调用完成而销毁。栈帧内存放者方法中的局部变量,操作数栈等数据。
Java栈也称作虚拟机栈(Java Vitual Machine Stack),JVM栈只对栈帧进行存储,压栈和出栈操作。Java栈是Java方法执行的内存模型。下面我们来看一个Java栈图。
总结
-
每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象)。对象都存放在堆区中。
-
每个战中的数据(基础数据类型和对象引用)都是私有的,其他栈不能访问。
-
栈分为3个部分:基本类型变量,执行环境上下文,操作指令区(存放操作指令).
-
在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。
-
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量
-
分配的内存空间,该内存空间可以立即被另作他用。
StackOverFlowError
java.lang.StackOverFlowError,当我们的方法过多占满了栈空间,那么就会产生该异常(本质是个错误)!
七、栈、堆、方法区之间的交互关系
HotSpot 是通过指针访问对象的!(HotSpot是我们oracle发布的JDK统称)
栈:保存着实例对象在堆内存中的地址
堆:保存着类元数据的地址
Java8中的内存分布 :
与1.7的区别:
1、字符串常量池由方法区移动到堆中
2、方法区从堆内存移出,到本地内存当中
八、堆【重点】
一个JVM实例中是只包含一个堆内存,堆内存的大小是可调节的,类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,保存所有引用类型的真实信息,以方便执行器执行。
区别:
-
JDK 7逻辑上分为:新生区 + 养老区 + 永久区 ,实际上:新生区 + 养老区
-
JDK 8逻辑上分为:新生区 + 养老区 + 元空间 ,实际上:新生区 + 养老区
由于我们的方法区在1.7被称为永久区,在1.8被称为元空间,逻辑上是将方法区归入堆内存中!也会被称为非堆,目的就是为了和堆区分开来。
永久区占用的是堆内存中空间,元空间占用的是物理机的内存
OOM
出现oom:OurOfMemoryError : java heap space 的原因
-
JVM的堆内存不够,可以通过手动设置参数来-Xms,-Xm来调整。
-
代码中创建了大量的对象,并且长时间不能被垃圾GC回收(存在引用)。
堆参数入门
注意:我们设置的堆参数-Xmx,-xms只会作用在堆中的新生区和养老区。
| 说明 | 备注 | |
| — | — | — |
| -Xms | 初始堆的分配大小,默认为物理内存的六十四分之一 | |
| -Xmx | 堆的最大分配大小(默认为物理内存的四分之一) | |
| -Xmn | 新生代的大小 | |
测试我们JVM所占用的内存!
@SuppressWarnings(“all”)
public class HeapMemeoryTest {
public static void main(String[] args) {
long totalMem = Runtime.getRuntime().totalMemory(); //返回当前JVM占用的内存
long maxMemory = Runtime.getRuntime().maxMemory(); //返回当前JVM可占用的最大内存
System.out.println(“当前JVM占用的内存” + totalMem/1024/1024);
System.out.println(“前JVM可占用的最大内存” + maxMemory/1024/1024);
}
}
//当前JVM占用的内存245
//前JVM可占用的最大内存3625
为什么要使用2个survivor区 ? 可以不用吗 ?
参考文章 :https://blog.csdn.net/antony9118/article/details/51425581
调节堆的占用内存 :
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
面试结束复盘查漏补缺
每次面试都是检验自己知识与技术实力的一次机会,面试结束后建议大家及时总结复盘,查漏补缺,然后有针对性地进行学习,既能提高下一场面试的成功概率,还能增加自己的技术知识栈储备,可谓是一举两得。
以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~
重要的事说三遍,关注+关注+关注!
更多笔记分享
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
以下最新总结的阿里P6资深Java必考题范围和答案,包含最全MySQL、Redis、Java并发编程等等面试题和答案,用于参考~
重要的事说三遍,关注+关注+关注!
[外链图片转存中…(img-6dv1VNmG-1712172659627)]
[外链图片转存中…(img-nG3K8EKw-1712172659628)]
更多笔记分享
[外链图片转存中…(img-tZ7A9Ptf-1712172659628)]
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算