虚拟机之JVM

3 篇文章 0 订阅

众所周知,Java是一门夸平台语言,之所以能跨平台,就是因为有Java虚拟机JVM.

JVM其实有三层意思:

1. 抽象虚拟机规范
2. 一个具体的商业实现
3. 一个运行中的虚拟机实例

一个Java程序对应一个JVM实例,他们的生命周期也相同.JVM主要是对字节码进行解释执行,也支持及时编译执行JIT.


==================================================================================================

知识点:
1. 类加载和初始化
2. 运行时内存划分及方法调用
3. gc

==================================================================================================

类加载和初始化
1. 加载过程
1) 加载
   通过类的全名来获取定义此类的二进制字节流,然后将字节流转换为方法区的数据结果,最后在内存中生成一个代表这个类的class

   对象作为访问入口.

   注意:可以从任何地方获取字节流,如网络,文件,数据库等;class对象可以放在方法区,也可以放在堆中.
2) 验证
   文件格式验证: 验证字节流是否符合class文件结构规范
   元数据验证: 对字节流进行语义分析,保证其符合Java语言规范
   字节码验证: 通过数据流和控制流分析,保证程序语义合法,符合逻辑,不危害JVM
3) 准备
   为静态变量在方法区分配内存,并初始化0值
4) 解析
   将符号引用转换为直接引用,也就是内存地址.
   JVM可以根据需要决定是在加载的时候还是在使用的时候进行解析.
5) 初始化
   执行类构造器,init()方法,为变量赋初值.该初值是程序员写的值,不是0;


2. 类加载器
    每一个类都必须和他的加载器一起才能确认其在JVM里面的唯一性,这会影响到equals()和isInstance()的返回结果.
    JVM的类加载器具有层次结构和继承关系
    顶层的加载器是启动类加载器,用于加载JDK的系统类库
    其次是扩展类加载器,用于加载扩展类库
    最后是应用程序类加载器,用于加载应用程序自己的类.

    用户可以继承应用程序类加载器,然后写自己的类加载器.

    每个类加载器都把加载请求发给其父类加载器,直到启动类加载器,如果父类无法加载,再分发给子类加载,这样就可以保证java对象       在JVM里面的层次关系.比如,object,只能由启动类加载器进行加载,这样才能保证其在JVM里面的唯一性.


==================================================================================================

运行时内存划分与方法调用
1. 内存划分
1) 程序计数器
   当前线程执行的字节码的行号指示器,也就是永远指向下一条需要执行的指令.线程独有,无OOM
2) Java方法机栈
   存放每个Java方法执行时的临时数据,每次方法的调用的数据就是一个栈贞,数据包括局部变量,操作数,动态链接和出口信息等.线程 

   独有,有OOM.
3) 本地方法栈
   与Java方法栈类似,存放本地方法执行时的临时数据.线程独有,有OOM.
4) Java堆
    存放Java对象实例,线程共享,有OOM
5) 方法区
    存放类信息,常量,静态变量,线程共享,有OOM

2. 方法的调用
   JVM给每个类都维护一个常量池,常量池位于方法区,在编译时就确定.包括以下内容:
   1) 类和接口名
   2) 方法名和描述符
   3) 属性名称和描述符

   String s = "adc"; 存于常量池
   String s = new String("abc");位于堆中

静态调用:
静态方法,私有方法,final()等不可重写的方法在类加载的时候就将符号引用转换为了直接引用,也就是内存地址,使用的时候直接调用即可.
动态调用(多态):
动态调用分为重载和重写
重载根据传入参数类型和个数确定调用的具体方法,主要用于构造函数.
重写方法的执行,首先需要确定调用者的实际类型,也就是具体的子类,然后根据实际类型将常量池中方法的符号引用转换为具体子类的直接引用.这就是多态的实现原理.简单点说,就是一个可重写的方法,在方法区中有对应的符号引用,在具体调用的时候,虚拟机先确定调用者的具体类型,然后将此符号引用转换为具体的直接引用.其本质原理就是在运行时根据不同的调用者将符号引用转换为对应的直接引用.
而具体的商用虚拟机实现一般都是在方法区中创建一个方法表,用于记录方法的入口地址,这样可以提高JVM的效率.

==================================================================================================

gc
1.对象生死裁决
1) 引用计数算法
   给每个对象设置一个引用计数器,每引用一次就+1,反之则-1.当引用计数器为0的时候就判断对象已死,可以回收.该算法无法解决循环引用问题,实际中并无使用.
2) 可达性分析
   首先设置一些GCRoot,凡是被这些GCRoot直接或间接引用的对象都判别为存活的,反之则是死亡对象.
   GCRoot包括:
   Java调用栈中使用的对象,也就是正在使用的对象
   方法区中静态属性引用的对象
   方法区中常量引用的对象
   JNI引用的对象

2.回收对象
JVM一般将内存划分为新生代和老年代,新生代存放生命周期短的对象,老年代存放生命周期长的对象.新生代对象在经历多次gc后进入老年代.虚拟机的回收策略是不同年代的对象使用不同的回收算法.
1) 标记清除算法
   首先标记未直接或间接被GCRoot引用的对象,然后进行清除操作.该算法效率较高,但会产生内存碎片.适合老年代.
2) 复制算法
   将内存划分为两块,对象内存的分配只在其中一块进行,在gc的时候,标记完后,就将所有未被标记的对象全部复制到另外一块内存空间依次
   存放.将之前的空间全部释放.该算法效率高,但会造成部分内存空间浪费.适合新生代.
   因为新生代的对象生命周期短,在gc时大多数对象已死亡,所以新生代的内存不需按照1:1进行划分.
   一般的商业虚拟机实现将内存分为一大二小3块内存,比如8:1:1的比例.平时使用其中的一大一小两块内存,在gc的时候,将被标记的对
   象拷贝到另外一块空闲的小内存中.然后清除之前使用的一大一小内存空间.如果小内存块不够空间,就需要老年
   代进行分配担保,即新生代小快内存无法存放的对象直接进入老年代.
3) 标记整理算法
   内存只有一块,标记完成后,将所有未被标记的对象全部移动到一端,依次存放.该算法无内存碎片,无空间浪费,但效率较低.适合老年代.


JVM在gc时对重写了finalize()方法对象的处理:
在对象被标记后,JVM会判断其是否重写了finalize()方法,如果重写了并且未执行过就将其加入到一个等待队列中,等待专门的Finalizer线程去调用其finalize()方法,如果在该方法中对象被重新引用,在下次gc时就不会被回收,否则会在下次gc时被回收.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值