第八天 Java中Jvm内存模型和四种引用

类加载过程
我们知道我们写的java程序是 .java,但是我们的程序是怎么运行到jvm中的那 ,其实一个文件加载到jvm大致分为了一下几个步骤
编译-》加载-》验证-》准备-》解析-》初始化-》使用-》卸载

 

 

每个流程主要做的什么??
编译:
1:把java文件编译为class文件,然后生成一个class常量池
2:class常量池里面主要存放了字面量和符号引用
3:符号引用,就是用一组符号来描述所引用的目标【比如我们在代码中 new了一个对象,但是在编译阶段我们其实是不知道new的那个对象的内存地址是什么,所以这个时候我们使用一串符号来标识,到后面解析的时候 在替换为直接引用】
4:字面量 比如 final int z3=7 这个代码 final int z3 这个z3是一个常量,7就是一个字面量 再比如 String s="zzl" 这个s就是一个变量,zzl就是一个字面量
 
加载:
1:在磁盘上找到并通过IO读入字节码文件【这个是编译后的文件 .class】
2:把class文件所代表的静态存储结构转化为方法区的数据结构
3:在内存生成一个代表这个类的java.lang.class 对象,作为方法区这个类的各种数据访问入口,【这个对象不一定放在堆里面,比如 HotSpot虚拟机而言这个对象放在方法区】
连接:
1:执行校验,
校验:
1:校验字节码的正确性
准备:
1:给类的静态变量分配空间内存,并赋默认值【如果是被 final static 修饰的时候直接赋指定的值】
2:实例对象会在对象 初始化【解析后面就是初始化】的时候,跟随对象一起分配在java的堆上面
解析:
1:类装载器装入类所引用的其他类
2:将class常量池的家具结构转化为 方法区的运行时常量池
3:把符号引用转化为直接引用【上面编译的时候生成的符号引用】因为解析会逐步解析每一行代码,如果new的类没有加载就会执行上面的,所以能知道new对象的内存地址
4:运行时常量池每个class一个,是将class常量池解析后将符号引用替换为直接引用然后在方法区生成的
初始化:
1:初始化是类加载过程的最后一步,除了在加载阶段用户可以通过自定义加载器参与加载外,其余的操作完全是虚拟机控制,而到了初始化才是真正的开始执行我们类定义的java代码
2:把准备阶段分配空间的变量赋值
3:执行类的构造器【不等同构造函数】
4:执行静态代码块
5:jvm保证执行类的构造器的时候,同时只有一个线程执行【加锁】,防止出现多次初始化的问题
6:一个类 不是执行完解析,就会紧接着执行初始化,只有触发了初始化操作才会执行初始化【如果不触发初始化它会一直在解析阶段】
7:触发初始化的场景,1)遇到new ,getstatic ,putstatic或者invokestatic这四个指令,2)使用了reflect反射调用 3)父类没有初始化的时候会触发父类初始化, 4) 用户指定的main主类
完整执行图如下

 

 

 
Jvm内存模型
上面说完了类的加载了,下面我们来看看类到jvm是怎么分布的
到jvm中的数据主要分为两块,一块是线程共享的,一个是线程私有的
线程共享的数据
1:用来存放对象的实例,几乎所有的对象都在这里分配
方法区:
1:类的所有字段,或方法的字节码,静态变量,常量,类信息,构造函数,接口定义,还有运行时常量池
线程私有数据
程序计数器
1:一个指针,指向将要执行的代码的地址
本地方法栈
1:代码中 native的方法,一般是C或者C++写的调用系统底层的方法
java栈
1:java执行方法的内部模型【主要有 本地变量表 操作数栈 动态链接 方法出口】
局部变量表:一组变量的存储空间,用于存放方法参数,方法内的局部变量 单位是slot 一个slot可以存放32位的数据 ,局部变量表索引值从0开始,默认0位置是方法所属对象的引用
操作数栈:这是一个 后进先出的栈,方法的各种操作就在这里
动态链接:每个栈帧都包含了一个指向运行时常量池中改栈帧所属方法的引用,这个主要是为了做方法调用过程中的动态链接比如我们方法中 有一个new aaa()
返回地址:方法的返回出口
内存分布图

 

 

Java的四种引用
java的引用和内存泄露 oom,但是我们需要注意的一点,软引用,弱引用,虚引用里面对数据回收说的是我们创建的这几种引用数据的回收,不是说我们代码整个对象的回收
1:强引用【StrongReference】
这个是我们代码中最多的,比如我们下面代码
String zzl="zzz";
int t=1;

  

这些都是强引用,这些数据的垃圾回收是和方法或者对象一起的
只有一个对象有强引用与之关联,那么jvm就不会回收这个对象,即使在内存不住的情况下,jvm宁愿抛出OutOfMemort【oom】错误也不会回收这些数据:
2:软引用【SoftReference】
软引用就是用来描述一些有用但不是必须的对象,在java中 用SoftReference类标识,
对于软引用关联的对象,只有在内存不足的时候才会回收这些数据,所有这个可以很好的解决oom问题,
public static void main(String[] args) {
    SoftReference<Object> softReference=new SoftReference<>("aaa");
    System.out.println(softReference.get());
}

  

 

 

 

3:弱引用(WeakReference)
弱引用也是用来描述那些需要但是不是必须的对象,这种弱引用的对象只要碰到垃圾回收就会被回收,不管内存是否足够
public static void main(String[] args) {
    WeakReference<Object> weakReference=new WeakReference<>(new String("zzz"));
    System.out.println(weakReference.get());
    System.gc();
    System.out.println(weakReference.get());


}

  

输出结果

 

 

我们可以看到我们手动调用gc()以后 弱引weakReference用已经被回收,但是这个时候我们主方法main这个对象其实是没有被回收的,
弱引用还可以ReferenceQueue【引用队列】联合时候,主要弱引用被jvm回收了,这个对象就会被放入与之关联的引用队列
4:虚引用【PhantomReference】
虚引用和前面的 软引用,弱引用不同,他不影响对象生命周期,如果一个对象和虚引用关联,那和没有关联是一样的吗,在任何时候都可以被回收器回收
而且虚引用必须和 ReferenceQueue【引用队列】一起使用,当一个回收器准备回收一个对象的时候,如果发现它还有虚引用,就会吧这个虚引用加入到与之关联的 ReferenceQueue【引用队列】里面,程序可以判断引用队列是否加入了虚引用来了解被引用的对象是否被垃圾回收
public static void main(String[] args) {
    ReferenceQueue<Object> queue=new ReferenceQueue<>();
    PhantomReference<Object> phantomReference=new PhantomReference<>(new String("zzl"),queue);
    System.out.println(phantomReference.get());
    System.gc();
    System.out.println(phantomReference.get());


}

  

输出

 

 

我们发现 虚引用的数据已经被回收
 
所以如果我们有大量的读取往内存写数据的操作,我们可以使用软引用或者弱引用,
比如我们需要大量的读取本地文件到内存,我们就是使用软引用,当我们读取数据过多内存放不下的时候,就会发生gc然后释放掉我们的软引用数据,这样我们的jvm就不会抛出oom内存泄露异常了
 
 
真的上面各种操作我制作了一个流程图,感兴趣的可以看下
 

posted @ 2022-04-08 18:18  瀚海行舟  阅读(89)  评论(0)    收藏  举报