
借用一句话: Java和C ++之间存在动态内存分配和垃圾回收的障碍. 围墙墙内的人想出去.
1. 为什么我们需要了解Java内存
因为虚拟机可以帮助我们的JAVA程序员管理内存,所以在我们申请内存以在new Object()中创建对象之后,我们无需删除/释放内存即可. 因此,它不容易出现内存泄漏和内存溢出问题,并且一切似乎都很好.
但是,如果程序员不了解虚拟机如何管理内存,那么对与内存相关的错误进行故障排除将成为一个巨大的问题.
两个. 什么是存储区?

内存区域分为两种类型,其中一种在虚拟机进程启动时存在. 另一个是根据用户进程的开始和结束而建立和销毁的.
1. 程序计数器
一小部分线程专用内存空间可视为当前线程执行的字节码的行号指示符.
如果线程正在执行JAVA方法,则计数器记录正在执行的虚拟机的字节码指令的地址. 如果正在执行本机方法,则计数器值为空(未定义).

此内存区域是唯一未在JAVA虚拟机规范中指定任何OutOfMemoryError(OOM)条件的区域.
2. 虚拟机堆栈
线程私有,每个Java方法将创建一个堆栈框架(Stack Frame)来存储局部变量表,操作数堆栈,动态链接,方法导出和执行期间的其他信息. 从调用到完成每种方法的过程与虚拟机堆栈中从推入到弹出的堆栈帧的过程相对应.
3. 本地方法堆栈
线程专用,与虚拟机堆栈相同,提供本机方法. 在HotSpot虚拟机中,虚拟机堆栈和本地方法堆栈直接组合为一个.
4. 堆
线程共享的区域. 实例存储的区域,几乎所有对象实例都在此处分配内存. 同时,由于空间是固定的,用户可能需要连续生成实例,因此该区域也是垃圾收集的主要区域. 垃圾收集将在后面提到.
Java堆可以在物理上不连续的内存空间中,只要它在逻辑上是连续的即可.
5. 方法区域
线程共享区域存储虚拟机已加载的数据,例如类信息,常量和静态变量.

许称其为“不朽的一代”,因为HotSpot使用不朽的一代来实现方法区域. Java规范在方法区域上有非常宽松的限制,您可以选择不实施垃圾回收.
6. 运行时常量池
方法区域的一部分,用于存储由编译器生成的各种文字和符号引用,并在加载类后输入方法区域的运行时常量池.
关于这一快速领域,有一件事需要引起注意. 代码如下:

public class t18 {
public static void main(String[] args){
Integer a1 = 128 ;
Integer a2 = 128 ;
System.out.println(a1==a2);
Integer b1 = 127;
Integer b2 = 127;
Integer b3 = 1 + b1;
Integer b4 = a1 -1 ;
System.out.println(b1==b2);
System.out.println(b3==a1);
System.out.println(b4 == b1);
}
}

以上代码的运行结果为false,true,false,true. 这是许第一次看到它时无法理解的东西,因为这里涉及到恒定池的知识. JVM将在常量池中缓存一些int,String和其他数据,但是重要的一点是,对于int数据,将仅缓存-128〜127范围内的数据. 因此:
a1和a2超过127,在堆中分配了内存,两个指向不同的对象,并返回false;
b1和b2都指向常量池中的127,因此b1和b2指向相同的地址并返回true;

第三个和第四个相同,Integer b3 = 1 + b1 ----> Integer b3 = Integer.valueOf(1 + b1).
7. 直接记忆
在JDK1.4之后,添加了NIO(新I / O)类,并引入了基于通道和基于缓冲区的IO方法. 本地函数库可用于分配机器内存,例如8g计算机内存,而JVM可以使用计算机的剩余内存,您只需要在Java堆中存储DirectByteBuffer对象作为操作的内存引用即可. 这样可以在某些情况下提高性能.
3. 当对象是新对象时会发生什么?当虚拟机遇到新指令时,首先返回以检查它是否可以位于常量池中,并检查是否已加载,解析和初始化了该类. 如果不是,则必须首先执行该类的加载过程. 加载该类之后,接下来将为该对象分配内存,即,将从Java堆中划分出一定大小的内存. 如果Java堆是连续且规则的,则分配的内存放在一侧,而空闲的内存放在另一侧. 中间的指针用作分界点的指示符,然后分配内存是将指针移至自由方向所需的距离(使用带有正则化过程的垃圾收集器,例如Serial,PalNew等);如果java堆是不规则的,则虚拟机必须维护一条记录,并且该记录需要在分配内存的同时进行更新(例如,基于标记清除算法(例如CMS)的收集器). 为分配的内存空间分配初始值,例如将integer变量设置为0并将bool类型设置为false. 确保对象字段可以直接在代码中使用,而无需支付初始值. 但是,在实际编码中,建议使用初始值分配的形式来保持良好的编码习惯. 另外
String s ; System.out.println(s); //未初始化,编译器报错
代码将报告错误而不是输出null,因此请记住.
初始化对象的对象头数据. 每个java对象都有一个Object Header,它记录该对象是哪个实例,如何查找该类的元数据信息,哈希码,GC使用期限以及偏向锁等信息. 执行init方法以将对象初始化为程序员的成员,以便可用的对象完成新操作. 4.对象在内存中的组成部分是什么
以HotSpot虚拟机为例,可以将对象存储在内存中的区域分为三个部分
1. 对象标题
对象标头由两部分组成,一部分用于存储对象本身的运行时数据,正式称为“标记字”,包括: HashCode,GC年龄,锁状态,线程保持锁,偏置锁线程ID,biasing Timestamp等. 占用字长(32位或64位,具体取决于虚拟机).

另一部分是类型指针,即对象指向的类元数据指针,通过它可以确定对象是哪个类实例. 此外,如果对象是数组,那么还有一段数据可用来记录数组的长度.
2. 对象数据
它是存储在实例中并由程序员设计的数据.
3. 对齐并填充
没有必要,它仅用作占位符. HotSpot内存管理规定,对象的起始地址必须是8字节的整数倍,换句话说,对象的大小必须是9字节的整数倍. 因此,当实例大小未对齐时,需要使用对齐填充.
5. 如何访问定位对象
创建对象以使用对象. Java虚拟机使用堆栈上的参考数据来操作堆上的特定对象. 当前有两种主流访问方法:
1. 使用句柄访问
一块内存被分成Java堆作为句柄池. 引用存储对象的句柄地址,并且句柄包含对象的实例数据和类型数据的相应地址信息.
访问期间的引用(存储的句柄地址)->处理池(在堆中存储的对象地址)->特定对象(在堆或方法区域中).
2. 使用直接指针访问
直接访问,引用(存储对象地址)->特定对象(在堆或方法区域),一次跳转. HotSpot虚拟机使用此方法.
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/shoujiruanjian/article-301852-1.html
美国人流放犯的后代
不是现在