作者:当年明月123
转载自:
https://www.cnblogs.com/paulwang92115/p/12251476.html
如果在大学里学过或者在工作中使用过 C 或者 C++ 的读者一定会发现这两门语言的内存管理机制与 Java 的不同。在使用 C 或者 C++ 编程时,程序员需要手动的去管理和维护内存,就是说需要手动的清除那些不需要的对象,否则就会出现内存泄漏与内存溢出的问题。
如果你使用 Java 语言去开发,你就会发现大多数情况下你不用去关心无用对象的回收与内存的管理,因为这一切 JVM 虚拟机已经帮我们做好了。了解 JVM 内存的各个区域将有助于我们深入了解它的管理机制,避免出现内存相关的问题和高效的解决问题。
在 Java 编程时我们会用到许多不同类型的数据,比如临时变量、静态变量、对象、方法、类等等。那么他们的存储方式有什么不同吗?或者说他们存在哪?
Java 虚拟机在执行 Java 程序过程中会把它所管理的内存分为若干个不同的数据区域,各自有各自的用途。
这其中堆和方法区是线程之间共享的,而栈和程序计数器是线程私有的。
局部变量
、对象的引用
等等。
在这片区域中,规定了两种异常情况,当线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常。当虚拟机栈动态扩展无法申请到足够的内存时会抛出 OOM 异常。对象实例
,也就是 NEW 出来的对象。这个区域也是 Java 垃圾收集器的主要作用区域。
当堆的大小再也无法扩展时,将会抛出 OOM 异常。
可以说,此内存区域唯一的作用就是存放对象实例,几乎所有的对象实例和数组都在这里分配内存。
Java 堆是垃圾收集管理的主要区域,因此也被称为 GC 堆。垃圾收集都采用分代垃圾回收算法,所以 Java 堆还可以细分:新声代(再细致一点分为 Eden,From Survivor,To Survivor)和老年代。进一步划分的目的是跟好地回收内存,或者更快地分配内存。
类信息
、常量
、静态变量
等等。当方法区无法满足内存分配需求时,会抛出 OOM 异常。这个区域也被称为永久代。虽然上面的图里没有运行时常量池和直接内存,但是这两部分也是我们开发时经常接触的。所以给大家补充出来。
字面量
和符号引用
,这部分内容将在类加载后存放到方法区的运行时常量池中。也会抛出 OOM 异常。NIO
操作时会直接使用的一块内存,虽然不受虚拟机参数限制,但是还是会受到本机总内存的限制,会抛出 OOM 异常。这里有一个概念希望大家能够清除,堆中使用分代垃圾回收算法时的永久代表方法区,它并不在堆内存中,上面的图片将其放在一起是为了说明分代垃圾回收算法会作用在这几个区域。
对于方法区,它是线程共享的,主要用于存储类的信息,常量池,方法数据,方法代码等。我们称这个区域为永久代
。它也是 JVM 垃圾回收作用的区域。
大部分程序员应该都见过 java.lang.OutOfMemoryError:PermGen space 异常,这里的 PermGen space
其实指的就是方法区。由于方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代
的内存溢出,典型的场景是在 JSP 页面比较多的情况,容易出现永久代内存溢出。
在 JDK 1.8 中,HotSpot 虚拟机已经没有 PermGen space 方法区这个地方了,取而代之的是一个叫 Metaspace
(元空间)的东西。
元空间与方法区最大的区别是:元空间不再虚拟机中,而是使用本地内存。默认情况下,元空间的大小仅受本地内存限制。
常量区原本在方法区中,现在方法区被移除了,所以常量池被放倒了堆中。
这样做的好处是:
这样更改的好处:
对象的创建过程,最好是能记住,并且能知道每一步在做什么。
HotSpot 虚拟机中,对象在内存中的布局可以分为三块区域:对象头,实例数据和对齐填充。
对象头中包含两部分信息,第一部分用于存储对象自身运行时数据(哈希码,GC 分代年龄,锁状态标志),另一部分是类型指针,即指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
实例数据部分存储的对象的有效信息。
对其填充起到的是占位的作用。
String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//false
这两种方式创建的对象是有差别的,第一种方式是在常量池中,第二种方式是在堆内存中。
直接使用双引号声明创建出来的 String 对象会直接存储在常量池中。
如果不是使用常量池声明的 String 对象,可以使用 String 提供的 intern 方String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
String s1 = new String("计算机");
String s2 = s1.intern();
String s3 = "计算机";
System.out.println(s2);//计算机
System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象,
System.out.println(s3 == s2);//true,因为两个都是常量池中的String对
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";//常量池中的对象
String str4 = str1 + str2; //在堆上创建的新的对象
String str5 = "string";//常量池中的对象
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
String s1 = new String("abc"); 这句话创建了几个对象?
先有字符串 “abc” 放入常量池,然后 new 了一个字符串 “abc” 放入 Java 堆。栈中的引用指向堆中的对象。
Java 基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean。除了 Boolean 之外的 5 种包装类都默认创建了 【-128 127】的缓存数据,超出此范围仍然去创建新的对象。Float 和 Double 并没有实现常量池技术。
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出true
Integer i11 = 333;
Integer i22 = 333;
System.out.println(i11 == i22);// 输出false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出false
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);//输出false
其它优质文章请见后台公众号菜单
【191101】Java第一期资源【191110】Java第二期资源【191117】Java第三期资源【191124】Java第四期资源【191201】Java第五期资源【191228】Java第六期资源【200105】Java第七期资源【200110】Java第八期资源【200117】Java第九期资源