大家好,又见面了,我是你们的朋友全栈君。
目录
jvm是什么?
jvm是java虚拟机Java Virtual Machine的缩写。
什么是java虚拟机?
虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。简单来说JVM是用来解析和运行Java程序的。
java虚拟机的好处?
java可以跨平台运行最主要的原因就是因为有java虚拟机,同时jvm可以屏蔽系统差异发明一些新的语言就可以使用jvm来完成。这样就做到了《一次编译到处运行》的特点。
要想知道JVM中内存区域的划分,拿首先就要知道JVM中的内存区域从什么地方来,JVM中的内存是JVM向操作系统中申请一块内存,在针对这个内存划分一个区域。
堆(运行时常量池) | new的对象就会放在堆中 |
---|---|
方法区 | 加载好的类放在方法区,静态成员 |
栈(JVM栈和本地方法栈) | 局部变量 |
程序计数器 | 存储的是地址描述的是当前线程接下来 要执行的指令在什么地方 |
注意:一个进程中存在多个线程,每个线程中都存在自己的栈和程序程序计数器,一个进程中共用一个堆和一个方法区。 判断一个变量是在堆上还是在栈上,和这个变量是基础类型还是引用类型没有关系,和这个变量是局部变量、静态变量还是成员变量有关。
1)如何理解基础数据类型还是引用数据类型
基础数据类型中对应的内存空间储存的是具体的值
引用数据类型中对应的内存空间存储的是new对象的地址
2)如何理解引用和对象
引用还是存储的是地址,new对象是本体。
3)如何理解局部变量成员变量以及静态变量
成员变量在堆上 局部变量在栈上 静态变量在方法区中
public class Test01 {
//成员变量
Test01 t1=new Test01();
//静态变量
static Test01 t2=new Test01();
public static void main(String[] args) {
//局部变量
Test01 t3=new Test01();
}
}
4)递归的执行过程
虽然这几个变量a,但是他们是处于不同栈帧中的变量。
5)static方法和普通方法之间的区别
普通方法中有this(和实例相关),静态方法中没有this(和类相关)普通方法也叫实例方法,静态方法也叫类方法。
GC是垃圾回收机制,java中申请的内存可以被垃圾回收装置进行回收,GC可以一定程度的避免内存泄漏,但是会引入一些额外的开销。
GC中主要回收的是堆和方法区中的内存,栈中内存的释放要等到线程结束或者是栈帧被销毁,而程序计数器中存储的是地址不需要进行释放。
回收对象的基本单位:
对于GC中回收的基本单位不是字节而是对象
GC回收的一般是已经不使用的位置的内存
回收对象的基本思路:
1)标记:找到这个对象是否需要回收,并且标记出来
2)回收:将这个对象回收回去
每个对象都会分配一个专门的计数器变量当有一个新的引用指向这个变量的时候计数器就会加一,当有一个引用不指向这个变量计数器就会减一,当引用计数器为0时就会让这个对象标记回收。
但是这就引用出循环引用问题不能解决:
public class Test01 {
public static void main(String[] args) {
Test a=new Test();
Test b=new Test();
a.t=b;
b.t=a;
a=null;
b=null;
}
}
class Test{
Test t=null;
}
当想要使用对象a,找到a的引用,这个引用在对象b中,想找对象b的引用在a中
在java中GC采用可达性分析来描述
代码中的对象之间是有一定的关联的,他们构成的一个“有向图”,然后我们去遍历这个“有向图”如果对象遍历的到就不是垃圾,反之就是垃圾。
一般从什么地方开始遍历?
1、每个栈中栈帧的局部变量表
2、常量池中引用的对象
3、方法区中静态变量引用的对象
遍历的时候不是像二叉树一样从一个地方开始遍历,而是从多个地方遍历,这样的我们统称为GCRoot。
1、该类的所有实例都已经被回收
2、加载该类的ClassLoader也已经被回收了
3、该类对象没有在代码中使用
强引用:可以找到对象也能决定对象的生死
软引用:可以找到对象也能在一定程度上决定对象的生死
弱引用:可以找到对象但是不能决定对象的生死
虚引用:不能找到对象也能不能决定对象的生死
标记清除指的是直接释放将标记的区域中的内存释放
优点:简单高效
缺点:容易造成内存的碎片问题
将内存划分为两个区域直接拷贝不是垃圾的对象放到另一个区域
优点:可以很好的解决内存的碎片问题,不会存在碎片
缺点: 需要额外的内存空间(如果生存的对象比较多这时就比较低效)
类似于数组删除数据
优点:不想复制一样需要额外的内存空间也没有内存碎片
缺点:搬运的效率较低不适合频繁使用
我们可以通过分代回收的方法来更好的清理内存
我们把堆中的内存分为新生代(伊甸区和生存区)和老年代
对象诞生于伊甸区,新对象的内存就是新时代中的内存 当经历过第一轮GC扫描时就会把标记的对象干掉,没有被干掉的就会被拷贝到生存区01 进入生存区中的对象也会经过扫描然后拷贝到生存区02(生存区中的对象是从另外一个生存区和伊甸区过来的) 当对象在生存区经历了若干次拷贝之后没有被回收就说明这个对象存活的时间比较长就会被拷贝到老年代 老年代中的对象也要经过GC扫描,由于老年代中的对象存活的时间比较长,扫描老年代中的周期会比较长
垃圾回收器中都是做两件事情标记和回收,当进行回收的时候应用线程就会停止工作STW。
CMS和G1的区别:
a)初始标记(只去找GCRoot直接关联的对象)
b)并发标记(和应用线程并发执行,去遍历所有对象)
cms会一直执行下去、G1发现老年代没有存活的对象之后就会直接回收。
c)最终标记(为了修正b产生的误差)
d)筛选回收
我们根据类名找到文件,并且读取文件构造解析,将内容读取到内存中去,并且构造相应的类对象,这个过程叫做加载。
如果这个类还有其他相关联的类,就会将其他的依赖内容引入,这个过程叫做链接。
初始静态成员并且执行静态代码块,这个过程叫做初始化。
1)构造该类的实例
2)调用该类的静态属性和静态方法
3)使用子类时会触发父类的加载
class Test{
static {
System.out.println("Test.static{}");
}
{
System.out.println("Test{}");
}
public Test() {
System.out.println("Test构造方法");
}
}
class Test0 extends Test{
static {
System.out.println("Test0.static{}");
}
{
System.out.println("Test0{}");
}
public Test0() {
System.out.println("Test0构造方法");
}
}
public class Test01 extends Test0{
public static void main(String[] args) {
System.out.println("开始");
new Test0();
new Test0();
System.out.println("结束");
}
}
执行静态代码块——执行代码块——执行构造方法
内置的三个加载器 | 加载内容 | 位置 |
---|---|---|
BootstrapClassLoader | 加载标准库中的类(String ArrayList…) | jdk\jre\lib\rt.jar(标准库中的类的实现在这个rt.jar中) |
ExternsionClassLoader | 加载一个特殊的类 | jdk\jre\lib\ext 的所有jar包中找 |
ApplicationClassLoader | 加载应用程序自己写的类 | CLASS_PATH环境变量 java -cp 当前目录 |
双亲委派模型的本质就是类加载的过程中通过类名查找到类的.class文件的查找过程,属于加载过程中的内容。
当一个类开始加载时,会先从AppClassLoader开始,但是它不会立刻查找会先交给自己的父类,ExtClassLoader也会交给自己的父类,然后BootstrapClassLoader拿到类名之后在rt.jar中开始查找,找到就返回,没找到就会在ExtClassLoader中的ext目录中开始查找,找到就返回,没有找到就会在AppClassLoader中的CLASS——PATH环境变量中、-cp指定目录中、当前目录中三个地方去查找,如果找到就加载,没有找到就抛出一个ClassNotFoundException异常。
注意:自己写的类加载器是可以违背双亲委派模型的(Tomcat中的类加载器就没有遵守双亲委派模型)
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/174459.html原文链接:https://javaforall.cn