JVM 内存结构、类生命周期
Contents
参考大神的blog:
http://www.cnblogs.com/ityouknow/p/5610232.html
java的内存结构(一)
大佬都讲的巨详细了,深度干货啊,没啥要补充的了
http://www.cnblogs.com/ityouknow/p/5610232.html
重点注意:
- 名词的区别:新生代、老年代、永久代 (都是代表不同的内存空间)
- 命名也可以叫:堆区、栈区、方法区构成JAVA内存空间
- 栈区由多个线程构成(不共享的),每个线程栈:java栈、本地方法栈和程序计数器
- 本地方法区就是 java中的 native方法,有可能是底层用c实现的,比如数组的一些操作
- 老年代空间大小=堆空间大小-年轻代大空间大小
- 栈中的方法里申明的基本类型的变量和值都保存在栈中,非基本类型的变量引用在栈中,值指向堆中的对象。 类中的成员变量为基本类型的成员变量和值都保存在堆中。
栈区
- 由多个线程栈组成,不共享。
- 每个线程:JVM栈(普通java方法)、计数器、本地栈构成(比如native开头的方法)
- 程序计数器:CPU 多线程切换的时候,用计数器记录该方法执行到第几行了,只记录java方法,不记录native方法
- JVM栈 执行的普通方法,包括 方法的局部变量、操作栈、动态链接、方法出口等等信息,方法调用超过1000,会Stack OverflowError
方法区(非堆区、永久代)
- 存储类信息、常量、静态变量(静态方法和静态成员变量)、即时编译器编译后的代码等
- 多线程共享
- 也需要GC回收,主要是常量池的维护和类型的卸载(当某个类,没有任何实体对象被引用的时候,就会被卸载)
- JDK1.7把字符串常量池移到堆中去了,intern的逻辑也发生了改变
- JDK1.8把方法区移到本地内存了,同时支持metaspace做设置大小,不设置就自动策略增长
存储的内容
- 类的相关信息
- 类信息:类的全限定名、访问修饰符、类类型还是接口类型、直接超类(父类或者接口类)的全限定名
- 字段信息:字段名、字段类型、字段的修饰符
- 方法信息:方法名、方法返回类型、方法参数的数量和类型(按照顺序)、方法的修饰符
- 常量池(字面量和符号引用)
- static修饰的成员变量
常量池
- 静态常量池、运行时常量池(包括字符串常量池),我们通常指的常量池就是运行时常量池(内容差不多,只是形态和先后顺序不同而已)
- 常量池中不仅仅存常量,还有符号引用和字面量
- 字面量:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
- 符号引用:(1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符、装载该类的装载器的引用(classLoader)、类型引用(class) )
- 静态常量池是class文件中占据很大空间的一部分,class文件的构成:JDK的版本、字段、方法、接口、常量池
运行时常量池(字符串常量池也属于运行时常量池的一部分)
- class文件被加载到内存之后(JAVA类的加载),class常量池在内存当中的版本,区别是这里面可以动态的追加常量(比如String test= new String(“hello); test.intern();)
- 在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的
字符串常量池
- 为了提升性能,节约空间,jvm用一种类似于HashMap的数据结构,构造了一个 StringTable,来存放字符串常量
- JDK 1.6时候字符串常量池位于方法区,链表最大长度1009,而1.7就进入了堆中管理
静态常量池和运行时常量池的关系
- 时间先后顺序不同,java文件被编译成class文件,这时常量信息存在 class物理文件当中,静态常量池。当发生jvm加载的过程中,静态常量池被加载到JVM内存当中,就变成运行时常量池
- 相较于Class文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入。最主要的运用便是String类的intern()方法
类的生命周期(二)
主要参考大佬的博客了
http://www.cnblogs.com/ITtangtang/p/3978102.html
一定要参考这个大神的一起看才看的懂,这个帖子里面有例子,适合菜鸟理解
http://www.cnblogs.com/chy2055/p/5124520.html
概要
1. 加载:查找并加载类的二进制数据(高频面试,classLoader)
2. 连接
–验证:确保被加载的类的正确性
–准备:为类的静态变量分配内存,并将其初始化为默认值
–解析:把类中的符号引用转换为直接引用
3. 初始化:为类的静态变量赋予正确的初始值
A. 类的加载、静态变量和函数的加载和赋值、常量的加载和赋值、实例化对象和普通变量的加载 的区别
- 类的结构加载发生在加载期
- 静态变量赋值默认值和静态函数的加载发送在准备期
- 常量的加载和赋值真实值发生在准备期
- 初始化期间执行 静态成员的真实赋值,以及执行静态代码块
- 实例化对象(比如new)的时候,才去堆中生成非静态变量和方法
B. 可控的时期是类加载时期,可以通过new ,或者子类调用等等很多方式主动的去加载特定的类,而且类的加载时一次加载,缓存策略,如果未被GC回收(永久代)就一直存在。
C. JVM类加载机制:全盘负责,父类委托,缓存机制
D. 只有主动调用的时候才会触发初始化过程
额外补充相关的知识
static的特点
- 和类的信息加载而一起加载,不依赖于对象的存在,也优先于对象实例化(构造函数)加载
- 保存在方法区,线程共享。
- 线程共享所以节省内存开销,比如一些常量对象,另外常量方法不需要实例化对象再调用他,可以直接类名.方法调用,这样不仅简约代码,而且避免实例化对象的内存开销
- 线程共享所以可以统一维护多个相同类型的对象属性值。
- 常用形式: 静态方法,静态成员变量,静态代码块