1.1.1. java内存分区

Java虚拟机在程序执行过程会把jvm的内存分为若干个不同的数据区域来管理,这些区域有自己的用途,以及创建和销毁时间,有的随着jvm进程的启动而存在,有的则是依赖用户线程的启动和结束而建立和销毁。

image

1. 共享数据区:

  • 方法区一般存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。运行时常量池是方法区的一部分。此区域也会有GC,但不会频繁触发,主要目标是针对常量池的回收和对类型的卸载

JDK6中常量池在方法区 JDK7常量池在堆

  • 堆:JVM管理的内存中占比最大的一块区域,虚拟机启动时创建,唯一目的是存放对象实例,几乎所有对象实例都在这里分配内存。(初始化的对象,成员变量 ),

image

Java堆主体分为

  1. 新生代: eden survoir0 survor1 按照8:1:1分配。 复制清除算法

新生代分为:

- eden 8
- survoir0 1
- survor1  1
  1. 老年代:存放大对象、经过Minor GC15次的对象 标记整理算法

2. 线程私有:

  • 栈:每个方法从调用到执行完成,就对应一个栈帧在虚拟机栈中的入栈和到出栈的过程。

    栈的结构是栈帧组成的,每个方法执行时都会创建一个栈帧,用于存储方法执行期间所用到的数据结构,包含局部变量表,操作数栈,动态链接,方法出口等信息。其中最重要的就是局部变量表,局部变量表包含着各种编译期已知的基本数据类型、对象引用和returnAddress。基本数据类型就是Java的8大基本数据类型(boolean,byte,char, short, int, float, long, double),对象引用,你可以把它当成指向实际对象地址的指针或者一个代表对象的句柄,returnAddress则是一条字节码指令的地址。

  • 本地方法栈:虚拟机栈的作用是类似的,区别在于,虚拟机栈为执行java方法(字节码)服务,而本地方法栈则是为执行本地Native方法服务。

  • 程序计数器:一块较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器。在多线程环境下,每个线程是并非有序执行,而是需要竞争CPU的时间,所以为了线程切换后能恢复到正确的执行位置,每条线程都需要一个独立的计数器,互不干扰,独立存储,这块儿区域可以看作是“线程私有内存”,此内存区域是唯一一个在jvm规范中没有规定任何OutOfMemoryError的区域。

  • 直接内存 不属于虚拟机运行时数据区的一部分。Java NIO引入了一种基于通道与缓冲区的IO方式。可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。避免Java堆和Native堆之间来回复制数据,在某种场景中显著提高性能。由于不在堆中分配,因此不受到堆大小限制。但既然是内存总有会被用完时候,因此会抛出OutOfMemoryError。

3. java中堆和栈的区别

每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值。

stack:

每个应用程序运行时,都有属于自己的一段内存空间,用于存放临时变量、参数传递、函数调用时的PC值的保存。这叫stack。

heap:

所有的应用可以从一个系统共用的空间中申请供自己使用的内存,这个共用的空间叫heap。

  • Stack存取速度仅次于寄存器,Stack里面的数据可共享,但是其中数据的大小和生存期必须在运行前确定。
  • Heap是运行时可动态分配的数据区,从速度看比Stack慢,Heap里面的数据不共享,大小和生存期都可以在运行时再确定

  • 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配

  • 堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。 new关键字 是运行时在Heap里面创建对象,每new一次都一定会创建新对象。 只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

看下面代码

public class A {
    int a = 0; // 栈内
    B b = new B(); // new B()堆内  b在栈内
    public void test(){
        int a1 = 1;  //栈内
        B b1 = new B(); // b1在栈内 new B() 在堆内
    }
}

A object = new A(); //object栈内  new A() 堆内

A类内的局部变量都存在于栈中,包括基本数据类型a1和引用变量b1,b1指向的B对象实体存在于堆中

引用变量object存在于栈中,而object指向的对象实体存在于堆中。new A 对象的所有成员变量a和b在栈内(句柄),而引用变量b指向的B类对象实体存在于堆中。

3.1 ==和equals区别
  • == 对于基本类型来说比较值是否相等,对于引用类型来说则比较引用是否相同(栈中)
  • equals非重写情况 等于“==”比较两个对象引用是否相同,重写后比较对象内容是否相同,如String 和 Integer 等重写了 equals 方法,把它变成了值比较。

3.2 hashcode 和 equals
当类需要放在HashTable、HashMap、HashSet等等hash结构的集合做为key时,必须重写 hashCode 和 equals。

以此为前提下

  • 如果两个对象相等(equal()),那么它们的hashCode()值一定要相同;
  • 如果两个对象hashCode()相等,它们并不一定相等(equal())。

4. 常量池相关

4.1 8种基本类型包装类与常量池

4.1.1 java中基本类型的包装类的大部分都实现了常量池技术,

即Byte,Short,Integer,Long,Character,Boolean;

  Integer i1 = 40;
  Integer i2 = 40;
  System.out.println(i1==i2);//输出TRUE

这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象

4.1.2 两种浮点数类型的包装类Float,Double并没有实现常量池技术。
   Double i1=1.2;
   Double i2=1.2;
   System.out.println(i1==i2);//输出false
4.1.3 应用常量池的场景
  1. Integer i1=40;Java在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
  2. Integer i2 = new Integer(40);这种情况下会创建新的对象。
  Integer i1 = 40;
  Integer i2 = new Integer(40);
  System.out.println(i1==i2);//输出false

例题

  Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);

  System.out.println("i1=i2   " + (i1 == i2));     // true
  System.out.println("i1=i2+i3   " + (i1 == i2 + i3)); // true
  System.out.println("i1=i4   " + (i1 == i4));  // false
  System.out.println("i4=i5   " + (i4 == i5));   //false
  System.out.println("i4=i5+i6   " + (i4 == i5 + i6));   //true
  System.out.println("40=i5+i6   " + (40 == i5 + i6));    //true

解释:

语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较

jdk1.5自动拆箱装箱

自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。


4.2 String类和常量池

4.2.1 String创建对象
String str1 = "abcd";//在常量池中创建字符串“abcd”,并把他的引用赋值给str1
String str2 = new String("abcd");//在常量池中创建字符串“abcd”,在堆中创建对象c,c指向常量池中的字符串,str2指向c。
System.out.println(str1==str2);//false
4.2.2"+" 连接
  • 只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。
  • 对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中
  String str1 = "str";
  String str2 = "ing";

  String str3 = "str" + "ing";
  String str4 = str1 + str2;
  System.out.println(str3 == str4);//false

  String str5 = "string";
  System.out.println(str3 == str5);//true
特例1
public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
     String s = A + B;  // 将两个常量用+连接对s进行初始化 
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 } 
s等于t,它们是同一个对象

A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。也就是说:String s=A+B; 等同于:String s="ab"+"cd";

特例2
public static final String A; // 常量A
public static final String B;    // 常量B
static {   
     A = "ab";   
     B = "cd";   
 }   
 public static void main(String[] args) {   
    // 将两个常量用+连接对s进行初始化   
     String s = A + B;   
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 } 
s不等于t,它们不是同一个对象

A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。

4.2.3 String s = new String("xyz");创建几个对象?

请别再拿“String s = new String("xyz");创建了多少个String实例”来面试了吧

TMD直接背下来创建两个吧,一个是"xyz" , 一个是s。 很不幸我被问过这个问题,我暂时不打算搞清楚了。

String s1 = new String("s1") ;
String s1 = new String("s1") ;
上面一共创建了几个对象?

答案:3个 ,编译期Constant Pool中创建1个,运行期heap中创建2个.(用new创建的每new一次就在堆上创建一个对象,用引号创建的如果在常量池中已有就直接指向,不用创建)

4.2.4 java.lang.String.intern()

运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。

String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

public static void main(String[] args) {    
      String s1 = new String("计算机");
      String s2 = s1.intern();
      String s3 = "计算机";
      System.out.println("s1 == s2? " + (s1 == s2));
      System.out.println("s3 == s2? " + (s3 == s2));
  }
s1 == s2? false
s3 == s2? true

下面这个吐血了

public class Test {
 public static void main(String[] args) {   
      String hello = "Hello", lo = "lo";
      System.out.println((hello == "Hello") + " ");
      System.out.println((Other.hello == hello) + " ");
      System.out.println((other.Other.hello == hello) + " ");
      System.out.println((hello == ("Hel"+"lo")) + " ");
      System.out.println((hello == ("Hel"+lo)) + " ");
      System.out.println(hello == ("Hel"+lo).intern());
 }   
}
class Other { static String hello = "Hello"; }
package other;
public class Other { public static String hello = "Hello"; }
true true true true false true

在同包同类下,引用自同一String对象. 在同包不同类下,引用自同一String对象. 在不同包不同类下,依然引用自同一String对象. 在编译成.class时能够识别为同一字符串的,自动优化成常量,引用自同一String对象. 在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.

```

Copyright © tracyliu-FE 2021 all right reserved,powered by Gitbook文件修订时间: 2022-03-06 12:52:33

results matching ""

    No results matching ""