精华内容
下载资源
问答
  • deepinJvm-zzm 周志明深入理解JVM的源代码
  • deepinJvm-zzm:周志明深入理解jvm的源代码
  • 深入理解JVM

    2017-12-28 11:22:36
    JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明JVM-周志明
  • 1 HotSpot的垃圾收集是基于分代的,不同...2 看周志明显示的《深入理解Java虚拟机》第三章垃圾收集器与内存分配,对Young generation的Parallel Scaverge和Tenured generation的CMS印象深刻,简单整理了下相关的知识。H

    1   HotSpot的垃圾收集

            是基于分代的,不同代的内存区域根据其对象生命周期的长短特性选择不同的垃圾收集算法。HotSpot提供的垃圾收集器:

      

    2   Parallel Scavenge收集器

          看周志明显示的《深入理解Java虚拟机》第三章垃圾收集器与内存分配,对Young generation的Parallel Scavenge和Tenured generation的CMS印象深刻,简单整理了下相关的知识。HotSpot的新生代由于生存时间较短,最终存活对象较少,适合拷贝清除算法,老年代由于对象存活时间较长,采用标记整理或者标记清除算法。

          Parallel Scavenge收集器,关注吞吐量,这种收集器中吞吐量和停顿时间是不可兼顾的两个方面,在共享CPU的情况下,这两个要素必定是此消彼长的。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值;停顿时间在单CPU系统中也是有GC时间的比率决定的。在Java进程中CPU时间由用户线程和GC回收线程轮换使用,二者的和构成了Java进程的CPU总消耗时间。Parallel Scavenge关注的两个要素的计算公式如下:

         吞吐量 throughtout=(运行用户代码的时间)/(运行用户代码的时间+垃圾收集时间)

         垃圾收集时间的占比=(垃圾收集时间)/(执行用户代码的时间+垃圾收集时间)

         停顿时间越短,说明GC消耗CPU时间越长,适合需要和用户交互的程序,能良好地提升用户的体验,另一方面用户代码执行时间也越短,系统吞吐量也越小,但是对用户响应速度也越高。而高吞吐量则可以高效利用CPU更快地完成用户的运算任务,主要适合后台运算交互较少的运用中。

         Parallel Scavenge提供了两个参数来精确控制吞吐量,-XX:MaxGCPauseMills,-XX:GCTimeRatio。前者是控制最大垃圾手机停顿时间的毫秒;后者是控制垃圾收集时间在CPU总消耗时间的占比的。GCTimeRatio的默认值是99,这个值的设置,我是这么理解的:就是你期望用户代码的CPU消耗时间和GC的CPU消耗时间的比例。在该收集器中,默认的值是99,即总CPU时间中,用户CPU时间:GC CPU时间=99:1,则GC时间占据总CPU时间的比例为1/(1+99)=1%。

        结论,所谓的GCTimeRatio,就是(用户程序CPU时间):(GC CPU时间),通过这个值,可以计算出GC时间在总CPU时间的比率。

        ParNew和Parallel Scavenge的区别是,后者关注的是系统吞吐量,也称为吞吐量优先的收集器。会跟当前系统的运行情况收集性能监控信息,动态调整某些参数,以提供最合适的停顿时间或者最大的吞吐量。自适应调节策略是其与ParNew的一个重要区别。

    3 CMS收集器

           并发标记清除收集器,在多处理器的系统中才能发挥其优势,此时用户线程和GC线程可以在不同的CPU上运行,在CMS并发清理阶段,用户线程还在运行,这存在两个问题:首先,用户线程运行过程中还会产生新的垃圾,但此时CMS已经开始,这些新垃圾并未标记,无法在此次GC中收集,也称为“浮动垃圾”;其次,用户线程既然要运行,就必须有足够的内存空间,所以CMS收集Old区时必须保证为用户线程预留足够的内存。

         CMS收集Old区是在Old的使用空间到达一定的比例后就被触发的(不像其它的收集是在Old区几乎完全塞满时才进行),这样确保用户线程有足够的空间。控制参数是:-XX:CMSInitiatingOccupancyFraction,当用户进程在CMS运行过程中出现内存不足时会出现“Concurrent Mode Failure",JVM的后备方案就发生作用,以Serial Old方式重新收集Old区(暂停用户线程)。这个参数设置过高,容易导致用户线程运行过程中内存不足而出现Concurrent Mode Failure。

         CMS是采用标记清除的收集算法,还有一个缺点是容易产生空碎片,CMS提供了两个参数来解决这个问题。-XX:UseCMSCompactAtFullCollection开关参数(默认是开启),如果CMS出现无法分配连续内存时,对碎片内存进行整理。-XX:CMSFullGCsBeforeCompation,默认值为0,表示每次进入FullGC之前执行多少次不压缩的GC后跟着执行一次碎片整理。

          我的理解是,在CMS默认的参数下,其实CMS已经不是标记清除算法,而是标记整理算法了,我在想为什么一开始不直接使用标记整理算法呢?仔细想想,CMS其实是提供了动态调节算法的机制,应该可以综合获取这两种算法共同的优势,在特定场景下相互切换,以达到最好的效果。

         结论:不得不佩服设计这周详的考虑,对存在的问题提供解决方案和应急预案,CMS里面的优秀理念,受教了!

       

    
    展开全文
  • 深入理解JVM》(周志明著)<1>

    千次阅读 2019-03-30 16:42:56
    Java虚拟机运行时的数据区,以下所讲的和计算机内存有区别,读者请勿混淆。 程序计数器 Program Counter Register,对于Java内存来说这是占空间很小的...由此,可以将计数器理解为当前字节码运行的行号指示器。一般...

    Java虚拟机运行时的数据区,以下所讲的和计算机内存有区别,读者请勿混淆。

    • 程序计数器

    Program Counter Register,对于Java内存来说这是占空间很小的一部分。它的作用也很单一,但是必不可少。一般而言,将这部分从概念上来说是这么一个设计的实现理念:字节码解释器工作的时候,计数器改变自己的值,来指示字节码指令运行的行数。由此,可以将计数器理解为当前字节码运行的行号指示器。一般的虚拟机都是如此设计。在程序设计中,有循环、分支、判断语句,它们都是需要程序计数器来指示执行的行号。对于进程的实现,通常采用轮流切换和分配处理器的使用时间来实现。在同一个时间里面,一个内核(CPU)只能执行一个线程,在一个线程完成之后,需要恢复到正确的位置,因此, 在程序计数器中的线程是独立的,不会收到其他线程的影响。对于Java方法,程序计数器会进行记录,但是Native方法,是一个非Java代码实现的方法,不会被程序记录器记录行号。关于Native方法,可以参考native方法在java中介绍及使用图解

    • Java虚拟机栈

    Java Virtual Machine Stacks,Java方法里面会定义变量、操作数、方法的出入口等信息。这些信息都将保存在Java虚拟机栈的栈帧中,也就是说一个线程的生命周期和栈的生命周期是一样的,随栈而生,随栈而亡。对于栈而言,存放的局部变量表是县城私有的,因为是局部!具体而言,这边的变量是指局部变量表,它包括基本的数据类型和对象的引用类型(可以是指向对象的引用指针、句柄、指向字解码地址的指令)。局部变量的空间(Slot)在编译期间(启动之前)就会完成大小的分配,也就是这个方法的空间会被分配,具体的变量的大小视情况而定。在虚拟机栈中,涉及到栈的深度(高度),讲一个栈看成是一个木桶,当存放的东西(栈帧)越大时,桶的剩余高度(深度)越小。如果是线程的请求的栈的深度越大,那么可以拥有的存放的栈帧越小,越容易出现栈溢出(Stack Overflow)。在Java中可以通过设置-XSS参数来进行对于栈内存的大小分配。具体的操作过程可以参考JVM调优总结 -Xms -Xmx -Xmn -Xss。还有一种情况,在使用JDBC时,我们进行创建驱动类常会用java.lang.class类中的forName()方法实现,这时,JVM采用动态扩展来进行分配内存,如果动态分配的过程中虚拟机空间不足,会导致内存溢出(OutOfMemory)。实现Java Web的工程中,浏览器跨域调用applet里面的class文件也是属于动态扩展。另这边,我比较特意做了功课,对于两种不一样的Error问题,分别是在单线程和多线程下的不同描述。

    • 本地方法栈 

    Native Method Stack,实现的作用和虚拟机栈相同,但是底层的方法是使用非Java写的。在虚拟机中对于这样的方法栈中的语言和数据结构没有规范。关于Native方法,可以参考native方法在java中介绍及使用图解

    • Java堆

    Java Heap, 存放是对象的实例。几乎所有的对象实例都放在堆中。随着JIT(即时)编译器的发展,在Java中,产生栈上分配和标量替换技术,使得在堆中的空间更加有效地使用。垃圾收集器主要管理堆,这边具体不说具体的垃圾回收器。在Java堆的区域,可以划分为新生代和老年代。更细可以分From Survivor和To Survivior等。在Java中还有多线程私有的分配缓冲区(Thread Local Allocation Buffer)。在堆中,存储的区域是一个逻辑上连续的空间,因此在物理地址上不要个要求是连续的。在具体的空间设置中,这个也可以是进行设计的,通过-Xmx和-Xms。在堆中,线程是共享的,但是还是要注意,线程必须有自己的寄存器和栈。关于共享可以参考jvm中堆栈的数据共享和线程共享

    • 方法区 

    Method Area,这个区域和Java堆功能相近,存储的内容不相同。这里主要掌握运行时常量池(Runtime Constant Pool)即可。方法区中,有编译期间生成的字面量和符号引用,还有运行时常量池 。运行时常量池有动态特性,之所以这么说,由于Java中,运行期间的新的常量也可以放在池中,常常使用的有String类的intern()方法。字符串常量的变化和迭代,可以参考 Java 8: 从永久代(PermGen)到元空间(Metaspace)String中intern方法的作用

    • 直接内存

    Direct Memory,这部分在JDK1.4中引入NIO(新输入/输出类),一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,可以使用Native函数库直接分配堆外内存,再通过一个存储在Java堆中的DirectByteBuffer对象作为引用进行操作。这样的好处是,可以减少在Java堆和Native堆中的来回复制。具体的实现操作可以参考JVM直接内存。 

     

     

    展开全文
  • 1、首先在浏览器上输入http://hg.openjdk.java.net/jdk7/jdk7 周老师是下载的OpenJDK7... 2、周老师下载的是2012年12月9日发版的,但是我没有找到,以下是周老师安装的 这个是我找到的比较接近的版本: ...5、点...

    1、首先在浏览器上输入 http://hg.openjdk.java.net/jdk7/jdk7     周老师是下载的OpenJDK7 版本

    2、周老师下载的是2012年12月9日发版的,但是我没有找到,以下是周老师安装的

    这个是我找到的比较接近的版本:

     

    3、点击找到的版本号会得到如下的页面:

    4、点击 browse

     

    点击之后会看到这样的包结构

    5、点击zip就会把源码下载下来 如图:

     

    最后如果各位嫌麻烦,可以直接用到我给你的链接地址就好http://hg.openjdk.java.net/jdk7/jdk7/file/58a44f077f6a   进去直接点击zip就开始下载了。

    展开全文
  • 深入理解JVM 之 逃逸分析 逃逸分析的理论学习及实验 准备 逃逸分析的知识:参考 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明 的 11.4.3 逃逸分析部分 逃逸分析的算法:Escape Analysis for Java 此篇...

    深入理解JVM 之 逃逸分析

    逃逸分析的理论学习及实验

    准备

    逃逸分析的知识:参考 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明11.4.3 逃逸分析部分
    逃逸分析的算法:Escape Analysis for Java
    此篇论文略深奥,暂时作为辅助作用,此篇不详细讲论文的内容

    逃逸分析的基本概念

    什么是逃逸分析
    当一个对象指针被多个方法或线程引用时称这个指针发生逃逸。而用来分析这个逃逸的方法,则称为逃逸分析。因此逃逸分析是目前Java虚拟机中比较前沿的优化技术,不是直接优化代码的手段,而是为其他优化措施提供依据的分析技术。

    什么时候进行逃逸分析
    在编译阶段确立逃逸,注意并不是在运行时

    逃逸分析的状态

    1. GlobalEscape(全局逃逸或线程逃逸):
      即一个对象的作用范围逃出了当前方法或者当前线程,有以下几种场景:
    • 对象是一个静态变量
    • 对象是一个已经发生逃逸的对象
    • 对象作为当前方法的返回值
    1. ArgEscape(参数逃逸或方法逃逸):
      即一个对象被作为方法参数传递或者被参数引用,但在调用过程中不会发生全局逃逸,这个状态是通过被调方法的字节码确定的。

    2. NoEscape(没有逃逸):
      即方法中的对象没有发生逃逸。

    几种不同程度的逃逸状态

    public class EscapeStatus {
    
        public static Object globalVariableObject;
    
        public Object instanceObject;
    
        public void globalVariableEscape(){
            globalVariableObject = new Object();  // 静态变量,外部线程可见,发生逃逸 GlobalEscape
        }
    
        public void instanceObjectEscape(){
            instanceObject = new Object();  // 赋值给堆中实例字段,外部线程可见,发生逃逸 GlobalEscape
        }
    
        public Object returnObjectEscape(){
            return new Object();   // 返回实例,外部线程可见,发生逃逸 GlobalEscape
        }
    
        public void argEscape(){
            Object argEscape = new Object();
            callArgEscape(argEscape);   // 一个对象被作为方法参数传递,发生参数逃逸 ArgEscape
        }
    
        public void callArgEscape(Object obj){
    
        }
    
        public void noEscape(){
            Object noEscape = new Object();   // 仅创建线程可见,对象无逃逸 NoEscape
        }
    }
    

    全局逃逸GlobalEscape

    public class GlobalEscape {
        static class User {
            private int id;
            private String name;
        }
    
        private static User user;
    
        public static void foo() {
            user = new User();
            user.id = 1;
            user.name = "Alice";
        }
    
        public static void main(String[] args) {
            foo();
        }
    }
    

    未逃逸NoEscape

    public class NoEscape {
        class User {
            private int id;
            private String name;
        }
    
        public void foo() {
            User user = new User();
            user.id = 1;
            user.name = "Alice";
        }
    
        public static void main(String[] args) {
            NoEscape pos = new NoEscape();
            pos.foo();
        }
    }
    

    根据逃逸分析的不同状态,可以怎样优化

    • 栈上分配(Stack Allocations)
    • 标量替代(Scalar Replacement)
    • 锁消除(Synchronization Elimination)

    相关参数:

    -XX:+PrintGC :打印GC日志
    -XX:+DoEscapeAnalysis :启用逃逸分析(默认打开)
    -XX:+EliminateAllocations :标量替换(默认打开)
    -XX:+EliminateLocks :锁消除(默认打开)
    -XX:+PrintEliminateAllocations查看标量替换情况
    

    JVM五大分区:

    1. 程序计数器
      每个线程拥有一个,记录指令位置。大小是固定的,不会出现oom。线程数越多,使用内存越大?
    2. 虚拟机栈
      每个方法都会生成一个栈帧。存储局部变量表,方法返回地址
    3. 本地方法栈
      与虚拟机栈一致,存储native方法

    4. 几乎所有的对象实例在这里生成,会发生GC
    5. 方法区
      存储类信息、静态变量、常量 会发生oom

    栈上分配

    什么是栈上分配:
    几乎所有的对象实例,都是在堆上分配的,但存在部分例外,栈上分配就是这种除了堆上分配的例外。

    栈上分配有什么好处:
    不需要GC介入去回收这个对象,出栈即释放资源,可以提高性能,原理:由于我们GC每次回收对象的时候,都会触发Stop The World(停止世界),这时候所有线程都停止了,然后我们的GC去进行垃圾回收,如果对象频繁创建在我们的堆中,也就意味这我们也要频繁的暂停所有线程,这对于用户无非是非常影响体验的,栈上分配就是为了减少垃圾回收的次数

    哪些对象可以栈上分配:

    • 小对象(一般几十个byte),在没有逃逸的情况下,可以直接分配在栈上
    • 大对象或者逃逸对象无法在栈上分配 (大对象多大?需要验证)

    栈上分配需要有一定的前提:

    • 开启逃逸分析 (-XX:+DoEscapeAnalysis)
      逃逸分析的作用就是分析对象的作用域是否会逃逸出方法之外,在server虚拟机模式下才可以开启(jdk1.6默认开启)
    • 开启标量替换 (-XX:+EliminateAllocations)
      标量替换的作用是允许将对象根据属性打散后分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。默认该配置为开启

    什么逃逸状态下可进行栈上分配优化:
    NoEscape、ArgEscape

    GlobalEscape 状态下能否进行栈上分配优化:

    // -Xmx10m -Xms10m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations
    // 逃逸分析出是GlobalEscape的逃逸状态,无法进行栈上分配优化。是否开启逃逸分析不会影响结果。
    public class EscapeTest2 {
        static class User {
            private int id;
            private String name;
        }
    
        private static User user;
    
        public static void foo() {
            user = new User();
            user.id = 1;
            user.name = "Alice";
        }
        public static void main(String[] args) {
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                foo();
            }
            long endTime = System.currentTimeMillis();
            System.out.println("time: " + (endTime - beginTime) + "ms");
        }
    
    }
    

    参数为-Xmx10m -Xms10m -XX:+PrintGC -XX:-DoEscapeAnalysis -XX:+EliminateAllocations 关闭逃逸分析
    结果为

    [GC (Allocation Failure)  4709K->2661K(9728K), 0.0003478 secs]
    [GC (Allocation Failure)  4709K->2661K(9728K), 0.0003017 secs]
    此处省略n个gc信息
    [GC (Allocation Failure)  4709K->2661K(9728K), 0.0003630 secs]
    time: 1098ms
    

    参数为-Xmx10m -Xms10m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations 开启逃逸分析
    结果为

    [GC (Allocation Failure)  5065K->3017K(9728K), 0.0003543 secs]
    [GC (Allocation Failure)  5065K->3017K(9728K), 0.0003543 secs]
    此处省略n个gc信息
    [GC (Allocation Failure)  5065K->3017K(9728K), 0.0003322 secs]
    time: 1106ms
    

    以上结果可以说明:逃逸分析出是GlobalEscape的逃逸状态,无法进行栈上分配优化。是否开启逃逸分析不会影响结果。

    NoEscape 状态下是否开启逃逸分析对结果的影响:

    // -Xmx10m -Xms10m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations
    // 逃逸分析出是NoEscape的逃逸状态,可进行栈上分配优化。开启逃逸分析和标量替换会减少gc,提升性能。
    public class EscapeTest1 {
        class User {
            public int id;
            public String name;
        }
    
        public void foo() {
            User user = new User();
            user.id = 1;
            user.name = "Alice";
        }
    
        public static void main(String[] args) {
            long beginTime = System.currentTimeMillis();
            EscapeTest1 escapeTest = new EscapeTest1();
            for (int i = 0; i < 100000000; i++) {
                escapeTest.foo();
            }
            long endTime = System.currentTimeMillis();
            System.out.println("time: " + (endTime - beginTime) + "ms");
        }
    
    }
    

    参数为-Xmx10m -Xms10m -XX:+PrintGC -XX:-DoEscapeAnalysis -XX:+EliminateAllocations 关闭逃逸分析
    结果为

    [GC (Allocation Failure)  4893K->2845K(9728K), 0.0003884 secs]
    [GC (Allocation Failure)  4893K->2845K(9728K), 0.0003271 secs]
    此处省略n个gc信息
    [GC (Allocation Failure)  4893K->2845K(9728K), 0.0004020 secs]
    [GC (Allocation Failure)  4893K->2845K(9728K), 0.0004676 secs]
    time: 1049ms
    

    参数为-Xmx10m -Xms10m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations 开启逃逸分析
    结果为

    [GC (Allocation Failure)  2048K->716K(9728K), 0.0018283 secs]
    [GC (Allocation Failure)  2764K->748K(9728K), 0.0005826 secs]
    [GC (Allocation Failure)  2796K->748K(9728K), 0.0006814 secs]
    time: 32ms
    

    以上结果可以说明:逃逸分析出是NoEscape的逃逸状态,可进行栈上分配优化。开启逃逸分析和标量替换会减少gc,提升性能。

    如何确定大对象界限,使其无法在栈上分配:

    // -Xmx5m -Xms5m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations
    // 数组在长度64以上时会被认为是大对象,无法进行栈上分配优化。
    public class EscapeTest5 {
    
        public static void main(String[] args) {
            int[] intArray;
            long[] longArray;
            Integer[] integerArray;
            Test[] objectArray;
            
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                intArray = new int[64]; //不开启逃逸分析速度7749ms,开启速度18ms
                //intArray = new int[65]; //不开启逃逸分析速度8144ms,开启速度8804ms
                //longArray = new long[64]; //不开启逃逸分析速度14763ms,开启速度27ms
                //longArray = new long[65]; //不开启逃逸分析速度15179ms,开启速度14764ms
                //integerArray = new Integer[64]; //不开启逃逸分析速度7695ms,开启速度18ms 
                //integerArray = new Integer[65]; //不开启逃逸分析速度8120ms,开启速度8027ms
                //objectArray = new Test[64]; //不开启逃逸分析速度8120ms,开启速度18ms
                //objectArray = new Test[65]; //不开启逃逸分析速度8120ms,开启速度8008ms
            }
            long endTime = System.currentTimeMillis();
            System.out.println("time: " + (endTime - beginTime) + "ms");
        }
        public static class Test{
            Integer a;
            String b;
        }
    }
    

    开启逃逸分析参数为:-Xmx5m -Xms5m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations

    实例化长度\类型 int long Integer Test
    64 18ms 27ms 18ms 18ms
    65 8804ms 14764ms 8027ms 8008ms

    关闭逃逸分析参数为:-Xmx5m -Xms5m -XX:+PrintGC -XX:-DoEscapeAnalysis -XX:+EliminateAllocations

    实例化长度\类型 int long Integer Test
    64 7749ms 14763ms 7695ms 8120ms
    65 8144ms 15179ms 8120ms 8120ms

    以上结果可以说明:数组在长度64以上时会被认为是大对象,无法进行栈上分配优化。
    64是可以通过配置(感谢公司大佬帮忙找到)
    HotSpot JVM上的一个默认限制是大于64个元素的数组不会进行逃逸分析优化。这个大小可以通过启动参数-XX:EliminateAllocationArraySizeLimit=n来进行控制,n是数组的大小。

    int和Integer栈上分配的优化情况:

    
    // -Xmx5m -Xms5m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations
    // int是直接分配在栈上的,基础数据类型不需要开启逃逸分析就分配在栈上;
    // Integer在-128~127时,是缓存的对象,在范围外是重新实例化的对象,所以逃逸分析对重新实例化的对象有效
    public class EscapeTest6 {
        public static void main(String[] args) {
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                int x = 127; //不开启逃逸分析速度1ms,开启速度1ms
                //int x = 128; //不开启逃逸分析速度2ms,开启速度1ms
                //Integer x = 127; //不开启逃逸分析速度2ms,开启速度3ms
                //Integer x = 128; //不开启逃逸分析速度661ms,开启速度5ms
            }
            long endTime = System.currentTimeMillis();
            System.out.println("time: " + (endTime - beginTime) + "ms");
        }
    }
    

    以上对多种情况进行试验

    1. int是直接分配在栈上的,基础数据类型不需要开启逃逸分析就分配在栈上;
    2. Integer在-128~127时,是缓存的对象,在范围外是重新实例化的对象,所以逃逸分析对重新实例化的对象有效

    标量替代(Scalar Replacement)

    什么是标量:
    标量是指不可分割的量,如java中基本数据类型和reference类型,相对的一个数据可以继续分解,称为聚合量

    什么是标量替换:
    如果把一个对象拆散,将其成员变量恢复到基本类型来访问就叫做标量替换

    如何进行标量替换:
    如果逃逸分析发现一个对象不会被外部访问,并且该对象可以被拆散,那么经过优化之后,并不直接生成该对象,而是在栈上创建若干个成员变量

    标量替换需要有一定的前提:

    • 开启逃逸分析 (-XX:+DoEscapeAnalysis)
      逃逸分析的作用就是分析对象的作用域是否会逃逸出方法之外,在server虚拟机模式下才可以开启(jdk1.6默认开启)
    • 开启标量替换 (-XX:+EliminateAllocations)
      标量替换的作用是允许将对象根据属性打散后分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。默认该配置为开启

    什么逃逸状态下可进行标量替换优化:
    NoEscape

    锁消除(Synchronization Elimination)

    什么是锁消除:
    线程同步本身是一个相对耗时的过程,如果逃逸分析 能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争, 对这个变量实施的同步措施也就可以安全地消除掉。

    锁消除需要有一定的前提:

    • 开启逃逸分析 (-XX:+DoEscapeAnalysis)
      逃逸分析的作用就是分析对象的作用域是否会逃逸出方法之外,在server虚拟机模式下才可以开启(jdk1.6默认开启)
    • 开启锁消除(-XX:+EliminateLocks)
      线程同步本身是一个相对耗时的过程,如果逃逸分析 能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争, 对这个变量实施的同步措施也就可以安全地消除掉。默认该配置为开启

    什么逃逸状态下可进行锁消除优化:
    NoEscape、ArgEscape

    怎样子的锁才能进行锁消除优化:

    // -Xmx1m -Xms1m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks
    public class SynTest {
        public static void main(String[] args) {
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                SynTest synTest = new SynTest();
                synTest.addSyn();// 带synchronized,开启逃逸分析23ms,不开启逃逸分析156675ms
                //synTest.add();// 不带synchronized,开启逃逸分析29ms,不开启逃逸分析27ms
            }
            long endTime = System.currentTimeMillis();
            System.out.println("time: " + (endTime - beginTime) + "ms");
        }
    
        public static void main1(String[] args) {
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < 10000000; i++) {
                SynTest synTest = new SynTest();
                for(int j = 0; j < 5; j++) {
                    synTest.addSyn();// 带synchronized,开启逃逸分析15676ms,不开启逃逸分析12756ms
                    //synTest.add();// 不带synchronized,开启逃逸分析3543ms,不开启逃逸分析4634ms
                }
            }
            long endTime = System.currentTimeMillis();
            System.out.println("time: " + (endTime - beginTime) + "ms");
        }
    
        public synchronized void addSyn() {
            int a = 0;
        }
    
        public void add() {
            int a = 0;
        }
    }
    

    结论:在循环内部实例化并调用带锁的方法,可以进行锁消除优化
    (为什么循环外面就不可以了呢?)
    锁消除对性能的影响:
    java.lang.StringBuffer是一个使用同步方法的线程安全的类,非同步的java.lang.StringBuilder类来作为它的备选。这两个类都继承了包私有(注:简单来说就是没有修饰符的类)的java.lang.AbstractStringBuilder类,它们的length方法的实现也非常类似。

      @Override
      public int length() {
          return count;
      }
    
       @Override
       public synchronized int length() {
           return count;
       }
    
    // -Xmx1m -Xms1m -XX:+PrintGC -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks
    public class StringTest {
      public static void main(String[] args) {
          long beginTime = System.currentTimeMillis();
          for (int i = 0; i < 100000000; i++) {
              StringBuilder stringBuilder = new StringBuilder(); // 需在里面实例化
              stringBuilder.length();// 不带synchronized,开启锁消除1562ms,不开启锁消除1530ms
              /*StringBuffer stringBuffer = new StringBuffer();
              stringBuffer.length();// 带synchronized,开启锁消除1577ms,不开启锁消除3707ms*/
          }
          long endTime = System.currentTimeMillis();
          System.out.println("time: " + (endTime - beginTime) + "ms");
      }
    }
    

    结论: 开启锁消除对带锁的StringBuffer,性能上有显著的提升。

    疑问

    1. 标量替换和栈上分配的区别?
    2. 大对象不能分配到栈上的界限是由谁决定的?
    3. 锁消除的锁是什么程度的锁,在前面的StringBuffer的例子中,若将实例化StringBuffer放在循环外,开启锁消除不会提升性能,这里是不是出现锁加粗?
    展开全文
  • java虚拟机规范 周志明 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java语言的一个非常...
  • 更多信息我们会在后面的《深入 JVMJVM 类执行机制中详细解说》。 在之前,我们也已经提到, JVM 的逻辑内存模型如下:   我们现在来逐个的看下每个到底是做什么的! 1、程序...
  • 本文是阅读周志明的《深入理解Java虚拟机》一书所做的总结,非常经典的一本书,内容很多话语除了自己总结外采用了书中的内容进行涵盖。以下的题目都是JVM虚拟机的面试题来源于...
  • JVM内存结构(深入理解JAVA虚拟机-周志明 第二章可见)  JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor...
  • Java堆溢出 Java堆用于存储对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间...(摘自《深入理解java虚拟机周志明版》) //JVM Args -XX:+HeapDumpOnOutOfMemoryError -Xms20m -Xmx20m /** ...
  • 深入理解JVM - 阅读笔记之思维导图 - 目录

    千次阅读 多人点赞 2019-04-07 22:36:48
    深入理解JVM - 阅读笔记之思维导图阅读前须知目录总图 阅读前须知 本博客的主要作用是记录我在阅读《深入理解Java虚拟机》(周志明著)时的一些阅读笔记,此笔记是采用思维导图的形式记录的,较原书而言较为精简,但...
  • 最近一直在看周志明老师的《深入理解虚拟机》,总是看了忘,忘了又看,陷入这样无休止的循环当中。抱着纸上得来终觉浅的想法,准备陆续的写几篇学习笔记,梳理知识的脉络并强化一下对知识的掌握。(本文远远谈不上深入...
  • java内存区域是java虚拟机最基础的知识,只有透彻理解这部分内容才能理解其他部分本篇文章在其他地方摘录了不少东西也有在周志明深入理解java虚拟机》学到的不少东西,这本书在国内被奉为虚拟机的圣经还是很有道理...
  • 深入理解jvm(二)

    2014-05-10 18:05:21
    最近重看 BlueDavy 的 《分布式java应用:基础与实践》和周志明的《深入理解java虚拟机》,发现以前有些概念还是理解得不够,缺少实践,所以想在这里记录下自己学习的一些知识点。 今天就来看看OutOfMemoryError ...
  • 深入理解JVM(四)GC分析 命令调优

    千次阅读 2018-11-26 21:33:38
    JVM内存结构(深入理解JAVA虚拟机-周志明 第四章可见) 这里我只是列了调优命令以及工具: 调优命令 Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo jps,JVM Process Status Tool,显示指定系统...
  • JVM规范与深入理解

    2018-12-03 11:34:32
    JVM规范与深入理解,有JVM规范,有周志明深入理解JVM
  • 垃圾回收策略 6.1可进行垃圾回收算法 6.1.1引用计数法 6.1.2可达性分析算法 前言 参考书籍:深入理解java虚拟机--周志明著 目前主流的 Java 虚拟机主要还是 Oracle(前SUN) 的 jdk; linux开源的有 openjdk;...
  • 深入理解Java虚拟机》周志明-第三版 2.2运行时数据区域 运行时数据区: Java虚拟机在执行java程序的过程中将所管理的内存划分为若干个不同的数据区域。 2.2.1程序计数器 ...
  • 最近放暑假在家,没有实验室的工作要做,正好趁这个时间恶补了之前一知半解的JVM知识,使用的主要学习资料是周志明大大的《深入理解Java虚拟机——JVM高级特性与最佳实践》,我仔细拜读了两遍,结合一些优质博文以及...
  • 文章开始之前,首先需要申明,本系列文章讨论的是HotSpot VM,文章中多数观点基于《深入理解Java虚拟机:JVM高级特性与最佳时间 周志明》,笔者如有理解错误,欢迎指正。 在开始探索jvm虚拟机之前,不得不对jvm的...
  • 今天心血来潮,就把去年整理的...鉴于JVM版本比较多,不同的版本都有些差异,有些内容可能不是太准确,但大部分内容敢保证是没问题的,若有问题大家多多指证,整理该资料时主要参考周志明的那本 “深入理解java虚...
  • 链接:https://pan.baidu.com/s/1LPrPn4gWZ6LbnqN2ZmxWzg 提取码:u07m 复制这段内容后打开百度网盘手机App,操作更方便哦
  • JVM内存结构(深入理解JAVA虚拟机-周志明 第三章可见) 概述 垃圾收集 Garbage Collection 通常被称为“GC”,它诞生于1960年 MIT 的 Lisp 语言,经过半个多世纪,目前已经十分成熟了。 jvm 中,程序计数器、...
  • 本文主要参考:《深入理解Java虚拟机》—周志明   0. 概述 我们知道java相比于c,c++的优势在于内存管理。而内存管理由JVM来负责 一个java程序的编译和执行过程如下:  ● java ---------编译----------&...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 726
精华内容 290
关键字:

周志明深入理解jvm