JVM

来源:互联网 发布:pscad软件介绍 编辑:程序博客网 时间:2024/06/07 03:52

  其包含三个层面,抽象规范、规范的实现、运行的实例。
  抽象规范:jvm的实现规范,不一定要遵守,多数都会遵守。
  规范的实现:针对不同的硬件平台,根据规范已经编程实现的jvm,其有自己的指令集。
  运行的实例:正在运行的jvm实例,一个java应用对应一个实例,一个实例仅为一个java应用服务。
  
  jvm实例的生命周期,随应用的启动而开始,随应用程序的结束而结束,其使用系统创建进程的资源,运行在系统为该进程创建的内核级线程中,应用程序运行在jvm创建的用户级线程中。jvm中的线程分用户级线程和守护线程,初始用户级线程只有一个,为应用程序中的main方法对应的主线程,守护线程通常由jvm使用,如垃圾回收线程。
  守护线程的特点是当所有的用户级线程结束后,无论守护线程的语句是否执行完都会随之结束,jvm可将用户级线程标记为守护线程。

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

  java的内存分配就是jvm的内存分配,也称jvm运行时数据区。由os在物理内存中划分出一块区域,创建并加载jvm的实例,其会从该区域内模拟出pc寄存器、栈、堆、方法区、数据段、代码段。
  
  pc寄存器为jvm实例的每个用户级线程独有内存,用于记录该线程要执行的下一条指令的地址,速度快。
  
  栈为jvm实例的每个用户级线程独有内存,其成员由若干栈帧组成,多线程在对各自栈成员访问时不存在同步问题。栈帧用于存放线程正在执行非本地方法的执行状态,包括局部基本数据类型变量、局部引用类型变量、方法参数、返回值、运算中间结果,对本地方法,栈帧仅存放一个指向本地方法栈的引用,速度快。
  栈帧包含着线程执行方法的全部执行状态,每执行一个方法,就是创建一个对应该方法的栈帧并压入栈,方法执行结束后,将该方法的栈帧弹出栈。本地方法栈实际已经跳出jvm,与硬件平台有关。

// 栈使用享元模式来存放成员// 查询该线程的栈内存中是否有3,没有则开辟一个栈空间并存入3,同时让a指向该栈空间int a = 3;// 查询栈空间中是否有3,有则直接让b指向该栈空间int b = 3;// 查询栈空间是否有4,没有则开辟一个栈空间并存入4,同时让a指向该栈空间// a值的改变不是直接修改a所指向的栈空间的内容,而是重新指向另一个栈空间a = 4;



  堆为jvm所属的内核级线程独有的内存,可被该jvm实例的所有用户级线程共享,多线程对堆成员的访问存在同步问题,速度没有栈快,分实体区和常量池。
  无论是局部引用类型变量还是成员引用类型变量,它们指向的实体都存放在堆的实体区。实体区仅存放对象实体的成员基本数据类型变量和成员引用类型变量,不包括成员引用类型变量指向的实体和成员方法。
  常量池存放类、接口、方法的标识符声明,局部常量,成员常量,使用享元模式。
  
  方法区为jvm所属的内核级线程独有的内存,可被该jvm实例的所有用户级线程共享,仅存放实体的成员方法,不存放数据,速度没有栈快。
  
  数据段为jvm所属的内核级线程独有的内存,可被该jvm实例的所有用户级线程共享,存在同步问题,存放类的静态属性,速度没有栈快。
  
  代码段为jvm所属的内核级线程独有的内存,可被该jvm实例的所有用户级线程共享,代码段仅存放源代码,不存放数据,速度没有栈快。

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

  java的基本数据类型封装类Byte、Short、Integer、Long、Float、Double、Character、Boolean中,除了Float与Double,其余类型对-128—127之间的数据均使用常量池。

// s1、s2指向同一个空间,引用相同String s1 = “how are you”;String s2 = “how are you”;// s3、s4指向不同的实体区,引用不同String s3 = new String(“how are you”);String s4 = new String(“how are you”);// 内存中创建了两个对象实体,jvm检查常量池是否有abc,没有开辟空间存入abc,之后在实体区开辟空间存abcString s5 = new String(“abc”);// 内存中创建了三个对象实体,均在常量池中String s6 = “abc” + “d”;// 内存中创建了七个对象实体,均在常量池中,为a,b,c,d,ab,abc,abcd,关键是+号,+会产生额外的对象String s7 = “a” + “b” + “c” + “d”;public class Test {    // 成员基本数据类型变量i的值3存放在实体区    public int i = 3;    // 成员引用类型变量a的值存放在实体区,a指向的实体开辟自己的实体区存放    public A a = new A();    // 成员引用类型变量itg1的值存放在实体区,3在常量池    public Integer itg1 = 3;    // 成员引用变量itg2的值存放在实体区,itg2指向的实体开辟自己的实体区存放3    public Integer itg2 = new Integer(3);    // 成员引用类型变量itg3的值存放在实体区,128在-128—127外,itg3指向的实体使用自动类型封装,开辟自己的实体区存128    public Integer itg3 = 128;    public void Test() {        // 局部基本数据类型变量i的值3存放在栈内         int i = 3;        // 局部引用类型变量a的值存放在栈内,a指向的实体开辟自己的实体区存放        A a = new A();        // 局部引用类型变量s1的值存放在栈内,字符串常量how are you在常量池        String s1 = "how are you";        // 局部引用类型变量itg1的值存放在栈内,3使用常量池        Integer itg1 = 3;    }}

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

  jvm的垃圾回收器每隔一段时间,动态回收那些无任何对象引用的对象实体所占用的堆内存空间。可通过System.gc()或Runtime.getRuntime().gc()来显示地通知jvm进行一次垃圾回收,但jvm到底什么时候gc都不一定,我们无法控制,由其自己决定,有七种垃圾回收器。
  
  引用计数收集器:指实体区的每一个对象实体都维持一个引用计数器,用于记录指向该对象实体的引用个数。当一个对象实体的引用计数器值为0时,其可被当作垃圾回收,同时该对象内部引用的全部对象实体的引用计数器值都会减一,由此该方法中一个对象的回收可能会导致其它对象的回收行动。
  该方式引用计数器嵌入在程序中一起运行,可实时判断其值,快速收集,适于实时环境。但引用计数的增减会带来额外的开销,且无法处理循环引用。
  循环应用指,有两个对象a、b分别引用实体A、B,其中实体A内引用对象b’引用了实体B,实体B内引用对象a’引用了实体A。若实体A、B的引用计数均为2,令a = null,实体A不会被收集,因为B中a’引用A,除非B被收集,而b = null,不会导致B被收集,因为A中b’引用未被赋空,而实体A和B此时已经不能由程序员控制。

  跟踪收集器:分标记和清除两个阶段,标记阶段会从引用树的根对象开始遍历,标记遇到的每一个对象实体,清除阶段会释放未被标记的对象实体。
  gc时间快,但会增加内存碎片。
  
  压缩收集器:用于处理跟踪收集器中产生的堆碎块,将堆中未被收集的对象实体滑动到堆的一端,使另一端空出一个大的连续的空闲区域,对象实体移动后,对象实体的引用也要同时更新。使用一个中间句柄表,让表中的记录真正指向堆中的实体,引用指向表中的记录,由此对象实体的移动不会影响对象的引用,这就是java中只有引用,没有地址的原因。
  可有效减少内存碎片,但是增加gc时间。
  
  拷贝收集器:在遍历引用树的过程中,凡是被遍历到的对象实体都被同时拷贝到一个新的区域且在新区域连续存放,这样原区域的空闲碎片,以及未被遍历到的对象实体,都直接被视为空闲区域,一次遍历拷贝的过程就完成了标记与清除的全部过程,同时还可以清除堆碎块。此方法堆被分成两个区域,任何时候只能使用一个区域,当一个区域的空间耗尽时,停止分配,执行拷贝操作,又叫停止并拷贝。但需要两倍大小的实体区,因为一次只能使用一半的实体区。
  可同时减少内存碎片与gc时间,但会增加内存空间的消耗。
  
  按代收集器,将实体区分成若干子实体区,每个子实体区为一代,有一个年龄层。最年幼一代子实体区中对象被收集的频率最高,其存放的对象大多是生命周期很短的。该子实体区中的对象经过几次收集后,未被收集的对象被认为是生命周期较长的对象,被转移到年长的一代子实体区中,其中的对象生命周期大多较长,被执行垃圾回收的次数也会减少。

  自适应收集器:指根据上述几种垃圾收集器的适应场景,自适应的调整jvm的垃圾收集器。
  
  渐进式垃圾收集器:可避免一次收集全部的垃圾导致时间过长,影响系统性能,甚至被用户察觉到。一次只收集部分垃圾,每次收集时间限定在一定范围内,按代垃圾收集器就可实现渐进式收集。

  现今见得最多的一种垃圾收集思想,将整个堆内存分成年轻代、年老代、永久代三部分。
  年轻代分成eden、survivor1、survivor2三部分。大多数新创建的对象放在eden中,大对象由于年轻代空间不足可能会被分配到年老代。年轻代的gc频率高,空间小,对该层的gc称为young/minor gc,在无法为新对象分配空间时触发minor gc。eden中的对象至少熬过一次minor gc后,才有可能移动到survivor,只有survivor中的对象才有可能被移动到年老代。survivor1与survivor2同一时刻只能有一个起作用,用于拷贝收集器。
  年老代存放生命周期较长对象,年轻代survivor中的对象经过若干次minor gc后会被移动到此。年老代的gc次数少,空间比年轻代大,对该层的gc称为major gc。在年老代空间满,年轻代的对象无法移动到年老代时触发major gc,使用拷贝收集器。major gc的触发会触发full gc,对整个堆内存进行gc,但是各个层自己gc自己的,将full gc看为minor gc和major gc的总称。
  永久代存放java的类与静态文件,与gc关系不大,除非是动态生成的类。
  jdk默认客户端模式下使用串行gc,服务器模式下使用并行gc。

0 0
原创粉丝点击