堆:线程共享,new出来的对象,类的实例区,内部又分为新生代和老年代,新生代又分为EDEN、s1、s2;
虚拟机栈:或者叫线程栈,每个线程分配一个虚拟机栈,属于线程私有,方法执行时创建栈帧,栈帧里面主要存放:局部变量、操作数栈(计算过程中的中间结果)、动态链接(运行时常量池中该栈帧所属方法的引用)和方法出口(方法最初被调用的位置)
元空间:或者叫方法区,线程共享,主要是运行时常量池:类信息、常量、静态变量、即时编译器编译后的代码缓存
程序计数器:线程私有,记录当前线程执行的代码位置(字节码)
本地方法栈:线程私有,虚拟机运行时使用到的内地Native方法(C++的方法)
- 复制算法
- 内存换分为等大的两块
- 每次使用其中的一块,这一块使用满后,将存活的对象复制到另一块上,把已使用的内存清理掉
优缺点:内存效率高,不易产生碎片,但是可用内存压缩为原来的一半,如果存活的对象增多,算法效率会降低
- 标记-清除算法
- 第一阶段,标记需要回收的对象(根据根可达算法)
- 回收被标记对象占用的空间
优缺点:内存碎片化严重,大对象可能找不到连续的内存空间
- 标记-整理算法
- 标记过程和标记清除算法一致,先标记出需要回收的对象
- 标记后将存活对象移向内存一端
- 清除端边界外的对象
- 新生代使用复制算法:每次现将eden和s1中的对象复制到s2中,然后回收eden和s1,之后再将s2复制到s1上
- 老年代使用标记整理算法:老年代每次回收少量对象
- 对象头的markword中存放的有对象年龄,每次新生代回收,回收不掉的话,年龄+1
JVM在进行垃圾回收时,需要确定垃圾对象,也就是没有被引用的对象。但是直接找垃圾对象比较耗时,所以先找非垃圾对象,也就是正常对象。那么就需要从某些根出发,寻找正常对象,这些根就被称作GCRoot,主要包括:
JVM栈中的引用的对象、方法区中的静态变量和常量(字符串常量池中的引用)、本地方法栈中引用的对象、正在运行的线程
什么是双亲委派:类加载器收到类的加载请求后,不会先去加载,而是去委派给父类加载器去完成,主要包含三层classloader:
- APPClassLoader:主要负责加载应用程序主函数类下的类,也就是CLASSPATH(JAVA_HOME/lib)下的类
- ExtClassLoader:主要负责jre/lib/ext目录下的一些扩展类
- BootstrapLoader:核心类库的加载,比如java.lang包下的
过程:
- 类加载器收到类的加载请求后,不会先去加载,而是先去委派给父类去加载,也就是按照app==》ext==》bootstrap的顺序
- 每一层类加载器都会按照这个方式来加载,所有的加载请求最终都会到顶层的类加载其中去尝试加载
- 父加载器反馈无法完成加载时,子加载器才会尝试自己去加载
原因或者好处
- 避免核心类被篡改,比如String类
- 防止类的重复加载,加载过就不需要再次加载
破坏
JNDI:常见就是jdbc驱动,数据库Driver定义在jdk中,具体实现是各个数据库服务商。DriverManager要加载Driver接口实现类,然后进行管理,但是DriverManager是由启动类加载器进行加载的,而这个启动类加载器默认值加载JAVA_HOME下面的lib,但我们真正要加载的是各个实现类,需要有系统类加载器进行加载,这个时候就需要启动类加载器委托系统类加载器去加载Driver实现类,从而破坏了双亲委派。
Tomcat热部署也打破了:一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。



