1、jvm 内存结构分区
jvm内存结构主要包含5个区域:java虚拟机栈、本地方法栈、程序计数器、堆、方法区
-
java虚拟机栈
每个线程的创建都会创建一个java虚拟机栈,每次调用方法又生成一个栈帧推入java虚拟机栈中。方法调对应的栈帧出栈。一旦所有栈帧都出栈了,该线程也就结束了。
栈帧包含4个部分:局部标量表、操作数栈、动态连接、完成出口
-
本地方法栈
本地方法栈是一个和java虚拟机栈类似的区域,区别在于本地方法栈是用来管理本地方法调用的。
-
程序计数器
java程序都多线程的程序,所以就会存在上下文的切换。那程序计数器就用来记录各个线程的执行信息的。
-
堆
堆是线程共享的一个区域,主要用来存放:对象和数组
新生代
eden区
存放新new的对象和数组
survivor区
存放经历过一次Minor GC或多次Minor GC为晋级到老年代的对象或数组
老年代
存放通过相应机制晋级的对象和数组。
机制一:正常提升:通过15次Minor GC未被GC的对象或数组。可以通过参数: ‐XX:+MaxTenuringThreshold 来自定义。
机制二:动态对象年龄判:幸存区中相同年龄对象大小的和,大于幸存区的一半,大于或等于 age 的对象将会直接进入老年代。
机制三:大对象直接在老年代分配:默认0MB,可以通过参数:-XX:PretenureSizeThreshold 来自定义。
机制四:分配担保:新生代内存空间不足,又有新的对象需要分配空间。那该对象将直接晋级到老年代。
-
方法区
方法区主要是用来存放:类信息、常量池、方法数据、方法代码。
类信息包含:类的版本、字段、方法、接口和父类信息等。tips:1.8 以后类信息是存在本地内存的(元空间)。
常量池包含:静态常量池、运行时常量池。tips: 常量池是在JVM堆内存里的。
总结:方法区是一个JVM标准,是一个逻辑概念。1.8以前是用永久代来进行实现的。1.8以后用元空间取代了永久代。
2、Java类加载机制
java类加载的生命周期主要包含7个阶段:加载、验证、准备、解析、初始化、使用、卸载。
-
加载
加载的主要过程是将外部的.class文件,加载到JVM的方法区内。
对于我们的java项目无论是jar或war,并不会一上来就把包里的**.class**文件全部加载进去,而是会到需要时才会进行加载(如:new对象或反射生成对象)。
而为了避免重复的加载,加载时会遵循双亲委派原则。但是也有打破双亲委派原则的情况,如tomcat自定义的类加载器等。
- 连接
- 验证
验证阶段主要是为了验证加载进入方法区的.class文件是否符合当前虚拟机的标准,经常出现错误的情况有:用低版本的jvm跑一些高版本的java程序。
- 准备
准备阶段主要是为类的静态变量分配内存,初始化为系统的初始化值。如 int 初始化值 0。
- 解析
将符号引用转化为直接引用。 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和修饰符 直接引用:直接指向对应的内存位置。
- 初始化
为类的静态变量赋真正初始值,执行静态代码块。如果有父类先初始化父类
-
使用
-
卸载
tips :
new 一个对象的过程:判断对应的类是否已经加载,如果没有加载则进行上述的类加载过程。类加载完毕后创建对象,过程如下; 1、为对象在新生代分配大小合适的内存空间。
2、为实例变量赋默认值。
3、设置对应的对象头信息。
4、执行构造器。
3、双亲委派机制
双亲委派机制是为了jvm为了避免类的重复加载,类的加载安全。
-
双亲委派大概流程
大概流程就是:每次类加载的时候都不自己加载,让父类进行加载,依次上推直至加载不了。
-
类加载器
Bootstrap ClassLoader
启动类加载器:主要任务加载核心类库,也就是rt.jar、resources.jar、charsets.jar 等。也可以指定jar包路径,
参数为:-Xbootclasspath
这个加载器是C++编写的,随JVM启动
Extention ClassLoader
扩展类加载器:主要任务加载 lib/ext 目录下的 jar 包和 .class 文件。也可以通过系统变量 java.ext.dirs 可以指定这个目录。
这个加载器是java类。
App ClassLoader
应用程序类加载器: 主要加载我们自己写的java类,也就是classpath下的jar和class文件。
App ClassLoader 也同样集成自 URLClassLoader
Custom ClassLoader
自定义类加载器:支持个性扩展,如:tomcat打破双亲委派。
-
打破双亲委派的案例
- 案例一:Tomcat
Tomcat发布war包应用,其实已经打破了双亲委派的原则。
-
为什么要打破双亲委派原则:
在我们开发的war包应用中一般都会去依赖一些第三方的框架或jar包。并且我们通常会在一个tomcat中运行多种不同的war包应用。我们并不能保证这些war包所依赖的第三方框架和jar包都是同一个版本,那么就可能发生版本冲突。
-
怎么打破双亲委派原则 :
怎么为了解决这个问题,tomcat就定义了WebAppClassLoader来进行类加载,只有等待WebAppClassLoader没法加载,就会交给上层的 ClassLoader 进行加载。
-
大概的Tomcat类加载器结构
结尾
欢迎各位同学指点交流