Java虚拟机
Java虚拟机运行时数据区,分为以下几个模块,包含所有线程共有的数据区和线程单独享有的数据区。
JVM通过根搜索算法来判定对象是否可以回收,一般对于不能从根(GC Roots)搜索到的对象是可以被回收的。
能够被作为GC Roots对象有:虚拟机栈本地变量表中引用的对象(也就是正在调用的方法中引用的);方法区中静态属性或常量引用的对象;本地方法栈引用的对象。
可以被回收的对象并不一定绝对被回收,JVM先做一次标记和筛选,把那些覆盖了finalize方法的对象筛选出来然后触发finalize方法,如果在finalize方法中对象复活,则不回收,否则回收,且finalize方法仅会被触发一次。
垃圾回收算法
标记-清除:把标记为待回收的对象空间清除,容易造成大量空间碎片;复制算法:将内存分为三个区域,一个较大的eden区和两个较小的survivor区。每次GC都把存活的对象挪到其中一个servivor区,然后把eden全部清除。只对每次GC时存活对象较少时比较有效,适用于新生代;标记-整理:把标记后存活的对象向一个方向移动,然后清除其它空间。比较适合老年代。内存分配与回收策略
对象默认优先分配在新生代;大对象直接分配到老年代;长期存活的对象转移到老年代:虚拟机给每个对象定义一个对象年龄,没发生一次minor GC,年龄就增加一次,超过默认值之后就会进入到老年代。动态对象年龄判定:对象不一定是必须到了默认年龄才能进入老年代,如果一个eden区中所有相同年龄的对象大小综合超过eden一半的空间,那么大于等于这个年龄的对象也会进入老年代。类文件结构class文件是二进制组成的,class有两种数据类型:无符号数和表。
无符号数是基础数据类型,其中u1表示1个字节、u2表示2个字节(一个字节8个bit,而4个bit可以表示1个16进制的数,也就是说1个字节可以用2个16进制数表示);
表是由多个无符号数或其它表构成的。
code属性:java方法体中的代码经javac编译后会存储在code属性中(接口中方法或抽象方法没有code属性)
Exceptions属性:列举出方法throws后面抛出的异常;
其它各属性不再一一列举。类加载机制
类加载的时机
主动引用的几种情况才会加载(前提是此类没有被加载过)
被动引用不会触发初始化
调用父类静态方法,不会初始化子类;通过数组定义引用类,不会触发初始化;引用静态常量不会触发。加载过程
通过一个类全限定名获取定义此类的二进制字节流(一般是class文件)将二进制字节流转化为方法区中的运行时数据结构在内存(堆)中生成这个类的Class类的对象,作为方法区这个类的各个数据的访问入口连接过程
验证阶段:文件格式验证(是否符合Class文件规范)、元数据验证(是否符合java语法规范)、字节码验证(确保语义是符合逻辑的)、符合引用验证。准备阶段:正式为类变量分配内存并设置初始值。有两点需要注意:
一,此处只为类变量分配内存(static修饰的),不包含实例变量;
二,设置的初始值是这个类型的0值,不是实际值(但被final修饰的赋的就是实际值)解析阶段:将符合引用替换为直接引用
初始化过程
初始化过程主要是执行类构造器<cinit>方法
类加载器
比较两个类对象是否相等,只有加载两个类加载器的完全一样,才有意义;如果一个类加载器收到一个类加载请求,它首先会请求委派给父类加载器完成,父类无法完成时,子类加载器才进行加载。虚拟机字节码执行引擎
运行时栈帧结构
方法调用和分派
所有的方法在Class文件中都是一个符合引用,而一部分方法在类加载时就直接解析为直接引用。这种方法必须是“编译时已知,运行时不可变”,就是静态方法和私有方法两大类静态分派:依赖静态类型来定位方法执行版本称为静态分派,典型应用是重载。Human是静态类型,后面的Man和Women则是实际类型。
静态类型在编译器可知,而动态类型则是在运行时才能知道。动态分派:运行期间根据实际类型来确定方法执行版本,典型应用是覆盖。
结果是
内存模型及线程安全
JMM规定所有内存都存储于主内存中,每条线程还有自己的工作内存。
变量的读取、赋值操作必须在工作内存中进行。
内存直接的交互操作,主要有以下8种操作:
8种操作需要满足以下规则
volatile关键字
volatile关键字保证了变量的所有线程的可见性,但并非是线程安全的。两种情况下是线程不安全的:
一,变量依赖于自身(比如i++之类的)
二,变量依赖于其它变量(比如i=a+3)volatile禁止语义重排序volatile的具体实现
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。