今日复习内容:java的运行时的内存区域、java内存模型、java类的加载机制
java的运行时的内存区域
主要分为以下几个部分:
-
线程共享
- 堆
- 方法区
-
线程独享
- 虚拟机栈
- 本地方法栈
- 程序计数器
虚拟机栈
- 线程私有,生命周期同线程。
- 虚拟机栈存放栈帧,栈帧中有局部变量表、部分结果值、方法初始化信息和返回信息。
- 方法的执行通过压栈和弹栈实现
-
常见错误:
- 栈的大小可以固定也可以动态扩展,当无法扩展的时候会抛出
OutOfMameryError
- 当栈的深度(栈帧的数量)超出虚拟机允许,抛出
StackOverFlowError
- 栈的大小可以固定也可以动态扩展,当无法扩展的时候会抛出
本地方法栈
调用的本机的方法,比如C的方法
程序计数器
线程切换的时候,记录当前线程执行的位置
- 记录指令的地址
- 因为占用空间小不会出现OOM
堆
用于存放对象实例,是虚拟机管理的最大的内存,是GC管理的主要区域。从垃圾回收的角度看,堆区域还可以细分,这和不同的JVM实现有关。
方法区
没有规定说方法区在哪一个位置,所谓的方法区只是一种规范,不同的JVM实现存放的地址不一样
方法区中主要存放的是,类的元信息,类的静态变量、常量。
在JDK1.8之后,HotSpot方法区的实现
- 类的元信息放入非堆区即本地内存,作为元数据区。
- 类的静态变量和常量则和堆共享内存,是堆逻辑上的一部分。
java内存细分
当我们谈论Java的内存细分的时候,线程独享三块内存区域:
- 虚拟机栈
- 本地方法栈
- 程序计数器
因为其因为生命周期和线程同步,所以它们的回收时间都是固定的,线程死亡之后自然就回收了
而堆和方法区回收时间是不确定的,所以讨论内存模型的时候往往都需要讨论内存回收的机制,所以Java的内存模型主要是建立在堆和方法区上的。
那么内存模型的具体划分应该是:
-
堆区,虚拟机来分配:
-
新生代
- Eden区域80%
- 两个Survivor区域各占10%
- 年老代
-
-
非堆区,本地内存分配
- 元数据区
后面根据这个划分,具体说明不同区域不同内容是如何回收的
java类的加载机制
主要分为如下几个阶段
-
加载
- 来源:加载的是
.class
文件的字节流,可能来自于jar包、war包、网络等。 -
产物
- 字节流中静态的数据结构转换成运行时数据区中方法区内。也就是从class文件常量池到运行时常量池之间的转换,这个时候运行时的常量中只是字面量和符号引用。
- 生成位于堆的Class对象,是对方法区数据访问的入口。
- 补充
1、2中所述有class文件静态常量池,和运行时常量池,还有一个是字符串常量池的概念。
字符串常量池也要全局常量池:保存的是字符串实例的引用
- 来源:加载的是
String s \= new String("1");
s.intern();
String s2 \= "1";
System.out.println(s \== s2);
String s3 \= new String("1") + new String("1");
s3.intern();
String s4 \= "11";
System.out.println(s3 \== s4);
jdk6
false
false
jdk7+
false
true
String s = new String("1")
- 堆中生成StringObject
- 堆中生成“1”对象
-
常量池生成“1”的引用
所以
- s指向StringObject,哪怕inter之后,返回仍然是StringObject的地址。
- s2指向全局常量池的中的对象“1”的引用
String s3 = new String("1") + new String("1")
此时在全局常量池池是没有“11”的,但是在堆中有StringObject的
在调用 s3.intern()
Jdk6:常量池中生成实际对象“11”,返回其地址,这和StringObject的地址不一样,所以返回false
jdk7+:不在创建对象,而是将SringObject对象的地址存入常量池,此时s3指向StringObject,返回true
类加载阶段由于resolve 阶段是lazy的,所以是不会创建实例,更不会驻留字符串常量池了
-
链接
- 验证:验证是否符合虚拟机规范
- 准备:准备阶段类变量分配内存,并赋初始值,注意此处的初始值指的是0或者null,但是如果是final修饰的类变量,此时就会直接赋予值。
-
解析:符号引用转换成直接引用即具体的内存地址
- 注意,解析阶段有一部分实在加载阶段直接转换成直接引用,即静态链接
- 还有一部分实在运行期间才转换成直接引用,这就是所谓的动态连接
-
初始化
针对对象的主动引用和被动引用决定是否触发。
主动引用
- 使用new实例化对象、读取或设置类的静态变量、调用类的静态方法
- 使用反射实现上述三种行为
- 初始化子类会触发父类的初始化
被动引用
* 初始化类的数组不会初始化类
* 引用父类的静态变量,不会引起子类的初始化
* 引用类的常量不会,final static修饰的变量,因为在准备阶段就完成了解析
初始化的顺序
父类 --- > 子类
静态 ----> 普通 ---> 构造函数
类加载器
类加载器
主要有三类
- BootStrap:JRE/lib/rt.jar 核心库
- ExtClassLoader:JRE/lib/ext/*.jar 扩展库
- AppClassLoader:CLASSPATH指定的所有jar包和目录 系统类加载器
- 自定义的类加载器
双亲委派模式
- 原理:所有的加载请求都会最终到顶层加载器中,当父类反馈自己无法加载之后才会由子类来加载
- 目的:保证核心库的安全
tomcat为什么要自己实现类加载器
- 要保证每个应用程序的类库都是独立的,保证相互隔离
- 在同一个web容器中相同的类库相同的版本可以共享
- web容器也有自己依赖的类库,不能于应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来
- web容器需要支持 jsp 修改后不用重启
- commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
- catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
- sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
- WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
结构构造
-
首先容器和应用之间由共用的
- 容器私有
-
应用共有
- 应用私有
每一个jsp文件都有jsp类加载器
打破双亲委派模式:webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。
如果上层想加载下层中的类,使用线程上下文加载器。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。