JDK8中值传递和引用传递分析(java中堆栈的区别)

1.实现两个数字的交换

public class SwapDemo {

 public static void main(String[] args) {

   Integer a=10,b=20;

   System.out.println(“before a=”+a+”,b=”+b);

   swap(a,b);

   System.out.println(“after a=”+a+”,b=”+b);

 }

 private static void swap(Integer i1, Integer i2) {

   // 算法实现两个数字进行交换

 }

}

这个算法的实现方式有多种,但是如果不理解值Java中的值传递和引用传递的差别(实际上也是对堆栈的不理解)就会很容易给出错误的答案:Integer temp=i1;i1=i2;i2=temp;

2.Java中堆栈的区别

众所周知Java应用程序是运行在JVM虚拟机内部的,故Java中的堆和栈都是指JVM内存中的堆和栈。堆和栈都是内存中的一部分且都有着不同的作用,而且一个程序需要在这片区域上分配内存空间。

a.栈的介绍

栈(stack)是一个先进后出的数据结构,通常用于保存方法(函数)中的参数和局部变量。在Java中所有基本类型和引用类型都在栈中存储。栈中数据的生存空间一般在当前scopes内(就是由{…}括起来的区域)。 

b.堆的介绍

堆(heap)是一个可动态申请的内存空间(由操作系统维护的记录空闲内存空间的链表),在Java中,所有使用new构造出来的对象都在堆中存储,当垃圾回收器检测到某对象未被引用,则自动销毁该对象。所以理论上说Java中对象的生存空间是没有限制的,只要有引用类型指向它,则它就可以在任意地方被使用。

c.堆栈的区别

(1)堆和栈最主要的区别就是栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象,无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。

(2)栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。

(3)栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出StackOverFlowError。堆内存没有可用的空间存储生成的对象,JVM会抛出OutOfMemoryError。

(4)栈的内存要远远小于堆内存,当使用递归的时候发现栈很快就会填满,如果递归没有及时跳出就很可能发生StackOverFlowError问题,可以通过-Xss选项设置栈内存的大小。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。

(5)查看堆和栈的默认值

查看堆的默认值,其中InitialHeapSize为最开始的堆的大小,MaxHeapSize为堆的最大值。

[root@lceric ~]# java -XX:+PrintFlagsFinal -version | grep HeapSize

看栈的默认值,其中ThreadStackSize为栈内存的大小。

[root@lceric ~]# java -XX:+PrintFlagsFinal -version | grep ThreadStackSize

3.堆和栈的详细概述

Java把内存划分成两种:一种是栈内存,一种是堆内存。 

(1)栈(stack)与堆(heap)都是Java用来在RAM中存放数据的地方。Java自动管理栈和堆,程序员不能直接地设置栈或堆。

(2)栈的优势是存取速度比堆要快,仅次于直接位于CPU中的寄存器,但缺点是存在栈中的数据大小与生存期必须是确定的缺乏灵活性,另外栈数据可以共享。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是由于要在运行时动态分配内存,存取速度较慢。  

(3)Java中的数据类型有两种。  

基本类型(primitive types)共有8种,即int, short, long, byte, float, double, boolean, char存在于栈中。 

包装类数据如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。  

(4)堆和栈的使用

(4.a)在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。

(4.b)当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

(4.c)栈中主要存放的基本类型的变量有int,short,long,byte,float,double,boolean,char以及对象句柄。

(4.d)堆内存用来存放由new创建的对象和数组。在堆中分配的内存由Java虚拟机的自动垃圾回收器来管理。

(4.e)在堆中产生了一个数组或对象后还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。

(4.f)Java的堆是一个运行时数据区,类的对象从中分配空间,这些对象通过new、newarray、anewarray和multianewarray等指令建立,不需要程序代码来显式的释放。

Note:只要是用new()来新建对象的都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同也不会与栈中的数据共享。数据类型包装类的值不可修改,不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

4.Integer类的概述

当作{@code int}返回当前{@code Integer}的值。

public int intValue() {

  return value;

}

当前{@code Integer}的值。

private final int value;

返回一个Integer实例来表示特定的int值。如果一个新创建的Integer实例没有被要求,此方法应该一般的优先于构造方法Integer(int)前使用。此方法通过缓存频繁请求的值,很可能显示的让步于更好的空间和时间性能。这方法通常缓存的值的范围是-128到127,超过这个范围的值也可以被缓存。

public static Integer valueOf(int i) {

  if (i >= IntegerCache.low && i <= IntegerCache.high)

      return IntegerCache.cache[i + (-IntegerCache.low)];

  return new Integer(i);

}

Cache to support the object identity semantics of autoboxing for values between -128 and 127 (inclusive) as required by JLS。

当前缓存在首次使用时初始化,缓存的的大小被{@code -XX:AutoBoxCacheMax=<size>}选择控制。在虚拟机初始化的过程中,java.lang.Integer.IntegerCache.high属性也许会被设置保存在sun.misc.VM类文件的私有的系统属性中。

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() {}

}

5.实现分析

(1)Java中的装箱和拆箱

当在应用中定义Integer a=10的时候,JVM会自动装箱成Integer a=Integer.valueOf(10)。如果i的值在IntegerCache.low和IntegerCache.high之间,那么就直接从IntegerCache里面去获取,如果i的值超出了这个范围,那么就会新建一个Integer类型的对象,这个值默认是在-128-127之间。

(2)Java中的传值处理

Java中按值传递意味着将一个参数传递给另外一个函数时,函数接收到的是原始值的一个副本,因此不管副本如何变化对于原始值没有任何反应,换句话说就是swap(int a,int b)是永远也实现不了真正意义上的数值交换。

Java按引用传递,它传递的是引用副本指向自己的地址而不是自己的副本,故是可以实现真正意义上的数值交换。

(3)swap()方法的实现分析

当定义Integer a = 10, b = 20的时候会在stack内存里面开辟两个空间stack(a)和stack(b),同时对应的在heap里面开辟两个空间heap(a)和heap(b)分别保存值10和20。

当使用按值传递的方法使用swap()来实现两个数值的交换时,同样也会在stack内存里面开辟两个空间stack(i)和stack(j),由于栈内存是共享的(栈内存共享是指相同数值存在就直接使用,不存在的话就开辟新的空间存值),故不会在heap内存里面开辟两个新的栈空间而是分别指向现有的heap(a)和heap(b)。当swap()方法执行完毕后,stack(i)和stack(j)在heap内存里面指向的值应该分别是heap(b)和heap(a),但是stack(a)和stack(b)在heap内存里面指向的值依旧分别是heap(a)和heap(b),故没有实现数据交换。

当使用按引用传递的方法使用swap()来实现两个数值的交换时,由于传递的是引用的副本故不会在stack内存里面开辟两个新空间stack(i)和stack(j)。当swap()方法执行完毕后stack内存中的stack(a)和stack(b)在heap内存里面指向的值分别是heap(b)和heap(a),从而实现数据交换。

6.swap方法的实现

import java.lang.reflect.Field;

public class SwapDemo {

 public static void main(String[] args) {

   Integer a = 10, b = 20;

   System.out.println(“before a=” + a + “,b=” + b);

   swap(a, b);

   System.out.println(“after a=” + a + “,b=” + b);

 }

 private static void swap(Integer i1, Integer i2) {

   try {

     Field f = Integer.class.getDeclaredField(“value”);

     f.setAccessible(true);

     Integer temp = new Integer(i1);

     // 使用Integer temp = i1.intValue()会在栈内存开辟新的空间

     // i1.intValue->Integer.valueOf(i1.intValue).intValue();

     f.set(i1, i2.intValue());

     // f.setInt(i1,i2.intValue())不会做装箱操作

     f.set(i2, temp);

    } catch (NoSuchFieldException e) {

       e.printStackTrace();

    } catch (SecurityException e) {

       e.printStackTrace();

    } catch (IllegalArgumentException e) {

       e.printStackTrace();

    } catch (IllegalAccessException e) {

       e.printStackTrace();

  }

}

私有的成员属性是不能直接通过反射去获取的f.setAccessible(true);

Note:当定义为Integer a=1000,b=2000的时候使用int temp=i1.intValue()也可以实现数据的交换,因为超出范围时int temp = i1.intValue()会直接new新的Integer对象。




原址:http://ericchunli.iteye.com/blog/2358155

1.实现两个数字的交换

public class SwapDemo {

 public static void main(String[] args) {

   Integer a=10,b=20;

   System.out.println(“before a=”+a+”,b=”+b);

   swap(a,b);

   System.out.println(“after a=”+a+”,b=”+b);

 }

 private static void swap(Integer i1, Integer i2) {

   // 算法实现两个数字进行交换

 }

}

这个算法的实现方式有多种,但是如果不理解值Java中的值传递和引用传递的差别(实际上也是对堆栈的不理解)就会很容易给出错误的答案:Integer temp=i1;i1=i2;i2=temp;

2.Java中堆栈的区别

众所周知Java应用程序是运行在JVM虚拟机内部的,故Java中的堆和栈都是指JVM内存中的堆和栈。堆和栈都是内存中的一部分且都有着不同的作用,而且一个程序需要在这片区域上分配内存空间。

a.栈的介绍

栈(stack)是一个先进后出的数据结构,通常用于保存方法(函数)中的参数和局部变量。在Java中所有基本类型和引用类型都在栈中存储。栈中数据的生存空间一般在当前scopes内(就是由{…}括起来的区域)。 

b.堆的介绍

堆(heap)是一个可动态申请的内存空间(由操作系统维护的记录空闲内存空间的链表),在Java中,所有使用new构造出来的对象都在堆中存储,当垃圾回收器检测到某对象未被引用,则自动销毁该对象。所以理论上说Java中对象的生存空间是没有限制的,只要有引用类型指向它,则它就可以在任意地方被使用。

c.堆栈的区别

(1)堆和栈最主要的区别就是栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象,无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。

(2)栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。

(3)栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出StackOverFlowError。堆内存没有可用的空间存储生成的对象,JVM会抛出OutOfMemoryError。

(4)栈的内存要远远小于堆内存,当使用递归的时候发现栈很快就会填满,如果递归没有及时跳出就很可能发生StackOverFlowError问题,可以通过-Xss选项设置栈内存的大小。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。

(5)查看堆和栈的默认值

查看堆的默认值,其中InitialHeapSize为最开始的堆的大小,MaxHeapSize为堆的最大值。

[root@lceric ~]# java -XX:+PrintFlagsFinal -version | grep HeapSize

看栈的默认值,其中ThreadStackSize为栈内存的大小。

[root@lceric ~]# java -XX:+PrintFlagsFinal -version | grep ThreadStackSize

3.堆和栈的详细概述

Java把内存划分成两种:一种是栈内存,一种是堆内存。 

(1)栈(stack)与堆(heap)都是Java用来在RAM中存放数据的地方。Java自动管理栈和堆,程序员不能直接地设置栈或堆。

(2)栈的优势是存取速度比堆要快,仅次于直接位于CPU中的寄存器,但缺点是存在栈中的数据大小与生存期必须是确定的缺乏灵活性,另外栈数据可以共享。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是由于要在运行时动态分配内存,存取速度较慢。  

(3)Java中的数据类型有两种。  

基本类型(primitive types)共有8种,即int, short, long, byte, float, double, boolean, char存在于栈中。 

包装类数据如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。  

(4)堆和栈的使用

(4.a)在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。

(4.b)当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

(4.c)栈中主要存放的基本类型的变量有int,short,long,byte,float,double,boolean,char以及对象句柄。

(4.d)堆内存用来存放由new创建的对象和数组。在堆中分配的内存由Java虚拟机的自动垃圾回收器来管理。

(4.e)在堆中产生了一个数组或对象后还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。

(4.f)Java的堆是一个运行时数据区,类的对象从中分配空间,这些对象通过new、newarray、anewarray和multianewarray等指令建立,不需要程序代码来显式的释放。

Note:只要是用new()来新建对象的都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同也不会与栈中的数据共享。数据类型包装类的值不可修改,不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

4.Integer类的概述

当作{@code int}返回当前{@code Integer}的值。

public int intValue() {

  return value;

}

当前{@code Integer}的值。

private final int value;

返回一个Integer实例来表示特定的int值。如果一个新创建的Integer实例没有被要求,此方法应该一般的优先于构造方法Integer(int)前使用。此方法通过缓存频繁请求的值,很可能显示的让步于更好的空间和时间性能。这方法通常缓存的值的范围是-128到127,超过这个范围的值也可以被缓存。

public static Integer valueOf(int i) {

  if (i >= IntegerCache.low && i <= IntegerCache.high)

      return IntegerCache.cache[i + (-IntegerCache.low)];

  return new Integer(i);

}

Cache to support the object identity semantics of autoboxing for values between -128 and 127 (inclusive) as required by JLS。

当前缓存在首次使用时初始化,缓存的的大小被{@code -XX:AutoBoxCacheMax=<size>}选择控制。在虚拟机初始化的过程中,java.lang.Integer.IntegerCache.high属性也许会被设置保存在sun.misc.VM类文件的私有的系统属性中。

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() {}

}

5.实现分析

(1)Java中的装箱和拆箱

当在应用中定义Integer a=10的时候,JVM会自动装箱成Integer a=Integer.valueOf(10)。如果i的值在IntegerCache.low和IntegerCache.high之间,那么就直接从IntegerCache里面去获取,如果i的值超出了这个范围,那么就会新建一个Integer类型的对象,这个值默认是在-128-127之间。

(2)Java中的传值处理

Java中按值传递意味着将一个参数传递给另外一个函数时,函数接收到的是原始值的一个副本,因此不管副本如何变化对于原始值没有任何反应,换句话说就是swap(int a,int b)是永远也实现不了真正意义上的数值交换。

Java按引用传递,它传递的是引用副本指向自己的地址而不是自己的副本,故是可以实现真正意义上的数值交换。

(3)swap()方法的实现分析

当定义Integer a = 10, b = 20的时候会在stack内存里面开辟两个空间stack(a)和stack(b),同时对应的在heap里面开辟两个空间heap(a)和heap(b)分别保存值10和20。

当使用按值传递的方法使用swap()来实现两个数值的交换时,同样也会在stack内存里面开辟两个空间stack(i)和stack(j),由于栈内存是共享的(栈内存共享是指相同数值存在就直接使用,不存在的话就开辟新的空间存值),故不会在heap内存里面开辟两个新的栈空间而是分别指向现有的heap(a)和heap(b)。当swap()方法执行完毕后,stack(i)和stack(j)在heap内存里面指向的值应该分别是heap(b)和heap(a),但是stack(a)和stack(b)在heap内存里面指向的值依旧分别是heap(a)和heap(b),故没有实现数据交换。

当使用按引用传递的方法使用swap()来实现两个数值的交换时,由于传递的是引用的副本故不会在stack内存里面开辟两个新空间stack(i)和stack(j)。当swap()方法执行完毕后stack内存中的stack(a)和stack(b)在heap内存里面指向的值分别是heap(b)和heap(a),从而实现数据交换。

6.swap方法的实现

import java.lang.reflect.Field;

public class SwapDemo {

 public static void main(String[] args) {

   Integer a = 10, b = 20;

   System.out.println(“before a=” + a + “,b=” + b);

   swap(a, b);

   System.out.println(“after a=” + a + “,b=” + b);

 }

 private static void swap(Integer i1, Integer i2) {

   try {

     Field f = Integer.class.getDeclaredField(“value”);

     f.setAccessible(true);

     Integer temp = new Integer(i1);

     // 使用Integer temp = i1.intValue()会在栈内存开辟新的空间

     // i1.intValue->Integer.valueOf(i1.intValue).intValue();

     f.set(i1, i2.intValue());

     // f.setInt(i1,i2.intValue())不会做装箱操作

     f.set(i2, temp);

    } catch (NoSuchFieldException e) {

       e.printStackTrace();

    } catch (SecurityException e) {

       e.printStackTrace();

    } catch (IllegalArgumentException e) {

       e.printStackTrace();

    } catch (IllegalAccessException e) {

       e.printStackTrace();

  }

}

私有的成员属性是不能直接通过反射去获取的f.setAccessible(true);

Note:当定义为Integer a=1000,b=2000的时候使用int temp=i1.intValue()也可以实现数据的交换,因为超出范围时int temp = i1.intValue()会直接new新的Integer对象。




原址:http://ericchunli.iteye.com/blog/2358155

猜你喜欢

转载自blog.csdn.net/guoke2017/article/details/80946406