JavaSE学习之Java虚拟机(JVM)


1. 运行时数据区

程序计数器

用于记录正在执行的虚拟机字节码指令的地址,即每个线程都会有自己的一个程序计数器。换句话讲,它就是用来指示当前线程所执行的字节码执行到了第几行。程序计数器是线程隔离的。

Java虚拟机栈

即VM Stack,每个Java方法执行的同时会创建一个栈帧,虚拟机栈描述的是Java方法执行的内存模型,用于存储局部变量,操作数栈,动态链接(???),常量池引用,方法出口等信息。虚拟机栈是线程隔离的。

本地方法栈

本地方法栈与Java虚拟机栈类似,唯一区别在于本地方法栈是为Native本地方法所服务。本地方法栈是线程隔离的。
注: 本地方法,一般是是由其他语言编写,并被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理!

所有new的对象都是在这里分配内存,也是垃圾收集的主要区域(“GC堆”)。堆是线程共享的。

方法区

用于存放已被加载的类信息(包括类名称,方法信息,字段信息)、静态变量常量以及编译器编译后的代码等数据。
注: JDK1.8开始,HotSpot VM取消了永久代,把方法区移至元空间,元空间位于本地内存中,而不是虚拟机内存中。方法区是一个JVM规范,永久代和元空间都是其一种实现方式。在JDK1.8之后,原来永久代的数据被分到了元空间中。元空间存储类的元信息中存放静态变量常量等。

运行时常量池

运行时常量池属于方法区的一部分。
Class文件中的常量池(编译器生成的字面量符号引用)会在类加载后放入这个区域。
除了编译器生成的常量,也允许动态生成,如String#intern()。

直接内存

//TODO…

Java内存模型(JMM)

主内存和工作内存:

  • 处理器上的寄存器的读写的速度比内存(主存)快几个数量级,为了解决这种速度矛盾,在它们之间加入了高速缓存。
  • 加入高速缓存带来了一个新的问题:缓存一致性。如果多个缓存共享同一块主内存区域,那么多个缓存的数据可能会不一致,需要一些协议来解决这个问题。
  • 共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量(实体)存储在主内存(main memory)中,每个线程都有一个私有本地内存(local memory),本地内存中存储了该线程以读/写共享变量副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

(图片来源:https://www.cnblogs.com/haoworld/p/java-bing-fa-xian-cheng-an-quan-he-nei-cun-mo-xing.html#toc_19
在这里插入图片描述线程A与线程B之间如要“成功”通信的话,必须要经历下面2个步骤:

  • 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
  • 然后,线程B到主内存中去读取由线程A之前更新过的共享变量。

问题:

共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能某线程本地内存中的值没有及时刷新到主内存,导致其他线程读取的值不是最新的,从而发生数据不一致的问题。

解决方案:

  • volatile关键字:可以保证共享变量的可见性、有序性,无法保证原子性;
  • synchronized同步锁:能够保证可见性、原子性,由于同一时间只有一个线程获得锁,自然就保证了有序性

2. 垃圾收集

注: 垃圾收集主要是针对方法区进行。程序计数器虚拟机栈本地方法栈这三个区域是属于线程私有的,只存在于线程的生命周期内,线程结束后会小时,因此不需要对这三个区域进行垃圾回收。

如何判断一个对象是否可被回收?
1. 引用计数算法

为每一个对象维护一个引用计数器,每当对象增加一个新的引用时计数器加1,反之,引用失效时计数器则减1。于是当计数为0的时候对象即可被回收。
注: 当两个对象出现循环引用时,引用计数器不可能减为0,最终导致对象无法进行回收。正是如此,Java虚拟机不使用引用计数算法。示例代码如下:

public class Test {
    public Object instance = null;
    public static void main(String[] args) {
        Test a = new Test();
        Test b = new Test();
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
        doSomething();
    }
}
2. 可达性分析算法

以GC Roots为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。Java虚拟机使用该算法来判断对象是否可以被回收,GC Roots一般包含以下内容:

  • 虚拟机栈中局部变量表中引用的对象
  • 本地方法栈中JNI中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中的常量引用的对象
3. 方法区的回收

因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收的性价比不高。
主要是对常量池的回收和对类的卸载
为了避免内存溢出,在大量使用反射和动态代理的场景都需要虚拟机具备类卸载功能。
类的卸载条件有很多,需要满足以下三个条件,并且满足条件也u不一定会被卸载:

  • 该类的所有实例都已经被回收,此时堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的Class对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法
4. finalize()

类似于C++的析构函数,用于关闭外部资源。但是try…finally…等方式可以做得更好,并且finalize()方法运行代价很高,不确定性打,无法保证各对象的调用顺序,因此最好不要使用!
当一个对象可以被回收时,如果需要执行该对象的finalize()方法,就有可能在该方法中让对象重新被引用,从而实现“自救”。不过“自救”只能进行一次,即如果回收对象之前调用了finalize()方法自救,后面再次回收时不会在调用该方法!

引用类型

//TODO


参考:
[1] Cyc2018
[2] https://www.cnblogs.com/haoworld/p/java-bing-fa-xian-cheng-an-quan-he-nei-cun-mo-xing.html#toc_19

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值