Java对象创建过程和内存结构分析

JAVA内存分配和管理是JAVA的核心技术之一,在看了尚硅谷宋红康老师讲解的JAVA内存知识之后,结合《深入理解JVM这本书》对自己所学的知识进行简单的总结,写了这篇日志。

1.JAVA内存分区

 根据存储数据的不同,java内存通常被划分为5个区域:程序计数器(Program Count Register)、本地方法栈(Native Stack)、方法区(Methon Area)、栈(Stack)、堆(Heap)。

程序计数器(Program Count Register):又叫程序寄存器。JVM支持多个线程同时运行,当每一个新线程被创建时,它都将得到它自己的PC寄存器(程序计数器)。如果线程正在执行的是一个Java方法(非native),那么PC寄存器的值将总是指向下一条将被执行的指令,如果方法是 native的,程序计数器寄存器的值不会被定义。 JVM的程序计数器寄存器的宽度足够保证可以持有一个返回地址或者native的指针。

栈(Stack):又叫堆栈。JVM为每个新创建的线程都分配一个栈。也就是说,对于一个Java程序来说,它的运行就是通过对栈的操作来完成的。栈以帧为单位保存线程的状态。JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作。我们知道,某个线程正在执行的方法称为此线程的当前方法。我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧,这个帧自然成为了当前帧。在此方法执行期间,这个帧将用来保存参数、局部变量、中间计算过程和其他数据。从Java的这种分配机制来看,堆栈又可以这样理解:栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。

 堆(Heap):Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域。在此区域的唯一目的就是存放对象实例,几乎所有的对象实例都是在这里分配内存,但是这个对象的引用却是在栈(Stack)中分配。因此,执行String s = new String("s")时,需要从两个地方分配内存:在堆中为String对象分配内存,在栈中为引用(这个堆对象的内存地址,即指针)分配内存,如下图所示。


 方法区(Method Area):当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息,然后把这些类型信息(包括类信息、常量、静态变量等)放到方法区中,该内存区域被所有线程共享,如下图所示。本地方法区存在一块特殊的内存区域,叫常量池(Constant Pool),这块内存将与String类型的分析密切相关。


2.JAVA对象创建过程分析

以创建Person对象为例,分析对象创建过程中在内存中的分配情况以及方法的调用过程。

 

  1. class Person   
  2.     {   
  3.        private String name;   
  4.        private int age;   
  5.   
  6.        DemoTest dTest = new DemoTest();  
  7.        public Person(String name, int age)   
  8.       {   
  9.            System.out.println("这是person的构造函数");   
  10.            this.name = name;   
  11.            this.age = age;   
  12.        }   
  13.   
  14.   
  15.       {   
  16.            System.out.println("这是person的构造代码块");   
  17.        }   
  18.   
  19.   
  20.        static    
  21.       {   
  22.            System.out.println("这是person类的静态代码块");   
  23.        }      
  24.        public void setName(String name)   
  25.       {  
  26.            this.name = name;   
  27.        }   
  28.   
  29.   
  30.        public void show()   
  31.       {   
  32.           System.out.println("name = "+name + "::"+"age = "+age);   
  33.        }   
  34.   
  35.     }   
  36.   
  37.   
  38.     class DemoTest   
  39.     {   
  40.          public DemoTest()   
  41.         {   
  42.              System.out.println("这是一个测试的类");   
  43.          }   
  44.     }   
  45.   
  46.   
  47.     public class test   
  48.     {   
  49.          public static void main(String[] args)   
  50.         {   
  51.              Person p = new Person("gao",24);   
  52.              p.setName("wz");   
  53.              p.show();   
  54.          }   
  55.     }   

输出结果这是person类的静态代码块 
    这是一个测试的类 
            这是person的构造代码块 
    这是person的构造函数 
    name = wz::age = 20

首先栈中的main函数执行Person = new Person("gao",24);这个简单的语句会涉及到如下几个步骤: 
1,由于是要创建Person类对象,java虚拟机(JVM)先去找Person.class文件,如果有的话,将其加载到内存。 
2,将类型信息(包括静态变量,方法等)加载进方法区。 
3,执行该类中static代码块,如果有的话,对Person.class类进行初始化。(此时输出‘这是person类的静态代码块’) 
4,到这时才进行堆内存空间的开辟,并为对象分配首地址。 
5,在堆内存中建立对象的成员属性,并对其进行初始化(先进行默认初始化再进行显示初始化。(此时输出  ‘这是一个测试的类’) 
6,进行构造代码块的初始化,由此看出构造代码库初始化的优先级要高于对象构造函数的初始化。 (此时输出 ‘这是person的构造代码块’) 
7,对象的构造函数进行初始化。(此时输出 ‘这是person的构造函数’); 
8,将堆内存中的地址赋给栈内存中的p变量。

以上是我的java虚拟机机制的一些理解,由于JVM的复杂性,完全理解它是很困难的,如果文章中有错误的还望大家指出

posted @ 2016-11-19 21:33  天涯海角路  阅读(241)  评论(0)    收藏  举报