目录
jvm内存模型
线程私有的:
-
程序计数器
-
虚拟机栈
-
本地方法栈
线程共享的:
-
堆
-
方法区
-
直接内存 (非运行时数据区的一部分)
堆:
JVM内存管理最大的一块,对被线程共享,目的是存放对象的实例,几乎所欲的对象实例都会放在这里,
当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回
收器进行垃圾的回收管理
方法区:
又称非堆区,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器优化后的代码等数据.1.7
的永久代和1.8的元空间都是方法区的一种实现
虚拟机栈:
又称方法栈,线程私有的,线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方
法出口等信息.调用方法时执行入栈,方法返回式执行出栈.
本地方法栈:
与栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.
程序计数器:
保存着当前线程执行的字节码位置,每个线程工作时都有独立的计数器,只为执行Java方法服务,执行
Native方法时,程序计数器为空.
java类加载机制
Java的类加载机制是指 JVM 在运行时将类的字节码加载到内存,并进行验证、准备、解析和初始化的过程。这个过程是Java程序执行的基础,它确保了类和类的依赖关系正确加载和连接,使得程序能够正确执行。
其中验证,准备,解析合称链接
加载通过类的完全限定名,查找此类字节码文件,利用字节码文件创建Class对象.
验证确保Class文件符合当前虚拟机的要求,不会危害到虚拟机自身安全.
准备进行内存分配,为static修饰的类变量分配内存,并设置初始值(0或null).不包含final修饰的静态变量,因为final变量在编译时分配.
解析将常量池中的符号引用替换为直接引用的过程.直接引用为直接指向目标的指针或者相对偏移量等.
初始化主要完成静态块执行以及静态变量的赋值.先初始化父类,再初始化当前类.只有对类主动使用时才会初始化.
触发条件包括,创建类的实例时,访问类的静态方法或静态变量的时候,使用Class.forName反射类的时候,或者某个子类初始化的时候.
使用:new出对象程序中使用
卸载:执行垃圾回收
类加载器
类加载器是一个用来加载类文件的类。Java 源代码通过 javac 编译器编译成类 文件。然后 JVM 来执行类文件中的字节码来执行程序。类加载器负责加载文件 系统、网络或其他来源的类文件。
主要有一下四种类加载器:
-
启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
-
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
-
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
-
用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。
双亲委派模式
概念
加载器加载类时先把请求委托给自己的父类加载器执行,直到顶层的启动类加载器。父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载。
优点:
-
避免类的重复加载
-
避免Java的核心API被篡改
怎么打破双亲委派模型?
-
自定义类加载器,在自定义类加载器中重写
loadClass()
方法。 -
线程上下文类加载器:Java提供了
Thread.currentThread().setContextClassLoader()
方法,可以在特定线程中设置线程上下文类加载器。这个类加载器会在类加载时优先使用,可以打破双亲委派模型。 -
使用
Class.forName()
方法:Class.forName()
方法可以根据类的全限定名加载类,并允许指定特定的类加载器。通过指定特定的类加载器,可以绕过双亲委派模型。
堆和栈的区别?
栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在区域不连续,会有碎片。
1、功能不同
栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
2、共享性不同
栈内存是线程私有的。 堆内存是所有线程共有的。
3、异常错误不同
如果栈内存或者堆内存不足都会抛出异常。 栈空间不足:java.lang.StackOverFlowError。 堆空间不足:java.lang.OutOfMemoryError。
4、空间大小
栈的空间大小远远小于堆的。
对象分配规则?
-
对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC(收集新生代)。
-
大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
-
长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,达到阀值对象进入老年区。
-
动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,那么年龄大于等于该年龄的对象可以直接进入老年代。
-
空间分配担保。Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。如果大于,那么这次Minor GC是安全可行的,如果小于的话,虚拟机就会查看参数HandlePromotionFailure是否允许担保失败。如果是true那就只进行Minor GC,如果false则进行Full GC。
触发FullGC的情况?
-
对象内存分配失败:当应用程序尝试分配新的对象内存空间时,如果堆空间已经满了,无法再分配新的对象,就会触发 Full GC。
-
老年代空间不足:当年老代(Old Generation)空间占用达到一定阈值时,为了释放不再使用的对象,触发 Full GC 来进行年老代的垃圾回收。
-
永久代/元空间空间不足:在旧版本的 Java 中,永久代(Permanent Generation)用于存储类的元数据和常量池等信息。而在 Java 8 及之后的版本中,永久代被元空间(Metaspace)所取代。当永久代/元空间空间不足时,会触发 Full GC 来进行垃圾回收。
-
显式调用:程序中显式调用
System.gc()
方法可以建议 JVM 执行垃圾回收。尽管这只是一个建议,但在某些情况下,JVM 可能会选择执行 Full GC。
java对象创建过程?
1.检查类是否已经被加载;遇到new指令,检查这个指令的参数能否在常量池定位到符号引用,并检查是否加载、解析、初始化过,如无,先进行类的加载。
2.为对象分配内存。一种办法“指针碰撞”、一种办法“空闲列表”,最终常用的办法“本地线程缓冲分配(TLAB)”
3.将除对象头外的对象内存空间初始化为0
4.对对象头进行必要设置:如何找到类元数据、哈希码、gc分代年龄
5:执行构造方法。
Java的对象结构?
Java对象由三个部分组成:对象头、实例数据、对齐填充。
-
对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。
-
实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
-
对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
Java程序的执行过程?
-
先把 Java 代码编译成字节码,也就是把 .java 类型的文件编译成 .class 类型的文件。这个过程的大致执行流程:Java 源代码 -> 词法分析器 -> 语法分析器 -> 语义分析器 -> 字符码生成器 ->最终生成字节码,其中任何一个节点执行失败就会造成编译失败;
-
把 class 文件放置到 Java 虚拟机,这个虚拟机通常指的是 Oracle 官方自带的 Hotspot JVM;
-
Java 虚拟机使用类加载器(Class Loader)装载 class 文件;
-
类加载完成之后,会进行字节码效验,字节码效验通过之后 JVM 解释器会把字节码翻译成机器码交由操作系统执行。但不是所有代码都是解释执行的,JVM 对此做了优化,比如,以Hotspot 虚拟机来说,它本身提供了 JIT(Just In Time)也就是我们通常所说的动态编译器,它能够在运行时将热点代码编译为机器码,这个时候字节码就变成了编译执行。