码迷,mamicode.com
首页 > 编程语言 > 详细

JAVA虚拟机理解

时间:2020-03-14 20:23:28      阅读:70      评论:0      收藏:0      [点我收藏+]

标签:情况   循环   元数据   long   自带   类加载   影响   res   转换   

1、虚拟机运行时数据区域

1.1、运行时数据区

       JAVA虚拟机在执行JAVA程序过程中,会把他所管理的内存划分为若干个数据区域。

技术图片

 

 JAVA虚拟机运行时数据区

 1.2、程序计数器

         程序计数器可以看做是, 程序被执行时,内部字节码对应行号的指示器。这块空间很小,是线程私有的,也就是每个线程都有自己对应的程序计数器。程序在执行时,字节码解释器会通过改变计数器的值,来选取下一条需要执行的字节码。代码中分支、循环、跳转、异常、线程恢复等基础功能的操作都是由这个计数器来完成。

         JAVA虚拟机中的线程,并非一直在执行,而是会由CPU轮换分配进行处理,也就是说每个线程都在轮流等着CPU宠幸,而每次CPU只会宠幸一个线程一小会儿而已。那么线程在轮流等待CPU的时候,为了保证下次轮到自己能够正常的继续执行,就需要计数器帮忙记录当前代码执行的位置。而且为了保证每个线程相互没有影响,计数器会给每个线程建立一个属于自己的计数器。

          如果线程正在执行一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器的值则为 (Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

 

 1.3、虚拟机栈

          和计数器一样,栈(Stack)也是线程私有的,而且生命周期与线程是相同的,说白了就是线程执行结束,和这个线程先关的栈也就清理完了。

          栈的特点和队列很相似,队列QUEUE的特性先进先出(FIFI),还有一种队列是双端队列DEQUE,支持两端同时进出。

          双端队列的一种操作是从队里顶部进入、队列顶部出,也就是后进先出(LIFO),这种模式就是栈的模式。

          栈适用于管理程序运行的,程序每执行一个方法时,都会创建一个栈帧(StackFrame),这个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

          每个方法从调用到结束,就对应着一个栈栈在栈中的入栈出栈过程。所以说栈里面存放的栈帧。

          栈帧中的局部变量表中,存放的是一些编译期可知的基本数据类型(boolean、byte、int、short等)、对象引用、returnAddress(指向了一条字节码指令的地址),其中的对象引用就是类似于 String s = new String();中的s,实际的String是在堆中。

  

 1.3、堆

          对是虚拟机中内存最大的、所有线程都能共享访问的一块区域,这里存放着所有的对象实例、数组。堆中的内存在物理上是不连续的,但是逻辑上连续。

          堆通常被分为新生代、老年代、永久代(JDK1.8后成为元空间),

         1.3.1新生代

          新生代是一个对象刚刚被创建的地方,99%的对象在新生代创建后,随着使用完以后就能被回收掉,而逃逸掉的1%的对象会被转移到老年代中

          新生代还会被细分为Eden伊甸园区、幸存区suvivor0和suvivor1区,这三块区域的默认比例是8:1:1,Eden中的内存占大部分,在垃圾回收不掉时会转移到suvivor中

          如果一个对象被GC清理标记了15次还没有销毁,那么就会转移到老年代中去。这15次的的清理是指suvivor0和suvivor1之间的转移次数。

          suvivor被分为From和To两块区域,这两个区域是相互交换的,其中有一块必然是空的。如果suvivor0存在数据,清理完以后会把剩余的数据转移给suvivor1

          那么下次再清理时,如果suvivor1还有数据就会给suvivor0。所以判断那块是From那块是To,主要看内部是否存在数据,有数据就是From,没有数据就是To

         1.3.2老年代

           一个对象在新生代幸存了15次,说明引用一直存在无法被垃圾回收,那么就要被转移到老年代。这个块空间比新生代要更大,一般是新生代的一倍。

           如果这个区域的内存,超过了初始堆内存或者接近最大堆内存分配空间时,就会触发FULL GC,FULLGC会造成STW现象(Stop the World)。

 1.4、方法区

           属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

           java虚拟机将方法区描述为堆的一个逻辑部分,别名叫做Non-Heap,在JDK1.7前也被称之为永久代,在1.8以上成为元空间

           Class文件中存放类的版本、字段、方法、接口等信息,还有一个就是常量池,用于存放编译期间生成的各类字面量和符号引用。

           运行时常量池是方法区的一部分,永久代(元空间)的内存溢出,与常量池的增长也相关。

 1.5、本地方法栈

            本地方法栈与虚拟机栈发挥的作用相似,他们之间的区别不过是虚拟机栈执行java方法,而本地方法栈是虚拟机使用Native方法服务。

            本地方法就是操作系统提供的方法,一般来说都是C开发的本地方法。JAVA调用C的方法都是采用Native关键字类修饰的方法。

            JAVA调用C采用JNI方式 Java Native Interface

                       

2、类加载

          类从被加载到虚拟机内存开始,到卸载出内存,他的整个生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段

          其中验证、准备、解析被统一称为连接阶段。

          类被虚拟机加载的过程,就是加载、验证、准备、解析、初始化5个阶段,也可以说是加载、连接、初始化的过程。

         2.1加载

             加载过程,虚拟机将做3件事情:1、通过一个类的全限定名,来获取定义此类的二进制字节流;2、将这个字节流所代表的的静态存储的结构,转化为方法区的运行时数据结构。3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

        2.2连接

            连接包括验证、准备、解析三个阶段。

            连接是为了确保Class文件的字节流包含的信息,符合当前虚拟机的要求,不会危害虚拟机的整体运行。

            文件格式验证,比如文件版本号,是否是当前虚拟机的版本,常量池中的常量是否不被支持。

            元数据验证,比如类是否有父类,类是否继承了不被允许继承的父类(final修饰的)

            字节码验证,比如int类型的数据,不会按照long类型加载到本地变量表

            符号引入验证,比如符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。

        2.3准备 

            准备阶段是正式为类变量分配内存,并设置类变量初始值的过程,这些变量所以使用的内存都将在方法区中进行分配。

            1、此时的内存分配,仅包含类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时,随着对象一起分配在java堆中。

            2、初始值,是说数据类型的零值,假设public static int value=123;那么value在准备阶段后的初始化值是0,而不是123。

        2.4解析

            解析过程,是虚拟机常量池的符号引用,替换为直接引用的过程。

            在把java编译为class文件的时候,虚拟机并不知道所引用的地址;而是采用助记符,也就是符号引用,通过转为真正的直接引用,才能找到对应的直接地址。

        2.5初始化

public class Test{
    public static int a = 1;
}
// 1、加载   编译文件为 .class 文件,通过类加载,加载到JVM

// 2、连接   
      验证(1)  保证Class类文件没有问题
      准备(2)  给int类型分配内存空间,a = 0;
      解析(3)  符号引用转换为直接引用

// 3、初始化
      经过这个阶段的解析,把1 赋值给 变量 a;

 

 

 

3、类加载器

        类加载器分为虚拟机提供的类加载器和用户自定义的类加载器

      1、java虚拟机自带的加载器

  • BootStrap 根加载器 (加载系统的包,JDK 核心库中的类 rt.jar)

  • Ext 扩展类加载器 (加载一些扩展jar包中的类)

  • Sys/App 系统(应用类)加载器 (我们自己编写的类)

      2、用户自己定义的加载器

  • ClassLoader,只需要继承这个抽象类即可,自定义自己的类加载器

       虚拟机在加载类的时候,采用双亲委派加载方式,也就是说加载的类,都让父类去加载,如果父类加载了就不进行加载,如果父类不负责加载这个类就再交给子加载器进行加载。

       双亲委派机制 可以保护java的核心类不会被自己定义的类所替代,一层一层的让父类去加载,如果顶层的加载器不能加载,然后再向下类推。

       boot根记载器加载的是jre运行环境最底层的包rt.jar;Ext扩展加载器,只加载ext扩展包下面的jar;sys系统加载器才会加载应用系统开发的类。

 

4、GC垃圾回收

         4.1垃圾回收算法

           1、引用计数法

            每个对象都有一个引用计数器,每当对象被引用一次,计数器就+1,如果引用失效,则计数器-1,如果为0,则GC可以清理;

           这种算法在JVM中不用,因为计数器维护麻烦,对于循环引用的方式无法处理,会造成内存无法回收

技术图片

 

 

         4.2复制算法

           复制算法,就是将一个区的全部无法回收的对象,全部复制到另一个区域中,然后再清空原来的这片空间。

           优点是速度快,没有碎片空间,但是缺点是浪费内存,需要两块等同的空间。

技术图片

 

 

          4.3标记清除算法

            通过2次扫描进行清理

           扫描第一次,就会对活着的对象进行标记

           扫描第二次,回收没有被标记的对象

           优点是不需要额外的空间,但缺点时两次扫描,耗时较为严重,产生内存碎片,不连续

技术图片

 

 

          4.4标记整理算法

           标记整理算法是在标记清除算法的基础上,再次进行扫描,将活着的对象在向左移动。

           优点是没有内存碎片,缺点是耗时非常严重。老年代采用这类算法,不经常发生GC,那么可以考虑这个算法

技术图片

 

 

 

5、GC-ROOT

         垃圾回收时,是通过GC-ROOT可达性分析来判断一个对象是否能够被回收。

         GC-ROOT类似于一棵数,所有的对象如果能够到根节点说明依然被引用,反之说明不会被根节点引用可以清理

技术图片

 

 

 

        能够作为GC-ROOT根节点,一般是下面4种对象

       1、虚拟机栈中引用的对象

       2、类中静态属性引用的对象

       3、方法区中的常量

       4、本地方法栈中Native方法引用的对象

 

JAVA虚拟机理解

标签:情况   循环   元数据   long   自带   类加载   影响   res   转换   

原文地址:https://www.cnblogs.com/worryrock/p/12492681.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!