注意:栈中一定不存在垃圾,栈中数据用完一个弹出一个,总结来说,栈区、本地方法栈、程序计数器这三块必定不存在垃圾。JVM调优主要是针对方法区、堆(99%)进行调优。
常用的第三方插件(如Lombok)都是操作执行引擎区域,生成对应getter、setter方法
Java中所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,其作用是:将class文件从硬盘读取到内存中
类加载方式有两种:
备注:Java的加载是动态的,并不会一次性加载所有类后才运行,而是保证运行的基础类完全加载到JVM中,其他类只有在需要的时候才加载,为了节省内存开销
类装载执行过程:
定义:实现通过类的全限定名获取该类的二进制字节流的代码块
分类:
public class Car {
public static void main(String[] args) {
Car car1 = new Car();
Car car2 = new Car();
Class<? extends Car> aClass = car1.getClass();
Class<? extends Car> bClass = car1.getClass();
// 输出一致,说明两个实例由一个Class模板实例出
System.out.println(aClass.hashCode());
System.out.println(bClass.hashCode());
ClassLoader classLoader = aClass.getClassLoader();
ClassLoader classLoader2 = classLoader.getParent();
ClassLoader classLoader3 = classLoader2.getParent();
// AppClassLoader
System.out.println(classLoader);
// ExtClassLoader
System.out.println(classLoader2);
// null(Java程序读取不到,Java早期由C,C++语言编写,去除了C++中 指针和内存管理,所以早期也叫C++--)
System.out.println(classLoader3);
}
}
5592464
5592464
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@424c0bc4
null
重新定义一个java.lang.String类,启动时报错“无法找到main方法”
解释:最终会通过“Bootstrap ClassLoader”加载器找到 “rt.jar”包下的“java.lang.String”类,所以不会加载到自定义的String.java
出现这种情况是由于类加载器的“双亲委派机制”
定义:如果一个类加载器收到了类加载的请求,它首先不会自己去加载该类,而是把请求委派给父类加载器,每一层都是如此,这样所有加载请求都会被传送到顶层的启动类加载器中,只有当父加载器无法完成加载请求时,抛出异常通知子加载器尝试加载
这样设计的目的是:为了安全,防止开发人员修改一些系统类。
简单理解:当一个类收到类加载请求时,不会自己去加载,而是将其委派给父类,如果父类不能加载,再交由子类去完成加载
Java安全模型核心就是Java沙箱(sandbox),所谓沙箱机制,就是将Java代码限定在虚拟机(JVM)特定运行环境中,并严格限制代码对本地系统资源访问。通过这种保护措施保护代码的有效隔离,防止对本地系统造成破坏。
沙箱主要限制系统资源访问,系统资源包括:CPU、内存、文件系统、网络。不同级别的沙箱对资源访问限制也可以不一样
所有Java程序运行都可以指定沙箱,定制安全策略
在Java中将执行程序分为本地代码和远程代码两种,本地代码默认是可信任的,远程代码则被看作是不受信任的。对于授信的本地代码,可以访问一切本地资源,而对于非授信的远程代码,在早期Java实现中,安全依赖于沙箱机制。
但如此严格的安全机制也给程序功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时,就无法实现,因此在后续Java1.1版本中,对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限
在jdk1.2版本中,再次改进安全机制,增加了代码签名,无论本地代码还是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机权限不同的运行空间,实现差异化代码执行权限控制
当前最新的安全机制实现,则引入域(Domain)的概念,虚拟机会把所有代码加载到不同系统域和应用域,系统域专门负责与关键资源进行交互,而各应用域部分则通过系统域的部分代理来对各种需要的资源进行访问,虚拟机中不同的受保护域对应不同权限,存在于不同域中的类文件就具有当前域的全部权限
组成沙箱的基本组件:
虚拟机为不同类加载器载入的类提供不同的命名空间,命名空间由一系列唯一名称组成,每一个被装载的类将有一个名字,该命名空间由Java虚拟机为每个类装载器维护,他们之间互相不可见
类装载器采用双亲委派机制:
栈的特点:先进后出(所以main()方法,最先执行,最后结束)
栈:每个线程都有自己的线程栈,主管程序的运行,生命周期和线程同步;线程结束,栈内存释放。故栈中不存在垃圾回收问题!
栈中主要存放:8大基本类型、对象的引用(引用地址)、实例的方法
栈运行原理:每运行一个方法产生一个栈帧。当栈满了就会抛出错误StackOverflowError
三种JVM版本:
HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)
一个JVM只有一个堆内存(垃圾回收基本都在此),堆内存大小是可以调节的。
类加载器读取了类文件后,一般存放 类、方法、常量、变量、以及引用类型的真实对象。
堆内存细分为三个区域:
GC垃圾回收,主要发生在新生区和老年区,假若内存满了就会出现OOM异常
注意:
新生区
所有对象都是在 伊甸园 区 被new出来
假若 伊甸园区 空间达到阈值,触发一次 轻GC,该对象仍存活,就被移到幸存区
当新生区内存达到阈值,触发 重GC,存活的对象移到 老年区
特点:对象更新速度快,短时间内产生大量的“”死亡对象,产生连续可用的空间,所以使用复制清除算法和并行收集器进行垃圾回收,称为初级回收(minor gc)
发展变化:当对象在Eden出生后,经过一次minor GC后,若对象还存活着,并能被另一块survivor区域所容纳,则使用复制算法将其复制到另一块survivor区域,并将该对象年龄+1,当对象年龄达到某个值(默认为15)时,该对象即可变为老年代
老年区
老年代的GC算法采用标记-清除算法
注意:标记-清除算法收集垃圾的时候会产生许多的内存碎片。
永久区
此区域常驻内存的,用于存放JDK自带的class对象,interface元数据,存储的是Java运行时的环境或类信息(此区域不存在垃圾回收,关闭JVM时释放此区域内存)
去永久代
的思想,此时常量池存放于 堆 中查看JVM内存参数:
public class JVMDemo {
public static void main(String[] args) {
// 虚拟机能使用的最大内存(默认占电脑内存1/4)
long max = Runtime.getRuntime().maxMemory();// 单位为字节
System.out.println("max内存:"+max+"字节");
// JVM初始化总内存(默认占电脑内存1/64)
long total = Runtime.getRuntime().totalMemory();// 单位为字节
System.out.println("total内存:"+total+"字节");
}
}
max内存:3793747968字节
total内存:257425408字节
调整JVM参数:
-XX:+PrintGCDetails打印GC时内存信息
from、to对应幸存者0、1区(幸存者01区会随时交换位置)
注意:上述堆空间模型分布,逻辑上存在,物理上不存在(从打印结果可看出,PSYoungGen+ParOldGen就等于堆空间,而元空间并未占用堆内存,实际存于本地内存)
查看GC过程
public class JVMDemo {
// JVM参数 -Xms8m -Xmx8m -XX:+PrintGCDetails
public static void main(String[] args) {
String str = "khfkoanngopaalknfasopajr";
while (true) {
str += str + new Random().nextInt(918246192) + new Random().nextInt(189242864);
}
}
}
当项目出现OOM异常,尝试扩大JVM内存仍无法解决问题时,就需要排查何种原因导致
分析工具有 MAT、JPofiler,作用有:
工具安装:
基本信息:
大对象数据:
线程对应异常行:
在Java中,开发者无需显式地释放一个对象地内存,而是由虚拟机自动执行。
在JVM中,有一个垃圾回收线程,它是低优先级的,只在虚拟机空闲或堆内存不足时,才会触发执行,扫描没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收
Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的。Java没有提供释放已分配内存的显式操作方法。
GC回收主要针对 堆和方法区(本质也在堆中)
GC回收分为:轻GC、重GC(full-GC)
如何判断对象可被回收
一般有两种办法判断:
GC常见题目:
内存效率:复制算法>标记-清除>标记-整理
内存整齐度:复制算法=标记-整理>标记-清除
内存利用率:标记-整理=标记-清除>复制算法