精华内容
下载资源
问答
  • 在堆上分配内存:malloc和free 一般情况下,C程序使用malloc函数族在堆上分配和释放内存。较之brk和sbrk,这些函数具备不少优点: 属于C语言标准的一部分 更易于在多线程程序中使用 接口简单,允许分配小块内存 允许...

     

    在堆上分配内存:malloc和free

     

    一般情况下,C程序使用malloc函数族在堆上分配和释放内存。较之brk和sbrk,这些函数具备不少优点:

    属于C语言标准的一部分

    更易于在多线程程序中使用

    接口简单,允许分配小块内存

    允许随意释放内存块,它们被维护于一张空闲内存列表中,在后续内存分配调用时循环使用

     

    malloc函数在堆上分配参数size字节大小的内存,并返回指向新分配内存起始位置处的指针,其所分配的内存未经初始化。

     

    #include <stdlib.h>
    void *malloc(size_t size);    /* Returns pointer to allocated memory on sucess, or NULL on error */

     

    由于malloc的返回类型为void*,因而可以将其付赋给任意类型的C指针。malloc返回内存块所采用的的字节对齐方式,总是适宜于高效访问任何类型的C语言数据结构。在大多数硬件架构上,这意味着malloc是基于8字节或16字节边界来分配内存的。

     

    SUSv3规定:调用malloc(0)要么返回NULL,要么是一小块可以(并且应该)用free释放的内存。Linux的malloc(0)遵循的是后者。

     

    如果无法再分配内存(或许是因为已经达到了program break所能达到的地址上限),则malloc返回NULL,并设置errno以返回错误信息。

     

    虽然分配内存失败的可能性很小,但所有对malloc以及后续提及的相关函数的调用都应对返回值进行错误检查。

     

    free函数释放ptr参数所指向的内存块,该参数应该是之前由malloc,或者后续描述的其他堆内存分配函数之一所返回的地址。

     

    #include <stdlib.h>
    void free(void *ptr);

     

    一般情况下,free并不降低program break的位置,而是将这块内存填加到空闲内存列表中,供后续的malloc函数循环使用。这么做是出于以下几个原因。

    被释放的内存块通常会位于堆的中间,而非堆的顶部,因而降低program break是不可能的。

    它最大限度地减少了程序必须执行的sbrk调用次数。

    在大多数情况下,降低program break的位置不会对那些分配大量内存的程序有多少帮助,因为它们通常倾向于持有已经分配内存或是反复释放和重新分配内存,而非释放所有内存后再持续运行一段时间。

     

    如果传给free的是一个空指针,那么函数将什么都不做。(换句话说,给free传入一个空指针并不是错误代码。)

     

    在调用free后对参数ptr的任何使用,例如将其再次传递给free,将产生错误,并可能导致不可预知的结果。

    转载于:https://www.cnblogs.com/tuhooo/p/8673359.html

    展开全文
  • 为了防止歧义,可以换个说法:Java对象实例和数组元素都是在堆上分配内存的吗? 答:不一定。满足特定条件时,它们可以在(虚拟机)栈上分配内存。 这和我们平时的理解可能有些不同。虚拟机栈一般是用来存储基本...

    推荐阅读:阿里P8架构师谈:工作1-5年的Java工程师,怎样提高核心竞争力

                      阿里架构师直言:“没有实战都是纸上谈兵”!Redis实战PDF分享

                      奋发图强半年多,终于四面阿里如愿拿到心仪offer定级P7

    为了防止歧义,可以换个说法:Java对象实例和数组元素都是在堆上分配内存的吗?

    答:不一定。满足特定条件时,它们可以在(虚拟机)栈上分配内存。

    Java对象都是在堆上分配内存吗?

    这和我们平时的理解可能有些不同。虚拟机栈一般是用来存储基本数据类型、引用和返回地址的,怎么可以存储实例数据了呢?这是因为Java JIT(just-in-time)编译器进行的两项优化,分别称作逃逸分析(escape analysis)和标量替换(scalar replacement)。JIT是个复杂的话题,本文不赘述,看官如果想进一步了解的话,可以参考这篇文章,它里面提供了几篇有用的参考资料。

    https://dzone.com/articles/java-just-time-compilation

    Java对象都是在堆上分配内存吗?

    中文维基上对逃逸分析的描述基本准确,摘录如下:

    在编译程序优化理论中,逃逸分析是一种确定指针动态范围的方法——分析在程序的哪些地方可以访问到指针。当一个变量(或对象)在子程序中被分配时,一个指向变量的指针可能逃逸到其它执行线程中,或是返回到调用者子程序。
    如果一个子程序分配一个对象并返回一个该对象的指针,该对象可能在程序中被访问到的地方无法确定——这样指针就成功“逃逸”了。如果指针存储在全局变量或者其它数据结构中,因为全局变量是可以在当前子程序之外访问的,此时指针也发生了逃逸。
    逃逸分析确定某个指针可以存储的所有地方,以及确定能否保证指针的生命周期只在当前进程或线程中。

    简单来讲,JVM中的逃逸分析可以通过分析对象引用的使用范围(即动态作用域),来决定对象是否要在堆上分配内存,也可以做一些其他方面的优化。面试官:关于Java性能优化,你有什么技巧

    以下的例子说明了一种对象逃逸的可能性。

      static StringBuilder getStringBuilder1(String a, String b) {
        StringBuilder builder = new StringBuilder(a);
        builder.append(b);
        return builder;   // builder通过方法返回值逃逸到外部
      }
    
      static String getStringBuilder2(String a, String b) {
        StringBuilder builder = new StringBuilder(a);
        builder.append(b);
        return builder.toString();  // builder范围维持在方法内部,未逃逸
      }
    

    以JDK 1.8为例,可以通过设置JVM参数-XX:+DoEscapeAnalysis-XX:-DoEscapeAnalysis来开启或关闭逃逸分析(默认当然是开启的)。下面先写一个没有对象逃逸的例子。

    public class EscapeAnalysisTest {
      public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 5000000; i++) {
          allocate();
        }
        System.out.println((System.currentTimeMillis() - start) + " ms");
        Thread.sleep(600000);
      }
    
      static void allocate() {
        MyObject myObject = new MyObject(2019, 2019.0);
      }
    
      static class MyObject {
        int a;
        double b;
    
        MyObject(int a, double b) {
          this.a = a;
          this.b = b;
        }
      }
    }
    

    然后通过开启和关闭DoEscapeAnalysis开关观察不同。

    关闭逃逸分析

    ~ java -XX:-DoEscapeAnalysis EscapeAnalysisTest
    76 ms
    ~ jmap -histo 26031
     num     #instances         #bytes  class name
    ----------------------------------------------
       1:       5000000      120000000  me.lmagics.EscapeAnalysisTest$MyObject
       2:           636       12026792  [I
       3:          3097        1524856  [B
       4:          5088         759960  [C
       5:          3067          73608  java.lang.String
       6:           623          71016  java.lang.Class
       7:           727          43248  [Ljava.lang.Object;
       8:           532          17024  java.io.File
       9:           225          14400  java.net.URL
      10:           334          13360  java.lang.ref.Finalizer
    # ......
    

    开启逃逸分析

    ~ java -XX:+DoEscapeAnalysis EscapeAnalysisTest
    4 ms
    ~ jmap -histo 26655
     num     #instances         #bytes  class name
    ----------------------------------------------
       1:           592       11273384  [I
       2:         90871        2180904  me.lmagics.EscapeAnalysisTest$MyObject
       3:          3097        1524856  [B
       4:          5088         759952  [C
       5:          3067          73608  java.lang.String
       6:           623          71016  java.lang.Class
       7:           727          43248  [Ljava.lang.Object;
       8:           532          17024  java.io.File
       9:           225          14400  java.net.URL
      10:           334          13360  java.lang.ref.Finalizer
    # ......
    

    可见,关闭逃逸分析之后,堆上有5000000个MyObject实例,而开启逃逸分析之后,就只剩下90871个实例了,不管是实例数还是内存占用都只有原来的2%不到。另外,如果把堆内存限制得小一点(比如加上-Xms10m -Xmx10m),并且打印GC日志(-XX:+PrintGCDetails)的话,关闭逃逸分析还会造成频繁的GC,开启逃逸分析就没有这种情况。这说明逃逸分析确实降低了堆内存的压力。

    但是,逃逸分析只是栈上内存分配的前提,接下来还需要进行标量替换才能真正实现。

    所谓标量,就是指JVM中无法再细分的数据,比如int、long、reference等。相对地,能够再细分的数据叫做聚合量。仍然考虑上面的例子,MyObject就是一个聚合量,因为它由两个标量a、b组成。通过逃逸分析,JVM会发现myObject没有逃逸出allocate()方法的作用域,标量替换过程就会将myObject直接拆解成a和b,也就是变成了:

      static void allocate() {
        int a = 2019;
        double b = 2019.0;
      }
    

    可见,对象的分配完全被消灭了,而int、double都是基本数据类型,直接在栈上分配就可以了。所以,在对象不逃逸出作用域并且能够分解为纯标量表示时,对象就可以在栈上分配。

    JVM提供了参数-XX:+EliminateAllocations来开启标量替换,默认仍然是开启的。显然,如果把它关掉的话,就相当于禁止了栈上内存分配,只有逃逸分析是无法发挥作用的。在Debug版JVM中,还可以通过参数-XX:+PrintEliminateAllocations来查看标量替换的具体情况。

    除了标量替换之外,通过逃逸分析还能实现同步消除(synchronization elision),当然它与本文的主题无关了。举个例子:

      private void someMethod() {
        Object lockObject = new Object();
        synchronized (lockObject) {
          System.out.println(lockObject.hashCode());
        }
      }
    

    lockObject这个锁对象的生命期只在someMethod()方法中,并不存在多线程访问的问题,所以synchronized块并无意义,会被优化掉:

      private void someMethod() {
        Object lockObject = new Object();
        System.out.println(lockObject.hashCode());
      }

     

    展开全文
  • 对象和数组并不是都在堆上分配内存的 原文 前段时间,给星球的球友们专门码了一篇文章《深入分析Java的编译原理》,其中深入的介绍了Java中的javac编译和JIT编译的区别及原理。并在文中提到:JIT编译除了...

    对象和数组并不是都在堆上分配内存的

    原文

    前段时间,给星球的球友们专门码了一篇文章《深入分析Java的编译原理》,其中深入的介绍了Java中的javac编译和JIT编译的区别及原理。并在文中提到:JIT编译除了具有缓存的功能外,还会对代码做各种优化,比如:逃逸分析、 锁消除、 锁膨胀、 方法内联、 空值检查消除、 类型检测消除、 公共子表达式消除等。

    有球友阅读完这部分内容后,对JVM产生了浓厚的兴趣,自己回去专门学习了一下,在学习过程中遇到一个小问题,关于Java内存分配的。所以和我在微信上做过简单的交流。主要涉及到Java中的堆和栈、数组内存分配、逃逸分析、编译优化等技术及原理。本文也是关于这部分知识点的分享。

    JVM内存分配策略

    关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识:

    1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、堆、程序计数器等。

    2、我们通常认为JVM中运行时数据存储包括堆和栈。这里所提到的栈其实指的是虚拟机栈,或者说是虚拟栈中的局部变量表。

    3、栈中存放一些基本类型的变量数据(int/short/long/byte/float/double/Boolean/char)和对象引用。

    4、堆中主要存放对象,即通过new关键字创建的对象。

    5、数组引用变量是存放在栈内存中,数组元素是存放在堆内存中。

    在《深入理解Java虚拟机中》关于Java堆内存有这样一段描述:

    但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。

    这里只是简单提了一句,并没有深入分析,很多人看到这里由于对JIT、逃逸分析等技术不了解,所以也无法真正理解上面这段话的含义。

    PS:这里默认大家都了解什么是JIT,不了解的朋友可以先自行Google了解下,或者加入我的知识星球,阅读那篇球友专享文章。

    其实,在编译期间,JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做逃逸分析

    逃逸分析

    逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。

    逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。

    例如:

    public static StringBuffer craeteStringBuffer(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb;
    }

    StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。

    上述代码如果想要StringBuffer sb不逃出方法,可以这样写:

    public static String createStringBuffer(String s1, String s2) {
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        return sb.toString();
    }

    不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。

    使用逃逸分析,编译器可以对代码做如下优化:

    一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

    二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。

    三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

    上面的关于同步省略的内容,我在《深入理解多线程(五)—— Java虚拟机的锁优化技术》中有介绍过,即锁优化中的锁消除技术,依赖的也是逃逸分析技术。

    本文,主要来介绍逃逸分析的第二个用途:将堆分配转化为栈分配。

    其实,以上三种优化中,栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点,这里就不展开介绍了。如果大家感兴趣,我后面专门出一篇文章,全面介绍下逃逸分析。

    在Java代码运行时,通过JVM参数可指定是否开启逃逸分析, -XX:+DoEscapeAnalysis : 表示开启逃逸分析 -XX:-DoEscapeAnalysis : 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定-XX:-DoEscapeAnalysis

    对象的栈上内存分配

    我们知道,在一般情况下,对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。

    我们来看以下代码:

    public static void main(String[] args) {
        long a1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            alloc();
        }
        // 查看执行时间
        long a2 = System.currentTimeMillis();
        System.out.println("cost " + (a2 - a1) + " ms");
        // 为了方便查看堆内存中对象个数,线程sleep
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }
    

    private static void alloc() {
    User user = new User();
    }

    static class User {

    }

    其实代码内容很简单,就是使用for循环,在代码中创建100万个User对象。

    我们在alloc方法中定义了User对象,但是并没有在方法外部引用他。也就是说,这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后,就可以对其内存分配进行优化。

    我们指定以下JVM参数并运行:

    -Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError 

    在程序打印出 cost XX ms 后,代码运行结束之前,我们使用[jmap][1]命令,来查看下当前堆内存中有多少个User对象:

      ~ jps
    2809 StackAllocTest
    2810 Jps
      ~ jmap -histo 2809
    

    num #instances #bytes class name
    ----------------------------------------------
    1: 524 87282184 [I
    2: 1000000 16000000 StackAllocTest$User
    3: 6806 2093136 [B
    4: 8006 1320872 [C
    5: 4188 100512 java.lang.String
    6: 581 66304 java.lang.Class

    从上面的jmap执行结果中我们可以看到,堆中共创建了100万个StackAllocTest$User实例。

    在关闭逃避分析的情况下(-XX:-DoEscapeAnalysis),虽然在alloc方法中创建的User对象并没有逃逸到方法外部,但是还是被分配在堆内存中。也就说,如果没有JIT编译器优化,没有逃逸分析技术,正常情况下就应该是这样的。即所有对象都分配到堆内存中。

    接下来,我们开启逃逸分析,再来执行下以上代码。

    -Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError 

    在程序打印出 cost XX ms 后,代码运行结束之前,我们使用jmap命令,来查看下当前堆内存中有多少个User对象:

      ~ jps
    709
    2858 Launcher
    2859 StackAllocTest
    2860 Jps
      ~ jmap -histo 2859
    

    num #instances #bytes class name
    ----------------------------------------------
    1: 524 101944280 [I
    2: 6806 2093136 [B
    3: 83619 1337904 StackAllocTest$User
    4: 8006 1320872 [C
    5: 4188 100512 java.lang.String
    6: 581 66304 java.lang.Class

    从以上打印结果中可以发现,开启了逃逸分析之后(-XX:+DoEscapeAnalysis),在堆内存中只有8万多个StackAllocTest$User对象。也就是说在经过JIT优化之后,堆内存中分配的对象数量,从100万降到了8万。

    除了以上通过jmap验证对象个数的方法以外,读者还可以尝试将堆内存调小,然后执行以上代码,根据GC的次数来分析,也能发现,开启了逃逸分析之后,在运行期间,GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配,所以GC次数有了明显的减少。

    总结

    所以,如果以后再有人问你:是不是所有的对象和数组都会在堆内存分配空间?

    那么你可以告诉他:不一定,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有User对象都没有在堆上分配。

    展开全文
  • 原创 | 对象一定在堆上分配内存?面试官说你可以走了
    展开全文
  • 转载自 对象并不一定都是在堆上分配内存的JVM内存分配策略关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识:1、根据Java虚拟机规范,Java虚拟机所管理的内存包括...
  • 在堆上分配内存 基本语法1: 类型标识符 *指针变量名 =new 类型标识符(初值); 也可以为一个指针分配一个数组大小内存,并将首地址赋值给指针变量。 语法2: 类型标识符 *指针变量名=new 类型标识符[size] ...
  • 可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。... 由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配
  • 但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。 1.JVM内存分配策略 关于JVM的内存结构及内存分配...
  • JVM内存分配策略 关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识: 1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、、程序...
  • 在堆上分配内存—————————可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲,stack上分配的内存系统自动...
  • JVM内存分配策略 关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识: 1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、、程序...
  • 8、在堆上分配内存

    2004-10-12 00:15:00
    —————————可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个...由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的
  • 前段时间,给星球的球友们专门码了一...并文中提到:JIT编译除了具有缓存的功能外,还会对代码做各种优化,比如:逃逸分析、 锁消除、 锁膨胀、 方法内联、 空值检查消除、 类型检测消除、 公共子表达式消除等。 ...
  • JVM内存分配策略 关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识: 1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、、程序...
  • 文中提到:JIT编译除了具有缓存的功能外,还会对代码做各种优化,比如:逃逸分析、 锁消除、 锁膨胀、 方法内联、 空值检查消除、 类型检测消除、 公共子表达式消除等。 有球友阅读完这部分内容后,对JVM产生了...
  • 对象的栈上内存分配 总结 前段时间,给星球的球友们专门码了一篇文章《深入分析Java的编译原理》,其中深入的介绍了Java中的javac编译和JIT编译的区别及原理。并文中提到:JIT编译除了具有缓存的功能外,还会对...
  • // Test11.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "Test11.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE_
  • 最近学习了在堆上的内存分配,下面是一些总结和笔记 //一,通过返回值分配内存 int *getspace() { //malloc函数的返回值是分配区域的起始地址 ...//在堆上动态分配内存 if (NULL == p) { return NULL;
  • 怎么api hook以后的堆上分配内存,写入一个函数代码,并且将它作为addressof返回呢?
  • C#new出来的结构体内存分配在堆上

    千次阅读 2018-09-11 22:46:00
    我的直觉是但凡使用new的东西都在堆上分配内存,除非C#对结构体做了特殊处理。 new int[10]这个说明不了什么,因为数组是引用类型,必然在堆上分配。 如何验证? 利用UNITY的profiler来观察内存变化 :new一个内存...
  • 堆内存分配

    2012-05-18 08:55:42
    进程私有的内存里分配,又有两种分配情况,一种基于栈式的内存分配,另一种是基于内存的分配。c++里使用内存分配是使用HeapAlloc函数来实现的,也就是实现new操作符分配内存时会调这个函数。
  • java内存管理其实主要做了2件事,一个是内存分配一个是内存回收 下面说说内存分配的事,内存分配大致可以理解为在新建一个...本文主要聊一聊在堆上分配内存的几条普遍的规则:   3.6.1、对象优先分配到eden   ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,709
精华内容 3,483
关键字:

在堆上分配内存