Java虚拟机学习

1.java虚拟机管理内存区域划分

Java虚拟机学习

        由图可见,方法区(Method Area)和堆(Heap)是多线程共享的;而虚拟机的栈(VM Stack)、线程本地栈和程序计数器是各线程相互隔离私有的。

2.各区域功能划分:

2.1程序计数器:

        在虚拟机的概念模型里,当字节码解释器工作的时候,就是通过改变私有的程序计数器的值来获取下一条需要执行的字节码指令,无论是分支switch、循环for、跳转、异常处理、线程恢复等基础功能都需要依赖该计数器。

        因为java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间来实现,所以就需要各个线程就需要一个私有的程序计数器在能在线程切换后恢复到正确的执行位置。

2.2虚拟机栈和本地方法栈

        虚拟机栈是为虚拟机执行java方法服务(每个方法在执行时会创建一个栈帧用于存储局部变量表、操作栈数、动态链接、方法出口灯信息。每个方法从调用到执行完成就对应一个栈帧在虚拟机中入栈到出栈的过程);本地方法栈是为虚拟机使用native方法服务。

        Hotspot虚拟机并不区分虚拟机栈和本地方法栈。

        二者会抛出2种异常:StackOutflowError和OutOfMemoryError异常。

        

2.3Java堆

        堆(Heap)是java虚拟机管理内存中最大的一块,堆被所有的线程共享,在虚拟机启动时被创建。堆的唯一目的在于存放对象实例,(几乎)所有的对象实例都在堆中被分配内存。

        堆是垃圾收集器管理的主要区域。从内存回收的角度来说,垃圾收集器目前主要采用分代收集算法。从内存分配的角度来说,线程共享的堆可能划分出多个线程私有的分配缓存区(Thread Local Allocation Buffer , TLAB),但无论如何划分,在堆中存放的都是对象实例。

        若堆中无可用内存进行分配且堆无法在拓展的时候,会抛出OutOfMemoryException;

2.4方法区

        方法区(Method Area)同堆一样是为所有线程共享的内存区域,用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。

        方法区的垃圾收集主要针对常量池的回收以及对类型的卸载。但java虚拟机对方法区的规范十分宽松可以选择不实现垃圾收集。

2.5运行时常量池

        常量池用于存放编译器生成的各种字面量和符号引用,这部分在类加载后进入方法区的运行时常量池存放。

以下为关于常量池的一些例子:

String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
          
System.out.println(s1 == s2);  // true ,因为s1、s2在赋值时,均使用的字符串字面量,就是直接把字符串写死,在编译期间,这种字面量会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,s1、s2指向的是同一个内存地址,所以相等。
System.out.println(s1 == s3);  // true ,虽然s3是拼接而成的,但拼接的两部分内容都是已确认的字面量,所以虚拟机在编译时会进行内部排序,优化为String s3 = "Hello";
System.out.println(s1 == s4);  // false , 虽然s4也是拼接,但new String("lo")这部分不是已知字面量,只有当运行时才会确认具体值,所以编译器不会对其进行优化
System.out.println(s1 == s9);  // false , 同样s7和s8虽然是已知字面值,但拼接时作为s9的变量并不是确认值,所以二者拼接后的地址也不确定
System.out.println(s4 == s5);  // false , 二者在堆中的地址就不一致
System.out.println(s1 == s6);  // true , String.intern()方法尝试将该字符串对象添加到常量池中,若常量池中已存在一个该字符串(用equals方法判定)则返回常量池中该字符串的地址,所以这里为true。

 

除了字符串常量池外,还有整型常量池、浮点型常量池等等,但数值型常量池不允许手动添加常量,以整形常量池为例,其内常量池的值范围为-128~127。

如:

        Integer i1 = new Integer("100");
        Integer i2 = new Integer("100");
        System.out.println(i1 == i2);//false,因为用了new关键字,jvm在堆中分别开了2块区域指向i1和i2两个对象,所以i1和i2的地址不一致,这里返回false
        
        Integer i3 = 100;
        Integer i4 = 100;
        System.out.println(i3 == i4);//true,这里100存在于整型常量池,所以返回同一个对象,为true
        
        Integer i5 = 1000;
        Integer i6 = 1000;
        System.out.println(i5 == i6);//false,根据integer的源码,因为1000超过了整型常量池的范围,所以这里jvm分别新建了两个对象,同i1和i2,返回false

        

 

转载于:https://my.oschina.net/betteru/blog/1541351