转载

java的运行时的内存区域、java类的加载机制

今日复习内容:java的运行时的内存区域、java内存模型、java类的加载机制

java的运行时的内存区域

主要分为以下几个部分:

  • 线程共享

    • 方法区
  • 线程独享

    • 虚拟机栈
    • 本地方法栈
    • 程序计数器

虚拟机栈

  • 线程私有,生命周期同线程。
  • 虚拟机栈存放栈帧,栈帧中有局部变量表、部分结果值、方法初始化信息和返回信息。
  • 方法的执行通过压栈和弹栈实现
  • 常见错误:

    OutOfMameryError
    StackOverFlowError
    

本地方法栈

调用的本机的方法,比如C的方法

程序计数器

线程切换的时候,记录当前线程执行的位置

  • 记录指令的地址
  • 因为占用空间小不会出现OOM

用于存放对象实例,是虚拟机管理的最大的内存,是GC管理的主要区域。从垃圾回收的角度看,堆区域还可以细分,这和不同的JVM实现有关。

方法区

没有规定说方法区在哪一个位置,所谓的方法区只是一种规范,不同的JVM实现存放的地址不一样

方法区中主要存放的是,类的元信息,类的静态变量、常量。

在JDK1.8之后,HotSpot方法区的实现

  • 类的元信息放入非堆区即本地内存,作为元数据区。
  • 类的静态变量和常量则和堆共享内存,是堆逻辑上的一部分。

java内存细分

当我们谈论Java的内存细分的时候,线程独享三块内存区域:

  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

因为其因为生命周期和线程同步,所以它们的回收时间都是固定的,线程死亡之后自然就回收了

而堆和方法区回收时间是不确定的,所以讨论内存模型的时候往往都需要讨论内存回收的机制,所以Java的内存模型主要是建立在 堆和方法区上的。

那么内存模型的具体划分应该是:

  • 堆区,虚拟机来分配:

    • 新生代

      • Eden区域80%
      • 两个Survivor区域各占10%
    • 年老代
  • 非堆区,本地内存分配

    • 元数据区

后面根据这个划分,具体说明不同区域不同内容是如何回收的

java类的加载机制

主要分为如下几个阶段

  • 加载

    1. 来源:加载的是 .class 文件的字节流,可能来自于jar包、war包、网络等。
    2. 产物

      • 字节流中静态的数据结构转换成运行时数据区中方法区内。也就是从class文件常量池到运行时常量池之间的转换,这个时候运行时的常量中只是 字面量符号引用
      • 生成位于堆的Class对象,是对方法区数据访问的入口。
    3. 补充

      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文件,不会传递给父类加载器。

如果上层想加载下层中的类,使用线程上下文加载器。

原文  https://segmentfault.com/a/1190000022472414
正文到此结束
Loading...