• new 的对象如何不分配在堆而分配在栈上(方法逃逸等)


    当能够明确对象不会发生逃逸时,就可以对这个对象做一个优化,不将其分配到堆上,而是直接分配到栈上,这样在方法结束时,这个对象就会随着方法的出栈而销毁,这样就可以减少垃圾回收的压力。

    如方法逃逸。

    逃逸分析,是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。

    通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用使用范围从而决定是否要将这个对象分配到上。 


    在计算机语言编译器优化原理中,逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。

    变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他过程或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)

     
    Java在Java SE 6u23以及以后的版本中支持并默认开启了逃逸分析的选项。Java的 HotSpot JIT编译器,能够在方法重载或者动态加载代码的时候对代码进行逃逸分析,同时Java对象在堆上分配和内置线程的特点使得逃逸分析成Java的重要功能。

    通过逃逸分析来决定某些实例或者变量是否要在中进行分配,如果开启了逃逸分析,即可将这些变量直接在上进行分配,而非堆上进行分配。这些变量的指针可以被全局所引用,或者其其它线程所引用。

    上的空间一般而言是非常小的,只能存放若干变化的数据结构,大容量的存储结构是做不到。


    所以,逃逸分析的效果只能在特定场景下,满足高频高数量的容量比较小的变量分配结构,才可以生效。

    在Java中每一个对象都有一定的作用域,理论上,一个对象在一块代码中构造,那么也应该在这块代码中被回收,但是实际上,我们经常会让一个对象存活更长的时间,超过定义它的代码块,这就好比一个人逃出了生他养他的地方,我们将这种现象称为逃逸

    逃逸按照行为不同有可以分为方法逃逸线程逃逸

    方法逃逸是指在某一个方法中构造的对象,在该方法外部可以继续访问这个对象。产生方法逃逸一般是由于返回值返回,或者是将对象的引用设置到传入的参数中,如下图展示了两种产生方法逃逸的例子。

    线程逃逸则是在一个线程中构造的对象,能够在另一个线程中使用。这种情况是由于同一个对象被多个线程使用,产生资源占用而导致。下图就是一个通过共享静态变量,来实现线程逃逸的例子,这个例子中的resource对象在多线程的环境下会产生线程逃逸。

     

    至此,我们了解了如何判断一个对象是否会产生逃逸,那么对象逃逸有什么用呢?如果我们能够明确肯定一个对象不会产生逃逸,那么就可以对其进行很多的优化。下面本文就来介绍一下,Java虚拟机在确定对象不发生逃逸的情况下,所进行的一些高效的优化。

    1. 栈上分配

      众所周知,Java中对象时分配在堆上的,在初始化时,会在堆上分配一块空间,当这个对象不再使用时,会在之后发生垃圾回收时被回收,这是一个Java对象正常的生命周期。但是当能够明确对象不会发生逃逸时,就可以对这个对象做一个优化,不将其分配到堆上,而是直接分配到上,这样在方法结束时,这个对象就会随着方法的出栈销毁,这样就可以减少垃圾回收的压力

    2. 同步消除

      多线程中,对于一个变量操作进行同步操作是效率很低的,当我们确定一个对象不会发生逃逸时,那么就没有必要对这个对象进行同步操作,所以如果代码中有对这种变量操作的同步操作,JVM将会取消同步,从而提升性能。

    3. 标量替换

      标量指的是没有办法再分解为更小的数据的类型,即Java中的基本类型,我们平时定义的类都属于聚合量。标量替换即是将一个聚合量拆成多个标量来替换,即用一些基本类型来代替一个对象。如果明确对象不会发生逃逸,并且可以进行标量替换的话,那么就可以不创建这个对象,而是直接使用基本类型来代替,这样也就可以节省创建和销毁对象的开销。

    虽然基于逃逸技术的优化能够提升程序运行时的性能,但是在实际生产中,对象逃逸的分析默认是不开启的。这是因为分析一个对象是否会发生逃逸消耗比较大,所以,开启逃逸分析并进行这些优化之后得到的效果,并不一定就比不进行优化更好。如果确定开启逃逸分析效率更好,那么可以使用参数-XX:+DoEscapeAnalysis来开启逃逸分析。

    TLAB

    JVM在内存在新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation(美: [.ælə'keɪʃ(ə)n] ) buffer)
    默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。
    也就是说,Java中每个线程都会有自己的缓冲区称作TLAB(Thread-local allocation buffer),每个TLAB都只有一个线程可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的锁进行同步,也就是说,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可。
     
    总结: 多个线程同时new对象会分配内存, 容易造成线程安全问题(多个线程操作同一块内存), 所以多个线程new小对象时, 如果使用同一块内存, 则需要加锁, 但是如果每个线程有一些自己new对象分配内存独有的区域(TLAB), 就不用加锁, 没有锁开销, 也是提高了效率.
     

    Java对象分配的过程

    1. 编译器通过逃逸分析,确定对象是在上分配还是在上分配。如果是在堆上分配,则进入选项2.
    2. 如果tlab_top + size <= tlab_end,则在在TLAB上直接分配对象并增加tlab_top 的值,如果现有的TLAB不足以存放当前对象则3.
    3. 重新申请一个TLAB,并再次尝试存放当前对象。如果放不下,则4.
    4. 在Eden区加锁(这个区是多线程共享的),如果eden_top + size <= eden_end则将对象存放在Eden区,增加eden_top 的值,如果Eden区不足以存放,则5.
    5. 执行一次Young GC(minor collection)。
    6. 经过Young GC之后,如果Eden区任然不足以存放当前对象,则直接分配到老年代。

     

    来源:https://blog.csdn.net/blueheart20/article/details/76167489

    来源:https://blog.csdn.net/u011277123/article/details/53908270

    来源:https://blog.csdn.net/yangzl2008/article/details/43202969

  • 相关阅读:
    JS 关于 URL 的编码或解码方法
    PHP 获取上一个页面的url
    踩坑 Uncaught RangeError: Maximum call stack size exceeded
    Wordpress 数据库查询错误 Call to a member function get_results() on null
    Chrome autocomplete="off"无效
    js 遍历对象属性(for in、Object.keys、Object.getOwnProperty) 以及高效地输出 js 数组
    PHP PDO fetch() 详解
    Wordpress 自定义文章类型添加 Categoried、Tags
    Mac: mac git 的安装 及实现自动补全
    Uncaught TypeError: Cannot read property of undefined In JavaScript
  • 原文地址:https://www.cnblogs.com/theRhyme/p/9528761.html
Copyright © 2020-2023  润新知