从0到1了解JVM基本原理(一):Java内存区域

Java虚拟机强大的功能与特性隐藏了底层技术的复杂性,让程序员可以把精力主要集中在业务开发上。然而凡事都有两面性,当程序规模越来越大,提升硬件资源无法有效提升程序运作力时,可能比较省时省力的方法就是进行程序调优,
从另一个层面来说,如果程序员不是很了解虚拟机的特性以及运行原理,就无法写出适合虚拟机运行的优质代码。

运行时区域

JVM在运行时会将可使用的内存分成几个区域,了解这几个区域的功能对于理解JVM至关重要。其结构如下(图片来源见水印):

JVM运行时内存结构

程序计数器

JAVA虚拟机的字节码指示器,可以理解为当前线程所执行的字节码的行数指示器。字节码解释器工作时就是改变这个程序计数器的位置来告诉虚拟机下一条要执行的字节码的位置
分支、循环、跳转、异常、线程恢复等基础功能都依赖于此。

由于多线程都是通过分配处理器时间片进行轮询执行实现的,因此每个线程被恢复运行的时候必须知道程序挂起时代码运行的位置,因此程序计数器是每个线程私有的

虚拟机栈

Java虚拟机栈也是私有的,生命周期和线程相同。它描述的是Java方法执行的内存模型。每次方法运行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等方法运行时需要的东西。
每次方法调用至执行完成都标志着一个栈帧从入栈到出栈的过程

本地方法栈

与虚拟机栈一样,只不过本地方法栈执行的是native方法。虚拟机规范中对此栈使用的语言与数据结构没有强制规定。

Java堆

Java堆是虚拟机中内存最大的一块,是所有线程共享的区域,此区域的唯一目的就是存放对象实例。几乎所有的对象实例都在堆上分配,同时也是垃圾收集器主要工作的区域(通过 -Xmx -Xms 调整Java堆的最大值和最小值)。具体到垃圾回收再进行说明

方法区

与堆一样,是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器(JIT)编译后的代码等。在HotSpot虚拟机里,常被称为“永久代”(主要是针对垃圾回收而言)。

可能大部分人认为此区域不需要内存管理,其实事无绝对,当方法区的内存占用满了之后,再有新的方法运行时,会由于没有空间缓存编译后的机器码,而造成每次运行都是动态编译运行,进而严重影响性能。一般而言,方法区内存越小、程序运行越久,越容易出现这个问题。
这里又引入一个JVM何时缓存编译的问题,不同的JVM或者JVM不同的模式,其编译阈值不同,简单来说就是一段代码执行的次数超过阈值,就会将这段热点代码编译后的机器码缓存到code cache中。
一般有以下几种编译模式:
* 纯编译执行:使用 -Xcomp 启动参数,每段代码第一次运行就会被缓存编译后的机器码,其缺点为每次启动耗时非常久
* client模式c1编译器:此模式的目的在于让程序尽快进入编译执行阶段,因此会快速收集启动时的一些代码执行信息,缓存热点代码,启动速度快但是后续的执行效率较低。
* server模式c2编译器:服务器模式刚好相反,其侧重点在于使程序运行后的性能尽可能的高。因此启动阶段需要收集较多的代码运行信息,其优缺点都比较明显,代码执行效率高,启动耗时长。
看到这里大家都会想,那么能不能启动时使用client模式快速启动,运行时再使用server模式慢慢缓存真实业务中的热点代码呢?其实这就是分层编译模式

在JVM 使用参数 -XX:+TieredCompilation 参数开启分层编译模式,注意分层编译只能运行在服务器模式中。在启动初期使用客户端c1编译器缓存频繁调用的代码,系统稳定后继续使用服务端c2编译器继续优化性能

运行时常量池

这个其实也是方法区的一部分,Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项内容是常量池,用于存放编译期间生成的 字面量和符号引用。
Java不要求常量一定是编译期间产生,程序实际上可以动态生成,比较常用的就是String的intern()方法。

引申知识点:Integer对象的比较,(-128 ~ 127)范围的Integer 对象会缓存起来,即这个范围内的Integer对象(new 关键字生成的对象不算)可以用 == 操作符判断数值是否相等,超过这个范围,则只能用equals方法比较数值。
参看JAVA Integer类源码:

    /*
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }

            high = h;
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
        private IntegerCache() {}
    }

    /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

这两段代码和注释已经写得非常明白了:
1.虚拟机启动时会缓存常用的Integer对象
2.要命中这个缓存必须是自动装箱的场景,如果强制生成一个新对象,则不会命中缓存
3.-128到127的整数一定会被缓存,但是我们可以调整配置使缓存的最大值更大(最小值总是固定为-128),使用启动参数 -XX:AutoBoxCacheMax= 来设置缓存大小,从源代码看,小于127的配置会忽略
4.使用Integer a = 5;之类的语句会触发Integer自动装箱功能,对于等号右边的值会自动调用Integer的valueOf方法获取Integer对象。

以下为验证代码

public class Tesst{
    public static void main(String[] args){
        Integer a = 5;
        Integer b = 5;
        // true
        System.out.println(a == b);

        Integer c = new Integer(5);
        // false
        System.out.println(a == c);

        // default config
        Integer d = 1000;
        Integer e = 1000;
        // false
        System.out.println(d == e);
        // true
        System.out.println(d.equals(e));
    }
}

Java开发者编写这段代码的目的显然在于优化程序性能,减小常用整数的内存占用和新建对象的开销。
出于程序的规范而言,对象的比较应统一使用equals方法进行比较。
再来一个小问题,以下这段代码会输出什么?

        Integer a = 1000;
        int b = 1000;
        System.out.println(a == b);
        int c = new Integer(1000);
        System.out.println(a == c);

答案是两个true。牵涉到的也是对象和int值比较时自动装箱和自动拆箱的问题。

直接内存

这一块不太理解,看描述使用到的场景也比较少,直接摘录书的内容如下:

直接内存不是虚拟机运行时数据的一部分,也不是虚拟机规范定义的内存区域,但是这部分内存也被频繁的使用。
在JDK1.4中加入了NIO(New Input/Output)类 , 引入了一种基于通道与缓冲区的IO方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆的DirectByteBuffer对象作为这块内存区的引用进行操作,这样能在某些场景下显著提高性能,避免了再Java堆和Natived堆中来回复制数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值