• JVM学习1--数字存储,内存模型,指令重排


    一、数字在计算机中的存储

      整数:以补码形式存储。

        补码:正数的补码是自身,负数的补码是取反码加1(取反码时符号位还是1)

      浮点型:以float类型表示

      

            注意一下,这八位指数实际上是(127+次数)的结果,因为要考虑到负数指数的情况,例如如下118.5在计算机中的存储:

        

        而因为科学计数法第一位总是1开头,可以将小数点前面的1省略,所以23bit的尾数部分,可以表示的精度却变成了24bit。那24bit能精确到小数点后几位呢,我们知道9的二进制表示为1001,所以4bit能精确十进制中的1位小数点,24bit就能使float能精确到小数点后6位。那从这里可以看到,如果小数位数过多,是不能被精确存储的,还有另一种情况不能被精确存储,例如小数0.2,在取二进制的过程中,“乘2取整,顺序排列”,那最终也乘不到一个整数,所以也是不能被精确存储的。

    二、JVM基础结构--内存模型

      1. JVM启动流程

      

      2. JVM具体的模型--堆栈方法区(Metaspace)等

      请看这篇博客:https://blog.csdn.net/bruce128/article/details/79357870 写的非常棒,我这里只做这篇博客的补充。

        ① 栈、堆、方法区的交互(jdk1.8是Metaspace,字符串常量池从jdk1.7开始就从永久区移除到对空间上了)

       

      

        ② 栈上分配

        栈上分配是Java虚拟机提供的一项优化技术,它的基本思想是,对于那些线程私有的对象(这里指不可能被其他线程访问的对象),可以将它们打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高了系统的性能。

        栈上分配的基础是进行逃逸分析。逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体

        可逃逸对象:对象u分配在方法区内,该字段可能被任何线程访问,因此属于逃逸对象。

      private static User u;
      public static void alloc(){
          u = new User();
          u.id = 5;
          u.name = "xxx";
      }

        非逃逸对象:局部变量,对象u的引用在栈上,并且该对象并没有被alloc()函数返回,或者任何形式的公开,因此,它并未发生逃逸,所以对于这种情况,虚拟机就有可能把对象u分配在栈上,而不是在堆上。

      public static void alloc(){
          User u = new User();
          u.id = 5;
          u.name = "xxx";
      }
        /**
         * 非逃逸对象可能分配到栈上的测试
         * 运行参数:
         * -server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis
         * -XX:+PrintGCDetails -XX:-UseTLAB -XX:+EliminateAllocations
         */
            public class UnEscapeTest {
                public static class User{
                public int id = 0;
                public String name = "";
            }
    
            public static void alloc(){
                User u = new User();
                u.id = 5;
                u.name = "xxx";
            }
    
            public static void main(String[] args) {
                long begin = System.currentTimeMillis();
                for (int i = 0; i < 100000000; i++) {
                    alloc();
                }
                long end = System.currentTimeMillis();
                System.out.println(end - begin);
            }
        }

      上面的测试结果: 

      [GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->720K(9728K), 0.0008276 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
      [GC (Allocation Failure) [PSYoungGen: 2536K->488K(2560K)] 2768K->720K(9728K), 0.0019476 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
      8

      结果只进行了两次Minor GC,若是关闭逃逸分析(-XX:-DoEscapeAnalysis)或者关闭标量替换(-XX:-EliminateAllocations)(默认打开),将会有大量GC产生,说明栈上分配依赖逃逸分析和标量替换的实现。

      结论:对于大量的零散小对象,栈上分配提供了一种很好的对象分配优化策略,栈上分配速度快,并且可以有效避免垃圾回收带来的负面影响,但由于和堆空间比较,栈空间较小,因此对于大对象也不适合在栈上分配。

      3. 线程调用公共变量时

      

      这里只看这张图能反应出的东西:

        ① 每一个线程都有一个独立工作内存(寄存器)

        ② 工作内存中存放的是主存中变量的值的拷贝

        那我们可以发现,当一个线程修改了一个共享变量并写回主存时,另一个持有此共享变量的线程并不会立刻得知此变化,因为这个线程操作的是自己工作内存里的变量,这就是可见性问题(volatile,synchronized,final)。

        之前写的博客:http://www.cnblogs.com/NoYone/p/8541898.html

    三、指令重排

      指令重排能帮助程序更快速的运行,这是jvm做的优化,但有些时候程序必须顺序执行,指令重排符合以下规则。

      

  • 相关阅读:
    JSON
    event flow
    for,for each,for in,for of
    history of program language
    px fr em rem
    正则符号
    DOM、BOM
    web布局
    grid
    初学python环境安装
  • 原文地址:https://www.cnblogs.com/NoYone/p/8969818.html
Copyright © 2020-2023  润新知