1.
     
     java
     
      是如何管理内存的
     
    
    
     
      
       Java
       
        的内存管理就是对象的分配和释放问题。
       
      
     
    
   
    
     
      
       
        分配
        
         
         :内存的分配是由程序完成的,通过关键字
        
        new
        
         
         为每个对象申请内存空间
         
         
          (
         
         
          基本类型除外
         
         
          )
         
         
          ,所有的对象都在堆
          
         
         
          (Heap)
         
         
          中分配空间。
         
        
       
      
     
    
   
    
     
      
       
        
        
       
       
        
         释放
         
          
          :对象的释放是由垃圾回收机制决定和执行的。
         
        
       
      
     
    
   
    
     
      
      
      
       这样做加重了
       
        JVM
       
       
        的工作。因为,
       
       
        GC
       
       
        为了能够正确释放对象,
       
       
        GC
       
       
        必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,
       
       
        GC
       
       
        都需要进行监控。
       
       
      
     
    
   
    
     2.
     
     
      什么叫
     
     
      java
     
     
      的内存泄露
     
    
   
    
     
      在
     
     
      Java
     
     
      中,内存泄漏就是存在一些被分配的对象,这些对象有两个特点:1.这些对象是可达的,即在有向图中,存在通路可以与其相连(也就是说仍存在该内存对象的引用);2.这些对象是无用的,程序以后不会再使用这些对象。
     
    
   
    
     
      如果对象满足这两个条件,这些对象就可以判定为
     
     
      Java
     
     
      中的内存泄漏,这些对象不会被
     
     
      GC
     
     
      所回收,然而它却占用内存。
     
    
   
    
     3.
     
     JVM
     
      的内存区域组成
     
    
   
    
     
      java
      
       内存分两种:栈内存、堆内存
      
     
    
   
    
     
      3.1.
     
     
      在函数中定义的基本类型、对象的引用类型变量都在函数的栈内存中分配;
     
    
   
    
     
      3.2.
      
       由
      
      
       new
      
      
       创建的对象、数组、对象的实例变量放在堆内存
      
     
    
   
    
     
      在函数(代码块)中定义一个变量时,
      
       java
      
      
       在栈中为这个变量分配内存空间,当超过变量的作用域后
       
        java
       
       
        会自动释放掉为该变量所分配的内存空间;在堆中分配的内存由
       
       
        java
       
       
        虚拟机的自动垃圾回收器来管理
       
      
     
    
   
    
     
      
       
        4
       
       .堆和栈的优缺点
      
     
    
   
    
     
      
       
        
        
         4.1堆的优势是 可以动态分配内存大小,生存期不必事先告诉编译器,因为它是在运行时动态分配内存的。
        
       
      
     
    
   
    
     
      
       
        
         缺点是 要在运行时动态分配内存,存取速度较慢.
        
       
      
     
    
   
    
     
      
       
        
         
          4.2栈的优势是 存取速度比堆要快,仅次于直接位于
          
           CPU
          
          
           中的寄存器。
          
         
        
       
      
     
     
      
       
        
         栈数据可以共享
        
       
      
     
    
   
    
     
      
       
        缺点是 栈中的数据大小与生存期必须是确定的,缺乏灵活性。
       
      
     
    
   
    
     
      5.
     
     
      
      Java
      
       中数据在内存中是如何存储的
      
     
     
    
   
    
     
      5.1
     
     
      基本数据类型
     
    
   
    
    
    
     基本数据类型
     
      8
     
     
      种,
      
       如
       
        int
        
        a
        
        =
        
        3
       
       
        ;这里的
       
       
        a
       
       
        是一个指向
       
       
        int
       
       
        类型的引用,指向
       
       
        3
       
       
        这个字面值。这些字面值的数据,由于大小可知,生存期可知
       
       
        (
       
       
        这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了
       
       
        )
       
       
        ,出于追求速度的原因,就存在于栈中。
       
      
     
    
    
     
      
       
        由于
        
         栈有一个很重要的特性,存在栈中的数据可以共享。于是我们同时定义:
        
        
         int
         
         a=3;
        
        
         int
         
         b
         
         =3;
         
          
          
           编译器先处理
          
          
           int
           
           a
           
           =
           
           3
          
          
           ;首先它会在栈中创建一个变量为
          
          
           a
          
          
           的引用,然后查找有没有字面值为
          
          
           3
          
          
           的地址,没找到,就开辟一个存放
          
          
           3
          
          
           这个字面值的地址,然后将
          
          
           a
          
          
           指向
          
          
           3
          
          
           的地址。
           
            接着处理
            
             int
             
             b
             
             =
             
             3
            
            
             ;在创建完
            
            
             b
            
            
             这个引用变量后,由于在栈中已经有
            
            
             3
            
            
             这个字面值,便将
            
            
             b
            
            
             直接指向
            
            
             3
            
            
             的地址。
             
              这样,就出现了
              
               a
              
              
               与
              
              
               b
              
              
               同时均指向
              
              
               3
              
              
               的情况。
              
             
            
           
          
         
        
       
      
     
    
   
    
     
      
       
        
         
          
           
            
             
              
               
                
                 定义完
                
                
                 a
                
                
                 与
                
                
                 b
                
                
                 的值后,再令
                
                
                 a
                 
                 =
                 
                 4
                
                
                 ;那么,
                
                
                 b
                
                
                 不会等于
                
                
                 4
                
                
                 ,还是等于
                
                
                 3
                
                
                 。在编译器内部,遇到时,它就会重新搜索栈中是否有
                
                
                 4
                
                
                 的字面值,如果没有,重新开辟地址存放
                
                
                 4
                
                
                 的值;如果已经有了,则直接将
                
                
                 a
                
                
                 指向这个地址。因此
                
                
                 a
                
                
                 值的改变不会影响到
                
                
                 b
                
                
                 的值。
                
               
              
             
            
           
          
         
        
       
      
     
    
   
    
     
      
       
        
         
          5.2
          
           
            对象
           
          
         
        
       
      
     
    
   
    
     
      
       
        
         
         
        
        
         
          
           在
           
            Java
           
           
            中,创建一个对象包括对象的声明和实例化。
           
          
         
        
       
      
     
    
    
     
      
       
        
         
          
           
            下面用一个例题来说明对象的内存模型。
           
          
         
        
       
      
     
    
   
    
     
      
       
        
         
          假设类
         
         
          Rectangle
         
         
          定义如下:
         
        
       
       
        
       
       
        public
        
         
        
        class
        
         
         Rectangle
         
         {
         
        
       
       
        
       
       
        double
        
         
        
        width
        
         ;
        
       
       
        
       
       
        double
        
         
        
        height
        
         ;
        
       
       
        
       
       
        public
        
         
         Rectangle(
        
        double
        
         
         w,
        
        double
        
         
         h){
         
        
       
       
        
       
       
        
         w
         
         =
         
        
        width
        
         ;
        
       
       
        
       
       
        
         h
         
         =
         
        
        height
        
         ;
        
       
       
        
       
       
        }
       
       
        
       
       
        
         }
         
        
       
       
        
       
       
        1.声明对象时的内存模型
       
       
        
       
       
        
         
          
           Rectangle
          
          
          rect
         
         
          ,声明一个对象
         
         
          rect
         
         
          ,在栈内存为对象的引用变量
         
         
          rect
         
         
          分配内存空间。
         
        
       
      
     
    
   
    
     
      
       
        
         
          但
         
         
          Rectangle
         
         
          的值为空,
         
         
          rect
         
         
          是一个空对象。空对象不能使用,因为它还没有引用任何
         
         
          ”
         
         
          实体
         
         
          ”
         
         
          。
         
        
       
      
     
    
   
    
     
      
       
        
         
         
         2.
         
          
           对象实例化时的内存模型
          
         
         
          
         
         
          
          
          当执行
          
           rect=new
           
           Rectangle(3,5)
          
          
           ;时,会做两件事:
          
         
        
       
      
     
    
   
    
     
      
       
        
         
          
           
           
           在堆内存中为类的成员变量
          
          
           width,height
          
          
           分配内存,并将其初始化为各数据类型的默认值;
          
         
        
       
      
     
    
    
     
      
       
        
         
          
           接着进行显式初始化(类定义时的初始化值,调用构造方法
          
         
         
          
           
            
             
              
               )
              
             
            
            为成员变量赋值。
           
           
            返回堆内存中对象的引用(相当于首地址)给引用变量
           
           
            rect,
           
           
            以后就可以通过
           
           
            rect
           
           
            来引用堆内存中的对象了。
           
          
         
        
       
      
     
    
   
    
     
      
       
        
         
          
           
           
           3.
           
            
            
             创建多个不同的对象实例
            
           
          
         
         
        
       
      
      
       
        
         
         
         一个类通过使用
        
        
         new
        
        
         运算符创建多个不同的对象实例,这些对象实例将在堆中被分配不同的内存空间,改变其中一个对象的状态不会影响其他对象的状态。
        
       
      
     
    
   
    
    
    
     
      
       
        
         
          
          
          Rectangle
          
          r1=
          
         
         new
         
          
          Rectangle(
         
         3
         
          ,
         
         5
         
          );
         
        
        
         
        
        
         
          
          
          Rectangle
          
          r2=
          
         
         new
         
          
          Rectangle(
         
         4
         
          ,
         
         6
         
          );
         
        
        
         
        
        
         
          
          
          此时,将在堆内存中分别为两个对象的成员变量
          
           width
          
          
           、
          
          
           height
          
          
           分配内存空间,两个对象在堆内存中占据的空间是互不相同的。如果有:
          
         
        
        
         
        
        
         
          
          
          Rectangle
          
          r1=
          
         
         new
         
          
          Rectangle(
         
         3
         
          ,
         
         5
         
          );
         
        
        
         
        
        
         
         
         Rectangle
         
         r2=r1;
        
        
         
        
        
         
         
         则在堆内存中只创建了一个对象实例,在栈内存中创建了两个对象引用,两个对象引用同时指向一个对象实例。
        
        
         
        
       
      
     
    
   
    
     
      
       
        
         
          4.
          
           包装类
          
          基本类型的定义在栈中,包装类创建对象,就和普通对象一样了。
         
        
       
      
     
    
   
    
     
      
       
        
         
          5.
          
           
           String
          
         
        
       
       
        
        
         是一个特殊的包装类数据。可以用用以下两种方式创建:
        
        
         String
         
         str
         
         =
         
        
        new
        
         
         String(“abc”)
         
          ;
         
         
          String
          
          str
          
          =
          
          “abc”;
         
        
       
      
     
    
   
    
     
      
       
        
         
          
           第一种创建方式,和普通对象的的创建过程一样;
          
          
           
          
          
           
            第二种创建方式,
            
             Java
            
            
             内部将此语句转化为以下几个步骤:
            
           
          
          
           
          
          
           
            (1)
            
            
             先定义一个名为
            
            
             str
            
            
             的对
            
            
             String
            
            
             类的对象引用变量:
            
            
             String
             
             str
            
            
             ;
            
           
          
          
           
          
          
           
            (2)
            
            
             在栈中查找有没有存放值为
            
            
             “abc”
            
            
             的地址,如果没有,则开辟一个存放字面值为
            
            
             “abc”的
            
           
          
          
           
            地址,接着创建一个新的
            
             String
            
            
             类的对象
            
            
             o
            
            
             ,并将
            
            
             o
            
            
             的字符串值指向这个地址,而且在栈
            
           
          
          
           
            地址旁边记下这个引用的对象
            
             o
            
            
             。如果已经有了值为
            
            
             “abc”
            
            
             的地址,则查找对象
            
            
             o
            
            
             ,并
            
           
          
          
           
            回
            
             o
            
            
             的地址。
            
           
          
          
           
          
          
           
           
          
         
        
       
      
     
    
    
     
      
       
        
         
          
           
            (3)
            
            
             将
            
            
             str
            
            
             指向对象
            
            
             o
            
            
             的地址。
            
           
          
          
           
          
          
           
            值得注意的是,一般
            
             String
            
            
             类中字符串值都是直接存值的。但像
            
            
             String
             
             str
             
             =
             
             “abc”
            
            
             ;这种
            
           
          
          
           
          
          
           合下,其字符串值却是保存了一个指向存在栈中数据的引用。
          
          
           
          
          
           为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。
          
          
           
          
          
           
            String
            
            str1=”abc”
            
             ;
            
           
          
          
           
          
          
           
            String
            
            str2=”abc”
            
             ;
            
           
          
          
           
          
          
           System
           
            .out.println(s1==s2)
            
             ;
            
            
             //true
            
           
          
          
           
          
          
           
            注意,这里并不用
            
             str1.equals(str2)
            
            
             ;的方式,因为这将比较两个字符串的值是否相等。
            
            
             ==
            
            
             号,根据
            
            
             JDK
            
            
             的说明,只有在两个引用都指向了同一个对象时才返回真值。
            
           
          
         
        
       
      
     
    
   
    
     
      
       
        
         
          
          
          
           我们再接着看以下的代码。
          
          
           
          
          
           
            String
            
            str1=
            
           
           new
           
            
            String(
           
           “abc”
           
            )
            
             ;
            
           
          
          
           
          
          
           
            String
            
            str2=
           
           “abc”
           
            ;
           
          
          
           
          
          
           System
           
            .out.println(str1==str2)
            
             ;
            
           
           
            //false
           
          
          
           
          
          
           
            创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
            
             
             
             
            
            
             以上两段代码说明,只要是用
            
            
             new()
            
            
             来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。
            
           
          
         
        
       
      
     
    
   
    
     
      
       
        6.
        
         
          数组
         
        
       
      
     
    
   
    
     
      
       
       
      
     
     
      
       
        当定义一个数组,
       
       
        int
        
        x[]
       
       
        ;或
       
       
        int
        
        []x
       
       
        ;时,在栈内存中创建一个数组引用,通过该引用(即数组名)来引用数组。
       
       
        x=new
        
        int[3]
       
       
        ;将在堆内存中分配
       
       
        3
       
       
        个保存
       
       
        int
       
       
        型数据的空间,堆内存的首地址放到栈内存中,每个数组元素被初始化为
       
       
        0
       
       
        。
       
      
      
       
      
      
       7.
      
      
       
        静态变量
       
      
      
     
     
      
       静态表示的是内存的共享,就是它的每一个实例都指向同一个内存地址,
       
        把它改了,它就不会变成原样,你把它清理了,它就不会回来了。
        
         
          
           
            用
           
           
            static
           
           
            的修饰的变量和方法,实际上是指定了这些变量和方法在内存中的
           
           
            ”
           
           
            固定位置
           
           
            ”
           
           
            -
           
           
            static
            
            storage
           
           
            ,可以理解为所有实例对象共有的内存空间。
           
          
          静态变量与方法是在什么时候初始化的呢?对于两种不同的类属性,
         
         
          static
         
         
          属性与
         
         
          instance
         
         
          属性,初始化的时机是不同的。
         
         
          instance
         
         
          属性在创建实例的时候初始化,
         
         
          static
         
         
          属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。
         
        
       
      
      
     
    
   
 
