精华内容
下载资源
问答
  • java虚拟机中方法区作用

    千次阅读 2015-03-31 23:04:08
    随着对java的进一步挖掘,深入到java虚拟机的时候,我才发现原来以前学的那些莫名其妙的方法终于有了合理的解释。 比如main()?我们俗称为主函数,作为程序初始线程的七点,同时将告知虚拟机为列在常量池第一项的...

    随着对java的进一步挖掘,深入到java虚拟机的时候,我才发现原来以前学的那些莫名其妙的方法终于有了合理的解释。

    比如main()?我们俗称为主函数,作为程序初始线程的七点,同时将告知虚拟机为列在常量池第一项的类分配足够的内存。属于非守护线程。

    守护线程通畅有虚拟机自己使用的线程,比如垃圾收集任务的线程。

    下面我们用一个例子来分析一下,一个程序的运行,背后的虚拟机是怎么作用的。

    class T{

     private int speed =5;

    void flow(){

       }

    }

    class M{

    public static void main(String[] args){

    T t=new T();

    t.flow();

    }

    }

    按照之前的理解,这不就是很简单嘛,M类引用了T类然后调用T的方法。so easy!!

    但是真是这样简单的吗?里面的引用是怎么引用的? M类怎么知道T类的存在的?难道M是先知吗?

    事实是这样的:

         运行M程序时,首相以某种“依赖于实现的”方式告诉虚拟机“M”这个名字,然后虚拟机会找到相对应的M.class文件并且将文件里面的二进制数据提取类型的信息放在方法区中。虚拟机开始执行main()方法,它会一直持有指向当前类(M类)的常量池(方法区中的一个数据结构)的指针。(注意:虚拟机执行M类的main()方法的字节码时,尽管T类没被装载,其实是使用懒加载模式。)main方法的第一条指令就是告知虚拟机为列在常量池第一项的类分配足够的内存。所以虚拟机使用指向M类常量池的指针找到第一项,发现它是一个对T类的符号引用(T t=new T();)。然后它就检查方法区看看T类有没有被装载。

        当发现虚拟机还没有装载过名为“T”的类时,它就开始查找并装载文件“T.class”,并把从读入的二进制数据中提取的类型信息放在方法区中。接着,虚拟机以一个直接指向方法区的M类数据的指针替换常量池第一项(就是那个字符串“T”)-借助这个指针可以快速访问T类。这个替换过程叫做常量池解析,即把常量池中的符号引用替换成直接引用。

        终于,虚拟机准备为新的T对象分配内存,虚拟机借助在常量池第一项的指针访问到T类型信息,这样可以确定T对象需要分配多少堆空间。当虚拟机确定T对象的大小后,它就在对上分配这么大的空间,并且把这个对象实例speed初始化为默认初始值0,当把新生的T对象的引用压到栈中,main()方法的第一条指令也完成了。。。坑爹啊!!接下来通过这个引用调用java代码(将代码中的speed变量初始化为正确初始值5),另外一条指令将用这个引用调用T对象的flow()方法。

      ---------------------------------------------整个过程,相对简略一点,如果想详细了解,请看深入java虚拟机---------------------------------------

    展开全文
  • java虚拟机 jvm 方法区实战

    千次阅读 2016-04-03 07:23:46
    方法区也有一块内存区域所以方法区的内存大小,决定了系统可以包含多少个类,如果系统类太多,方法区内存不够肯定会导致方法区溢出,虚拟机同样会抛出内存溢出信息。(内存溢出后面相关文章给大家总结) jdk6和jdk7...

    java堆一样,方法区是一块所有线程共享的内存区域,用于保存系统的类信息,类的信息有哪些呢。字段、方法、常量池。方法区也有一块内存区域所以方法区的内存大小,决定了系统可以包含多少个类,如果系统类太多,方法区内存不够肯定会导致方法区溢出,虚拟机同样会抛出内存溢出信息。(内存溢出后面相关文章给大家总结)

    jdk6jdk7中,方法区可以理解为永久区(Perm).永久区可以使用参数-XX:PermSize-XX:MaxPermSize制定。默认情况下-XX:MaxPermSize64MB.如果你项目中使用代理模式或者CGLIB的话可能在运行的时候生成大量的类,如果这样,需要设置一下永久区的大小,防止永久区内存溢出。

    CGLIB会在后面专门的章节和代理模式一起讲解。(这个系列专注的是JVM的讲解)

    使用下面代码:

    for (int i = 0; i <10000; i++) {
    CglibWapper c=new CglibWapper("cn.springok.perm"+i)
    }


    代码解释:会根据传入的参数动态生成一个类以及类的实例。因为对象实例化,类的字段、方法、常量池保存在方法区,因此操作会占用一定内存的。

    大量的类可能导致方法区溢出,使用下面的参数运行代码:

    -XX:PermSize=10M  -XX:MaxPermSize=10M -XX:PrintGCDetails

    参数说明:

    -XX:PermSize=10M  初始永久区大小10M

    -XX:MaxPermSize 方法区最大内存10M

    -XX:PrintGCDetails 打印日志详情。

    执行程序部分输出如下:

     compacting perm gen  total 86272K, used 86136K [0x44600000, 0x49a40000, 0x64600000) 

       the space 86272K,  99% used [0x44600000, 0x49a1e2f8, 0x49a1e400, 0x49a40000) 

    系统内存溢出了,扩大-XX:MaxPermSize值,可以生成更多的类。

    可以使用工具Visual VM观察方法区的具体使用情况。

     

    需要注意一点:

    jdk8中永久区被移除了,取而代之的是元数据区,可能方法区依赖jvm的内存吧。元数据区可以使用-XX:MaxMetaspaceSize制定,跟之前版本的-XX:MaxPermSize一样,分配的值越多,就可以支持更多的类。不同的是元数据区是堆外直接内存,与方法永久区不同,在不指定大小的情况下,虚拟机会耗尽所有可用的系统内存。

    元数据区发生溢出,虚拟机一样抛出异常,如下:

    java.lang.OutOfMemoryError Metaspace

                       

    展开全文
  • Java虚拟机方法区

    千次阅读 2013-01-28 17:18:20
    Java虚拟机的加载子系统在加载一个类型(类或接口)的时候,主要完成以下三件事: 由一个类型的全限定名查找对应的二进制流(可能class文件,也可能是数据库的二进制或来自网络的字节流)根据二进制流转为...

    Java虚拟机的加载子系统在加载一个类型(类或接口)的时候,主要完成以下三件事:

    • 由一个类型的全限定名查找对应的二进制流(可能class文件,也可能是数据库中的二进制或来自网络的字节流)
    • 根据二进制流转为虚拟机方法区中的运行时数据结构
    • 在Java堆中生成代表该类型的java.lang.Class对象,作为方法区类型数据的访问入口。
    接下来就详细说说方法区中的运行时数据结构具体包括哪一些。在虚拟机规范中,对于方法区的规定十分抽象,因此跟具体的实现有很大的关系。但是一般都具有一些共同的部分。由于虚拟机的方法区使线程共享的,因此访问这些数据必须是线程安全的。根据《深入Java虚拟机》(Bill Venners)的描述,方法区中的数据包括以下内容:

    类型信息(Type information)

    • 类型的全限定名(class文件中的this_class)
    • 类型的直接超类的全限定名(class文件中的super_class),Object类型除外。
    • 直接超接口的全限定名列表(对应interfaces表集合)
    • 该类型是类类型还是接口类型。
    • 类型的访问修饰符(如public、abstract、final等,对应access_flags)
    其中的全限定名把Java源码中的“ . ”换成“ /  ”

    类型常量池

    常量池中存放的是该类型所用到的常量的有序集合,包括直接常量(CONSTANT_String、CONSTANT_Integer等)和对其他类型、字段和方法的符号引用(symbolic reference),这个常量池像数组一样通过索引来访问,在动态连接中起着核心作用。如果用javap  -verbose工具,可以得到虚拟机指令的“汇编程序”,可以看到指令中会用#后跟索引来表示对常量池的访问。例如getstatic #9。

    字段信息

    注意区别常量池中的字段引用与这里的字段信息。字段信息,是指该类中声明的所有字段(包括 类级变量和实例变量,不包括局部变量)的描述,如字段名称、字段修饰符等。
    具体如下:
    • 字段名
    • 字段的类型(可能是基本类型或引用类型)
    • 字段的修饰符(public、static、transient等)
    这些信息可以从class字节流中的fields_info中找到。注意的是,字段的顺序也需要保留,当然还可能包含其他一些信息。

    方法信息

    和字段很类似的,包括:
    • 方法名
    • 方法返回类型
    • 方法参数的个数和类型、顺序等
    • 方法的修饰符。
    方法的顺序也是需要保存的,除此以外,如果方法不是抽象或本地的,还必须保存:
    • 方法的字节码(bytecodes)
    • 操作数栈和该方法在栈帧中的局部变量区的大小等。
    • 异常表
    这些信息可以造class字节流的method_ref中找到。

    类变量

    由于类变量时所有对象共享的,因此并不保存在堆栈中,而是保存在方法区中。即使没有任何实例对象,也可以访问这些类变量。这些类变量只与类挂钩。除了在类中声明的编译时常量外,虚拟机在使用某个类之前,必须为这些类变量分配内存空间(在连接过程的准备阶段,会为类变量分配内存并设置为默认值(而不是初值))。对于编译时常量,则直接将其复制到使用它们(编译时常量)的类的常量池当中,或者作为字节码流的一部分。 比如,类C声明了一个final static(即编译时常量)的字段field1和static的field2,在D类和E类中用到了field1,则在类D和类E的常量池中,都会保存有field1的副本。而field2则保存在类C的方法区当中。【英文原文: non-final class variables are stored as part of the data for the type that declares them, final class variables are stored as part of the data for any type thatuses them. 】

    指向类加载器的引用:(A Reference to Class ClassLoader)

    一个类可以被启动类加载器或者自定义的类加载器加载,如果一个类被某个自定义类加载器的对象(实例)加载,则方法区中必须保存对该对象的引用。例如,我们自己定义了一个类加载器GreeterClassLoader,并实例化一个实例myLoader,然后用myLoader加载类类C,则在C的方法区中,必须保存对myLoader的引用。这样,当类C引用了类D并且类D还未被加载时,虚拟机就会请求myLoader对象来加载类型D。

    指向Class实例的引用

    在加载过程中,虚拟机会创建一个代表该类型的Class对象,方法区中必须保存对该对象的引用。可以通过Class类的forName静态方法来得到该Class对象。例如Class.forName("java.lang.Thread")将会返回一个代表Thread类的Class对象。但是如果虚拟机无法将Class类加载到当前的命名空间,则会抛出ClassNotFoundException。另以后总方式可以得到Class对象的引用,例如我有一个Object的对象实例obj,则直接调用obj.getClass()就可以得到一个代表Object类的Class对象的引用。通过该引用,就可以获取一些类型信息,例如getName、getSuperClass等。

    方法表

    方法表是为了提高访问效率的,并不一定包含这一项。虚拟机可能会对每个装载的非抽象类,都声称一个方法表,作为一部分类型信息保存在方法区中。方法表示一个数组,每个数组元素是实例可能调用的方法的直接引用(指向方法信息),包括从超类继承来的方法。而对于抽象类和接口,保存方法表则毫无用处,因为不可能实例化一个抽象类或者接口。运行的时候就可以通过方法表快速搜寻对象调用的方法(常量池中的方法引用在解析的时候被解析成方法表索引??)。

    区别字段引用和字段信息、方法引用与方法信息很重要。

    下面是一个具体的实例,来说明方法区中的信息:

    class Lava {
    	private int speed = 5;
    
    	void flow() {
    		System.out.println("I am flowing 5 kilometers per hour");
    	}
    }
    
    public class Volcano {
    	public static void main(String[] args) {
    		Lava lava = new Lava();
    		lava.flow();
    
    	}
    
    }

    首先必须告诉虚拟机初始类的名字是Volcano,例如你在命令行输入:java Volcano。虚拟机找到并读入想要的class文件Volcano.class。然从这个二进制流中提取类型信息并放到方法区中,经过一些诸如验证、准备等过程后,虚拟机开始执行main()方法,运行时,虚拟机持有指向当前类的常量池的指针。需要注意的是,开始运行main()方法的字节码时, Lava类还没有装载。虚拟机至于在需要的时候才装载相应的类。main方法虚拟机指令如下:



    第一条“汇编指令” new  #16 告诉虚拟机为常量池的第16项分配好足够的内存,所以虚拟机通过指向常量池的指针找到第16项,发现它是一个对Lava类的符号引用,接着检查方法区,看Lava是否已经被装载。发现哈没有装载后,它就开始查找Lava.class的文件,并且读入到方法区中。读入之后,就将16号符号引用替换为直接引用(本地指针、地址),以后就可以通过这个指针快速访问Lava类。这个过程也被称为常量池解析。

    现在,虚拟机准备为一个Lava对象分配内存了,通过第16项的直接引用,可以访问Lava类型信息,然后计算出一个Lava独享需要多少内存。OK,将new指令生成的lava对象引用压入栈顶。然后通过这个引用调用invokespecial #18,这是对象的初始化方法<init>,将speed赋值为5. astore_1指令将lava对象引用存入到局部变量表的1号slot。 aload_1将引用压入栈顶,然后执行 invokevirtual #19,即调用lava对象的flow方法。最后返回,结束main方法。

    方法区使很重要的内存区域,需要在以后不断总结。

    展开全文
  • Java虚拟机(JVM)你只要看这一篇就够了!

    万次阅读 多人点赞 2018-08-14 12:55:02
    根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存如下图所示。 1.1.1 程序计数器 内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节...

    本文是学习了《深入理解Java虚拟机》之后的总结,主要内容都来自于书中,也有作者的一些理解。一是为了梳理知识点,归纳总结,二是为了分享交流,如有错误之处还望指出。

    用XMind画了一张导图(源文件对部分节点有详细备注和参考资料,需要的朋友可以关注我的微信公众号:Java团长,然后回复“JVM”获取):

    1. Java 内存区域与内存溢出异常

    1.1 运行时数据区域

    根据《Java 虚拟机规范(Java SE 7 版)》规定,Java 虚拟机所管理的内存如下图所示。

     

    1.1.1 程序计数器

    内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成

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

    1.1.2 Java 虚拟机栈

    线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

    局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)

    StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
    OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。

    1.1.3 本地方法栈

    区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。

    1.1.4 Java 堆

    对于绝大多数应用来说,这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续。

    OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。

    1.1.5 方法区

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

    现在用一张图来介绍每个区域存储的内容。

     

    1.1.6 运行时常量池

    属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。

    1.1.7 直接内存

    非虚拟机运行时数据区的部分

    在 JDK 1.4 中新加入 NIO (New Input/Output) 类,引入了一种基于通道(Channel)和缓存(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。可以避免在 Java 堆和 Native 堆中来回的数据耗时操作。
    OutOfMemoryError:会受到本机内存限制,如果内存区域总和大于物理内存限制从而导致动态扩展时出现该异常。

    1.2 HotSpot 虚拟机对象探秘

    主要介绍数据是如何创建、如何布局以及如何访问的。

    1.2.1 对象的创建

    创建过程比较复杂,建议看书了解,这里提供个人的总结。

    遇到 new 指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过。如果没有,执行相应的类加载。

    类加载检查通过之后,为新对象分配内存(内存大小在类加载完成后便可确认)。在堆的空闲内存中划分一块区域(‘指针碰撞-内存规整’或‘空闲列表-内存交错’的分配方式)。

    前面讲的每个线程在堆中都会有私有的分配缓冲区(TLAB),这样可以很大程度避免在并发情况下频繁创建对象造成的线程不安全。

    内存空间分配完成后会初始化为 0(不包括对象头),接下来就是填充对象头,把对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息存入对象头。

    执行 new 指令后执行 init 方法后才算一份真正可用的对象创建完成。

    1.2.2 对象的内存布局

    在 HotSpot 虚拟机中,分为 3 块区域:对象头(Header)实例数据(Instance Data)对齐填充(Padding)

    对象头(Header):包含两部分,第一部分用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32 位虚拟机占 32 bit,64 位虚拟机占 64 bit。官方称为 ‘Mark Word’。第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例。另外,如果是 Java 数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以。

    实例数据(Instance Data):程序代码中所定义的各种类型的字段内容(包含父类继承下来的和子类中定义的)。

    对齐填充(Padding):不是必然需要,主要是占位,保证对象大小是某个字节的整数倍。

    1.2.3 对象的访问定位

    使用对象时,通过栈上的 reference 数据来操作堆上的具体对象。

    通过句柄访问

    Java 堆中会分配一块内存作为句柄池。reference 存储的是句柄地址。详情见图。

     

    使用直接指针访问

    reference 中直接存储对象地址

     

    比较:使用句柄的最大好处是 reference 中存储的是稳定的句柄地址,在对象移动(GC)是只改变实例数据指针地址,reference 自身不需要修改。直接指针访问的最大好处是速度快,节省了一次指针定位的时间开销。如果是对象频繁 GC 那么句柄方法好,如果是对象频繁访问则直接指针访问好。

    1.3 实战

    // 待填

    2. 垃圾回收器与内存分配策略

    2.1 概述

    程序计数器、虚拟机栈、本地方法栈 3 个区域随线程生灭(因为是线程私有),栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。而 Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期才知道那些对象会创建,这部分内存的分配和回收都是动态的,垃圾回收期所关注的就是这部分内存。

    2.2 对象已死吗?

    在进行内存回收之前要做的事情就是判断那些对象是‘死’的,哪些是‘活’的。

    2.2.1 引用计数法

    给对象添加一个引用计数器。但是难以解决循环引用问题。

     

    从图中可以看出,如果不下小心直接把 Obj1-reference 和 Obj2-reference 置 null。则在 Java 堆当中的两块内存依然保持着互相引用无法回收。

    2.2.2 可达性分析法

    通过一系列的 ‘GC Roots’ 的对象作为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用。

     

    可作为 GC Roots 的对象:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象

    2.2.3 再谈引用

    前面的两种方式判断存活时都与‘引用’有关。但是 JDK 1.2 之后,引用概念进行了扩充,下面具体介绍。

    下面四种引用强度一次逐渐减弱

    强引用

    类似于 Object obj = new Object(); 创建的,只要强引用在就不回收。

    软引用

    SoftReference 类实现软引用。在系统要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。

    弱引用

    WeakReference 类实现弱引用。对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时,无论内存是否足够都会回收掉只被弱引用关联的对象。

    虚引用

    PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

    2.2.4 生存还是死亡

    即使在可达性分析算法中不可达的对象,也并非是“facebook”的,这时候它们暂时出于“缓刑”阶段,一个对象的真正死亡至少要经历两次标记过程:如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

    如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象简历关联即可。

    finalize() 方法只会被系统自动调用一次。

    2.2.5 回收方法区

    在堆中,尤其是在新生代中,一次垃圾回收一般可以回收 70% ~ 95% 的空间,而永久代的垃圾收集效率远低于此。

    永久代垃圾回收主要两部分内容:废弃的常量和无用的类。

    判断废弃常量:一般是判断没有该常量的引用。

    判断无用的类:要以下三个条件都满足

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

    2.3 垃圾回收算法

    仅提供思路

    2.3.1 标记 —— 清除算法

    直接标记清除就可。

    两个不足:

    • 效率不高
    • 空间会产生大量碎片

    2.3.2 复制算法

    把空间分成两块,每次只对其中一块进行 GC。当这块内存使用完时,就将还存活的对象复制到另一块上面。

    解决前一种方法的不足,但是会造成空间利用率低下。因为大多数新生代对象都不会熬过第一次 GC。所以没必要 1 : 1 划分空间。可以分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 上,最后清理 Eden 和 Survivor 空间。大小比例一般是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。但是这里有一个问题就是如果存活的大于 10% 怎么办?这里采用一种分配担保策略:多出来的对象直接进入老年代。

    2.3.3 标记-整理算法

    不同于针对新生代的复制算法,针对老年代的特点,创建该算法。主要是把存活对象移到内存的一端。

    2.3.4 分代回收

    根据存活对象划分几块内存区,一般是分为新生代和老年代。然后根据各个年代的特点制定相应的回收算法。

    新生代

    每次垃圾回收都有大量对象死去,只有少量存活,选用复制算法比较合理。

    老年代

    老年代中对象存活率较高、没有额外的空间分配对它进行担保。所以必须使用 标记 —— 清除 或者 标记 —— 整理 算法回收。

    2.4 HotSpot 的算法实现

    // 待填

    2.5 垃圾回收器

    收集算法是内存回收的理论,而垃圾回收器是内存回收的实践。

     

    说明:如果两个收集器之间存在连线说明他们之间可以搭配使用。

    2.5.1 Serial 收集器

    这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束。

     

    2.5.2 ParNew 收集器

    可以认为是 Serial 收集器的多线程版本。

     

    并行:Parallel

    指多条垃圾收集线程并行工作,此时用户线程处于等待状态

    并发:Concurrent

    指用户线程和垃圾回收线程同时执行(不一定是并行,有可能是交叉执行),用户进程在运行,而垃圾回收线程在另一个 CPU 上运行。

    2.5.3 Parallel Scavenge 收集器

    这是一个新生代收集器,也是使用复制算法实现,同时也是并行的多线程收集器。

    CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程所停顿的时间,而 Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。

    作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)。

    2.5.4 Serial Old 收集器

    收集器的老年代版本,单线程,使用 标记 —— 整理

     

    2.5.5 Parallel Old 收集器

    Parallel Old 是 Parallel Scavenge 收集器的老年代版本。多线程,使用 标记 —— 整理

     

    2.5.6 CMS 收集器

    CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器。基于 标记 —— 清除 算法实现。

    运作步骤:

    1. 初始标记(CMS initial mark):标记 GC Roots 能直接关联到的对象
    2. 并发标记(CMS concurrent mark):进行 GC Roots Tracing
    3. 重新标记(CMS remark):修正并发标记期间的变动部分
    4. 并发清除(CMS concurrent sweep)

     

    缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记 —— 清除 算法带来的空间碎片

    2.5.7 G1 收集器

    面向服务端的垃圾回收器。

    优点:并行与并发、分代收集、空间整合、可预测停顿。

    运作步骤:

    1. 初始标记(Initial Marking)
    2. 并发标记(Concurrent Marking)
    3. 最终标记(Final Marking)
    4. 筛选回收(Live Data Counting and Evacuation)

     

    2.6 内存分配与回收策略

    2.6.1 对象优先在 Eden 分配

    对象主要分配在新生代的 Eden 区上,如果启动了本地线程分配缓冲区,将线程优先在 (TLAB) 上分配。少数情况会直接分配在老年代中。

    一般来说 Java 堆的内存模型如下图所示:
     

     

    新生代 GC (Minor GC)

    发生在新生代的垃圾回收动作,频繁,速度快。

    老年代 GC (Major GC / Full GC)

    发生在老年代的垃圾回收动作,出现了 Major GC 经常会伴随至少一次 Minor GC(非绝对)。Major GC 的速度一般会比 Minor GC 慢十倍以上。

    2.6.2 大对象直接进入老年代

    2.6.3 长期存活的对象将进入老年代

    2.6.4 动态对象年龄判定

    2.6.5 空间分配担保

    3. Java 内存模型与线程

     

    3.1 Java 内存模型

    屏蔽掉各种硬件和操作系统的内存访问差异。

     

    3.1.1 主内存和工作内存之间的交互

    操作作用对象解释
    lock主内存把一个变量标识为一条线程独占的状态
    unlock主内存把一个处于锁定状态的变量释放出来,释放后才可被其他线程锁定
    read主内存把一个变量的值从主内存传输到线程工作内存中,以便 load 操作使用
    load工作内存把 read 操作从主内存中得到的变量值放入工作内存中
    use工作内存把工作内存中一个变量的值传递给执行引擎,
    每当虚拟机遇到一个需要使用到变量值的字节码指令时将会执行这个操作
    assign工作内存把一个从执行引擎接收到的值赋接收到的值赋给工作内存的变量,
    每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
    store工作内存把工作内存中的一个变量的值传送到主内存中,以便 write 操作
    write工作内存把 store 操作从工作内存中得到的变量的值放入主内存的变量中

    3.1.2 对于 volatile 型变量的特殊规则

    关键字 volatile 是 Java 虚拟机提供的最轻量级的同步机制。

    一个变量被定义为 volatile 的特性:

    1. 保证此变量对所有线程的可见性。但是操作并非原子操作,并发情况下不安全。

    如果不符合 运算结果并不依赖变量当前值,或者能够确保只有单一的线程修改变量的值变量不需要与其他的状态变量共同参与不变约束 就要通过加锁(使用 synchronize 或 java.util.concurrent 中的原子类)来保证原子性。

    1. 禁止指令重排序优化。

    通过插入内存屏障保证一致性。

    3.1.3 对于 long 和 double 型变量的特殊规则

    Java 要求对于主内存和工作内存之间的八个操作都是原子性的,但是对于 64 位的数据类型,有一条宽松的规定:允许虚拟机将没有被 volatile 修饰的 64 位数据的读写操作划分为两次 32 位的操作来进行,即允许虚拟机实现选择可以不保证 64 位数据类型的 load、store、read 和 write 这 4 个操作的原子性。这就是 long 和 double 的非原子性协定。

    3.1.4 原子性、可见性与有序性

    回顾下并发下应该注意操作的那些特性是什么,同时加深理解。

    • 原子性(Atomicity)

    由 Java 内存模型来直接保证的原子性变量操作包括 read、load、assign、use、store 和 write。大致可以认为基本数据类型的操作是原子性的。同时 lock 和 unlock 可以保证更大范围操作的原子性。而 synchronize 同步块操作的原子性是用更高层次的字节码指令 monitorenter 和 monitorexit 来隐式操作的。

    • 可见性(Visibility)

    是指当一个线程修改了共享变量的值,其他线程也能够立即得知这个通知。主要操作细节就是修改值后将值同步至主内存(volatile 值使用前都会从主内存刷新),除了 volatile 还有 synchronize 和 final 可以保证可见性。同步块的可见性是由“对一个变量执行 unlock 操作之前,必须先把此变量同步会主内存中( store、write 操作)”这条规则获得。而 final 可见性是指:被 final 修饰的字段在构造器中一旦完成,并且构造器没有把 “this” 的引用传递出去( this 引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那在其他线程中就能看见 final 字段的值。

    • 有序性(Ordering)

    如果在被线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句指“线程内表现为串行的语义”,后半句是指“指令重排”现象和“工作内存与主内存同步延迟”现象。Java 语言通过 volatile 和 synchronize 两个关键字来保证线程之间操作的有序性。volatile 自身就禁止指令重排,而 synchronize 则是由“一个变量在同一时刻指允许一条线程对其进行 lock 操作”这条规则获得,这条规则决定了持有同一个锁的两个同步块只能串行的进入。

    3.1.5 先行发生原则

    也就是 happens-before 原则。这个原则是判断数据是否存在竞争、线程是否安全的主要依据。先行发生是 Java 内存模型中定义的两项操作之间的偏序关系。

    天然的先行发生关系

    规则解释
    程序次序规则在一个线程内,代码按照书写的控制流顺序执行
    管程锁定规则一个 unlock 操作先行发生于后面对同一个锁的 lock 操作
    volatile 变量规则volatile 变量的写操作先行发生于后面对这个变量的读操作
    线程启动规则Thread 对象的 start() 方法先行发生于此线程的每一个动作
    线程终止规则线程中所有的操作都先行发生于对此线程的终止检测
    (通过 Thread.join() 方法结束、 Thread.isAlive() 的返回值检测)
    线程中断规则对线程 interrupt() 方法调用优先发生于被中断线程的代码检测到中断事件的发生
    (通过 Thread.interrupted() 方法检测)
    对象终结规则一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始
    传递性如果操作 A 先于 操作 B 发生,操作 B 先于 操作 C 发生,那么操作 A 先于 操作 C

    3.2 Java 与线程

    3.2.1 线程的实现

    使用内核线程实现

    直接由操作系统内核支持的线程,这种线程由内核完成切换。程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口 —— 轻量级进程(LWP),轻量级进程就是我们通常意义上所讲的线程,每个轻量级进程都有一个内核级线程支持。

     

    使用用户线程实现

    广义上来说,只要不是内核线程就可以认为是用户线程,因此可以认为轻量级进程也属于用户线程。狭义上说是完全建立在用户空间的线程库上的并且内核系统不可感知的。

     

    使用用户线程夹加轻量级进程混合实现

    直接看图

     

    Java 线程实现

    平台不同实现方式不同,可以认为是一条 Java 线程映射到一条轻量级进程。

    3.2.2 Java 线程调度

    协同式线程调度

    线程执行时间由线程自身控制,实现简单,切换线程自己可知,所以基本没有线程同步问题。坏处是执行时间不可控,容易阻塞。

    抢占式线程调度

    每个线程由系统来分配执行时间。

    3.2.3 状态转换

    五种状态:

    • 新建(new)

    创建后尚未启动的线程。

    • 运行(Runable)

    Runable 包括了操作系统线程状态中的 Running 和 Ready,也就是出于此状态的线程有可能正在执行,也有可能正在等待 CPU 为他分配时间。

    • 无限期等待(Waiting)

    出于这种状态的线程不会被 CPU 分配时间,它们要等其他线程显示的唤醒。

    以下方法会然线程进入无限期等待状态:
    1.没有设置 Timeout 参数的 Object.wait() 方法。
    2.没有设置 Timeout 参数的 Thread.join() 方法。
    3.LookSupport.park() 方法。

    • 限期等待(Timed Waiting)

    处于这种状态的线程也不会分配时间,不过无需等待配其他线程显示地唤醒,在一定时间后他们会由系统自动唤醒。

    以下方法会让线程进入限期等待状态:
    1.Thread.sleep() 方法。
    2.设置了 Timeout 参数的 Object.wait() 方法。
    3.设置了 Timeout 参数的 Thread.join() 方法。
    4.LockSupport.parkNanos() 方法。
    5.LockSupport.parkUntil() 方法。

    • 阻塞(Blocked)

    线程被阻塞了,“阻塞状态”和“等待状态”的区别是:“阻塞状态”在等待着获取一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。

    • 结束(Terminated)

    已终止线程的线程状态。

     

    4. 线程安全与锁优化

    // 待填

    5. 类文件结构

    // 待填

    有点懒了。。。先贴几个网址吧。

    1. Official:The class File Format
    2.亦山: 《Java虚拟机原理图解》 1.1、class文件基本组织结构

    6. 虚拟机类加载机制

    虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验、装换解析和初始化,最终形成可以被虚拟机直接使用的 Java 类型。

    在 Java 语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。

    6.1 类加载时机

    类的生命周期( 7 个阶段)

     

    其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的。解析阶段可以在初始化之后再开始(运行时绑定或动态绑定或晚期绑定)。

    以下五种情况必须对类进行初始化(而加载、验证、准备自然需要在此之前完成):

    1. 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令时没初始化触发初始化。使用场景:使用 new 关键字实例化对象、读取一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)、调用一个类的静态方法。
    2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候。
    3. 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需先触发其父类的初始化。
    4. 当虚拟机启动时,用户需指定一个要加载的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类。
    5. 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需先触发其初始化。

    前面的五种方式是对一个类的主动引用,除此之外,所有引用类的方法都不会触发初始化,佳作被动引用。举几个例子~

    public class SuperClass {
        static {
            System.out.println("SuperClass init!");
        }
        public static int value = 1127;
    }
    
    public class SubClass extends SuperClass {
        static {
            System.out.println("SubClass init!");
        }
    }
    
    public class ConstClass {
        static {
            System.out.println("ConstClass init!");
        }
        public static final String HELLOWORLD = "hello world!"
    }
    
    public class NotInitialization {
        public static void main(String[] args) {
            System.out.println(SubClass.value);
            /**
             *  output : SuperClass init!
             * 
             * 通过子类引用父类的静态对象不会导致子类的初始化
             * 只有直接定义这个字段的类才会被初始化
             */
    
            SuperClass[] sca = new SuperClass[10];
            /**
             *  output : 
             * 
             * 通过数组定义来引用类不会触发此类的初始化
             * 虚拟机在运行时动态创建了一个数组类
             */
    
            System.out.println(ConstClass.HELLOWORLD);
            /**
             *  output : 
             * 
             * 常量在编译阶段会存入调用类的常量池当中,本质上并没有直接引用到定义类常量的类,
             * 因此不会触发定义常量的类的初始化。
             * “hello world” 在编译期常量传播优化时已经存储到 NotInitialization 常量池中了。
             */
        }
    }

    6.2 类的加载过程

    6.2.1 加载

    1. 通过一个类的全限定名来获取定义次类的二进制流(ZIP 包、网络、运算生成、JSP 生成、数据库读取)。
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法去这个类的各种数据的访问入口。

    数组类的特殊性:数组类本身不通过类加载器创建,它是由 Java 虚拟机直接创建的。但数组类与类加载器仍然有很密切的关系,因为数组类的元素类型最终是要靠类加载器去创建的,数组创建过程如下:

    1. 如果数组的组件类型是引用类型,那就递归采用类加载加载。
    2. 如果数组的组件类型不是引用类型,Java 虚拟机会把数组标记为引导类加载器关联。
    3. 数组类的可见性与他的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性将默认为 public。

    内存中实例的 java.lang.Class 对象存在方法区中。作为程序访问方法区中这些类型数据的外部接口。
    加载阶段与连接阶段的部分内容是交叉进行的,但是开始时间保持先后顺序。

    6.2.2 验证

    是连接的第一步,确保 Class 文件的字节流中包含的信息符合当前虚拟机要求。

    文件格式验证

    1. 是否以魔数 0xCAFEBABE 开头
    2. 主、次版本号是否在当前虚拟机处理范围之内
    3. 常量池的常量是否有不被支持常量的类型(检查常量 tag 标志)
    4. 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
    5. CONSTANT_Utf8_info 型的常量中是否有不符合 UTF8 编码的数据
    6. Class 文件中各个部分集文件本身是否有被删除的附加的其他信息
    7. ……

    只有通过这个阶段的验证后,字节流才会进入内存的方法区进行存储,所以后面 3 个验证阶段全部是基于方法区的存储结构进行的,不再直接操作字节流。

    元数据验证

    1. 这个类是否有父类(除 java.lang.Object 之外)
    2. 这个类的父类是否继承了不允许被继承的类(final 修饰的类)
    3. 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
    4. 类中的字段、方法是否与父类产生矛盾(覆盖父类 final 字段、出现不符合规范的重载)

    这一阶段主要是对类的元数据信息进行语义校验,保证不存在不符合 Java 语言规范的元数据信息。

    字节码验证

    1. 保证任意时刻操作数栈的数据类型与指令代码序列都鞥配合工作(不会出现按照 long 类型读一个 int 型数据)
    2. 保证跳转指令不会跳转到方法体以外的字节码指令上
    3. 保证方法体中的类型转换是有效的(子类对象赋值给父类数据类型是安全的,反过来不合法的)
    4. ……

    这是整个验证过程中最复杂的一个阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段对类的方法体进行校验分析,保证校验类的方法在运行时不会做出危害虚拟机安全的事件。

    符号引用验证

    1. 符号引用中通过字符创描述的全限定名是否能找到对应的类
    2. 在指定类中是否存在符方法的字段描述符以及简单名称所描述的方法和字段
    3. 符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问
    4. ……

    最后一个阶段的校验发生在迅疾将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,还有以上提及的内容。
    符号引用的目的是确保解析动作能正常执行,如果无法通过符号引用验证将抛出一个 java.lang.IncompatibleClass.ChangeError 异常的子类。如 java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError 等。

    6.2.3 准备

    这个阶段正式为类分配内存并设置类变量初始值,内存在方法去中分配(含 static 修饰的变量不含实例变量)。

    public static int value = 1127;
    这句代码在初始值设置之后为 0,因为这时候尚未开始执行任何 Java 方法。而把 value 赋值为 1127 的 putstatic 指令是程序被编译后,存放于 clinit() 方法中,所以初始化阶段才会对 value 进行赋值。

    基本数据类型的零值

    数据类型零值数据类型零值
    int0booleanfalse
    long0Lfloat0.0f
    short(short) 0double0.0d
    char'\u0000'referencenull
    byte(byte) 0 

    特殊情况:如果类字段的字段属性表中存在 ConstantValue 属性,在准备阶段虚拟机就会根据 ConstantValue 的设置将 value 赋值为 1127。

    6.2.4 解析

    这个阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

    1. 符号引用
      符号引用以一组符号来描述所引用的目标,符号可以使任何形式的字面量。
    2. 直接引用
      直接引用可以使直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和迅疾的内存布局实现有关

    解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行,分别对应于常量池的 7 中常量类型。

    6.2.5 初始化

    前面过程都是以虚拟机主导,而初始化阶段开始执行类中的 Java 代码。

    6.3 类加载器

    通过一个类的全限定名来获取描述此类的二进制字节流。

    6.3.1 双亲委派模型

    从 Java 虚拟机角度讲,只存在两种类加载器:一种是启动类加载器(C++ 实现,是虚拟机的一部分);另一种是其他所有类的加载器(Java 实现,独立于虚拟机外部且全继承自 java.lang.ClassLoader)

    1. 启动类加载器
      加载 lib 下或被 -Xbootclasspath 路径下的类

    2. 扩展类加载器
      加载 lib/ext 或者被 java.ext.dirs 系统变量所指定的路径下的类

    3. 引用程序类加载器
      ClassLoader负责,加载用户路径上所指定的类库。

     

    除顶层启动类加载器之外,其他都有自己的父类加载器。
    工作过程:如果一个类加载器收到一个类加载的请求,它首先不会自己加载,而是把这个请求委派给父类加载器。只有父类无法完成时子类才会尝试加载。

    6.3.2 破坏双亲委派模型

    keyword:线程上下文加载器(Thread Context ClassLoader)

    最后

    前面两次粗略的阅读,能理解内容,但是很难记住细节。每每碰到不会的知识点就上网查,所以知识点太碎片脑子里没有体系不仅更不容易记住,而且更加容易混乱。但是通过这种方式记录发现自己清晰了很多,就算以后忘记,知识再次捡起的成本也低了很多。

    这次还有一些章节虽然阅读了,但是还未完成记录。等自己理解深刻有空闲了就再次记录下来,这里的内容均出自周志明老师的《深入理解 Java 虚拟机》,有兴趣的可以入手纸质版。

    展开全文
  • Java虚拟机运行时数据

    万次阅读 2019-12-13 14:55:00
    Java虚拟机在执行Java程序的过程,会把它所管理的内存划分若干个不同的数据区域,这些区域被赋予不同的用途,它们有着各自创建和销毁时间,有的区域是随着虚拟机进程启动而存在的,有的区域则是依赖用户线程...
  • JAVA虚拟机方法区介绍

    千次阅读 2016-11-18 18:00:54
    最近项目数据量猛增,需要监控JVM参数并调优,作为一个之前搞C++的新小二,赶紧恶补一下虚拟机的知识,在网上搜集各种资料,总结一下方法区内存布局,如有错误,欢迎指导。 1、JVM内存模型  大多数JVM将内存...
  • Java虚拟机组成部分及作用

    千次阅读 2018-04-03 18:25:54
    感谢周志明所著的《深入理解Java虚拟机》一书。下面附上了一些我自己的理解。 java虚拟机(JVM) 当我们讨论到它的组成部分的时候,有人可能很多都会说是以栈和堆,但是实际上远远不止这两个部分。 下面...
  • java虚拟机 jvm java堆 方法区 java栈

    千次阅读 2016-03-27 21:12:41
    几乎所有的对象都存在堆java堆完全自动化管理,通过垃圾回收机制,垃圾对象会自动清理,不需要显式释放。 根据java垃圾回收机制的不同,java堆可能有不同的结构。最常见的是将整个java堆分为新生代和老年代。跟...
  • Java虚拟机(JVM)面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:26:32
    文章目录Java内存区域说一下 JVM 的主要组成部分及其作用?说一下 JVM 运行时数据深拷贝和浅拷贝说一下堆栈的区别?队列和栈是什么?有什么区别?HotSpot虚拟机对象探秘对象的创建为对象分配内存处理并发安全问题...
  • 方法调用在Java虚拟机中的实现方式?

    千次阅读 热门讨论 2021-05-20 00:27:56
    本文隶属于专栏《100个问题搞定Java虚拟机》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论帮忙指出,谢谢! 本专栏目录结构和文献引用请见100个问题搞定Java虚拟机 解答 虚方法调用包括 ...
  • JAVA虚拟机精讲

    2018-08-17 17:29:13
    JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA虚拟机精讲JAVA...
  • Java虚拟机--方法区(运行时常量池)

    千次阅读 2018-07-15 11:38:20
    文章引用: 深入理解Java虚拟机 ...虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来. 对于习惯在HotSpot虚拟机上开发...
  • 最近在Ubuntu安装eclipse时,遇到了eclipse找不到Java虚拟机的问题,提示如下:A Java Runtime Environment (JRE) or Java Development Kit (JDK) must be available in order to run Eclipse. No Java virtual ...
  • JAVA虚拟机

    千次阅读 2018-05-10 11:29:59
    1、Java虚拟机是什么? A、抽象规范; B、一个具体实现; C、一个运行的虚拟机实例;2、虚拟机的生命周期 A、Java虚拟机的职责就是负责运行一个Java程序; B、当启动一个Java程序时,Java虚拟机就开始了,当...
  • eclipse配置java虚拟机方法

    万次阅读 2010-06-24 11:28:00
    在eclipse运行java程序时,常常会出项OutofMemory错误,该错误就是由于java虚拟机内存不足引起的。怎样配置java的虚拟机内存呢?以下提供两种方法:一、直接配置jre的参数,步骤如下: 找到eclipse的windows->...
  • java虚拟机结构图解(堆栈方法区)

    千次阅读 2020-05-11 10:55:24
    线程私有:jvm虚拟机栈,本地方法栈,程序计数器 线程共享:堆和方法区 线程私有:比如我除了main线程还另外写了一个Test线程,他们同时在执行,那么我们内存里就会出现两个线程栈(即图右边紫色的部分各来一份,...
  • java虚拟机运行时数据介绍

    千次阅读 2019-02-14 22:10:53
    java虚拟机在程序执行的过程会把它所管理的内存划分为若干个不同的数据区域,每个区域都有各自的用途。结构图如下: 程序计数器   程序计数器(Program Counter Register)是一块较小的内存空间,这块区域是...
  • Java虚拟机规范Java8版

    2018-06-08 15:43:08
    基于全新Java SE 8,完整且准确地阐述Java虚拟机规范,是深度了解Java虚拟机和Java语言实现细节的必读之作。  《Java核心技术系列:Java虚拟机规范(Java SE 8版)》共分7章。第1章从宏观的角度介绍了Java...
  • java虚拟机中的内存区域划分

    千次阅读 2017-04-01 11:33:50
    Java虚拟机规范规定的java虚拟机内存其实就是java虚拟机运行时数据,其架构如下: 其中方法区和堆是由所有线程共享的数据。 虚拟机栈,本地方法栈和程序计数器是线程隔离的数据。 二、详解 下面来具体介绍这...
  • 为了更加深入的理解方法的覆盖和覆写原理需要了解java方法的调用原理首先...java虚拟机中提供了5条方法调用的字节码指令:invokestatic:调用静态方法 invokespecial:调用实例构造器方法、私有方法、父类方法 invokev
  • Java虚拟机方法的执行过程简述

    千次阅读 2017-05-04 22:49:22
    首先Java虚拟机在执行方法涉及三块区域,堆,栈方法区。public class Foo{ public int add(int a,int b){ return a + b; } }Foo foo = new Foo(); foo.add(1,3);以上面的例子来说,Java虚拟机会首先将这个...
  • Carson带你学JVM:图文解析Java虚拟机内存结构

    万次阅读 多人点赞 2019-09-29 07:33:11
    本文将全面讲解Java虚拟机中的内存模型 & 分区,希望你们会喜欢
  • Java虚拟机java虚拟机下的进程

    千次阅读 2012-02-22 14:48:29
    一、什么是Java虚拟机  当你谈到Java虚拟机时,你可能是指: ... 一个运行Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三个程序,就会有
  • 《深入理解 java 虚拟机》 读书扩展 作者:淮左白衣 写于 2018年4月13日21:26:05 目录 方法区 图例(方法区中都保存什么) 类型信息 类型的常量池 (即运行时常量池) 字段信息 方法信息 类变量(即static...
  • 这个插件不用多介绍,许多需要Java环境的软件必备。现在的网页如果缺少这个插件一般不提示,不能显示表单。此插件为微软java虚拟机,区别于sun java虚拟机
  • Java虚拟机之虚拟机栈与本地方法

    千次阅读 2018-08-13 21:31:45
     与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同。虚拟机描述的是Java方法执行的内存模型:  每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口...
  • 我们知道java之所以能够快速崛起一个重要的原因就是其跨平台性,而跨平台就是通过java虚拟机来完成的,java虚拟机属于java底层的知识范畴,即使你不了解也不会影响绝大部分人从事的java应用层的开发,但是如果你了解...
  • 事实上,在java虚拟机规范,大多数是宽泛,抽象的规则。这是为了让更多的硬件/操作系统可以自由的实现自己的虚拟机程序。因为毕竟硬件环境/操作系统千差万别。 在实现自己的虚拟机程序时,只要符合虚拟机规范...
  • 深入理解Java虚拟机-走近Java

    万次阅读 多人点赞 2020-01-02 15:02:08
    文章目录概述Java技术体系Java发展史Java虚拟机发展史展望Java技术的未来实战:自己编译JDK本章小结 本博客主要参考周志明老师的《深入理解Java虚拟机》第二版 读书是一种跟大神的交流。阅读《深入理解Java虚拟机...
  • 一篇文章快速搞懂Java虚拟机的栈帧结构

    万次阅读 多人点赞 2020-01-04 10:04:19
    栈帧是Java虚拟机中的虚拟机栈的基本元素,它也是用于支持Java虚拟机进行方法调用和方法执行背后的数据结构,了解了它就可以更好地理解Java虚拟机执行引擎是如何运行的。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 589,056
精华内容 235,622
关键字:

java虚拟机中方法区作用

java 订阅