虚拟机就是java的运行环境,使得java能一次编译,到处运行\
常见术语:
JVM:java运行的最基本环境
JRE:java Runtime Environment = JVM + 基础类库
JDK:Java Development Kit = JVM+ 基础类库 + 编译工具
javaSE:ava Standard Edition = JDK+基础开发
javaEE: Java Enterprise Edition = JDK+基础开发+应用服务器
基本结构
内存结构
垃圾回收器
java程序加载机制
java程序的生命周期
编写源码->代码编译->虚拟机加载->虚拟机储存->虚拟机运行->垃圾回收
JAVA代码运行机制
代码编译机制
将.java文件编译成.class文件
javac xxx.java
对.class文件的编译方式有两种
解释执行:逐条将字节码编译成字节码并执行
既时编译:即将一个方法中包含所有的字节码编译成机械码在执行
Hotspot默认使用混合编译(mixed mode),那些编译,那些优化,则是有监视器(Profile Monitor)决定
可以使用javap命令文件.class文件
javap -c xxx.class > xxx.txt
代码加载机制
代码加载过程
Loading:使用类加载器加载类
LinKing:是指将创建成的类合并到java虚拟机中,使之能够执行的过程
Initializing:则是为标记为常量值的字段赋值,利用这个机制可以使用的单例模式的代码可以延时加载
类加载器
多个类加载器
Bootstrap ClassLoader:加载$JAVA_HOME中jre/lib/rt.jar里面所有的class或Xbootclassoatch选项指定的jar包
Extension ClassLoader:加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
App ClassLoader:加载classpath中指定的jar包及Djava.class.path所指定目录下的类和jar
Custom ClassLoader:通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身 需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
双亲委派原则:
由于出现了多个类加载,那么就可能会出现类重复加载的问题.
当一个类加载器收到类加载的指令的时候,自己不会去加载,而是让父类去加载,如果父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需要的加载),然后自己加载
如此就可以保证重复被类加载器加载的类是同一个
代码运行机制
内存可以分为两部分
代码的储存区域
方法区
堆
代码的运行机制
栈
计数器
本地方法区(主要运行native的方法)
代码被加载之后都存在方法区,代码执行过程中创建的对象存储在堆中
代码主要实在栈里面运行
每个线程都会在栈里面被分配一块独立的内存空间
每个线程中执行的方法,会当前栈中,再次被分配一个空间,即栈帧
JVM的内存结构
程序计数器(Program Counter Register )
用于多线程在切换了记录当前运行位置,以保证切换回来的时候可以继续
是唯一一个没有内存溢出的空间
虚拟机栈
Java Virtual Machine Stacks(Java 虚拟机栈) 是每个线程运行时所需要的内存。
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
栈内存可能出现的异常的情况
-(1)当固定大小情况下,线程请求分配的栈容量大于Java虚拟机栈最大容量时,抛出异常:StackOverFlowError。
- (2)当可拓展时,如果在拓展过程中,无法申请到足够的内存时,抛出异常:OutOfMemoryError(比如:JVM运行内存被占满,此时已经无处可以申请内存了)。
栈内存的大小
可以自定义设置栈的大小,通过java运行的执行设置栈的大小
java -Xss1024k Test001
java -XX:ThreadStackSize=1024 Test002
查看默认的栈大小
java -XX:+PrintFlagsFinal -version | findstr /i "ThreadStackSize"
不同的操作系统栈大小不同
Linux / ARM(32位):320 KB
Linux / i386(32位):320 KB
Linux / x64(64位):1024 KB
OS X(64位):1024 KB
Oracle Solaris / i386(32位):320 KB
Oracle Solaris / x64(64位):1024 KB
一台服务器可以创建线程的计算公式,根据栈的大小计算可以创建线程的数量
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
- MaxProcessMemory 指的是一个进程的最大内存
- JVMMemory JVM内存
- ReservedOsMemory 保留的操作系统内存
- ThreadStackSize 线程栈的大小
本地方法栈
1,本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。本地方法栈服务的对象是JVM执行的native方法,其就是一个java调用非java代码的接口,作用是与操作系统和外部环境交互
2,某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。当C程序调用一个C函数时,其栈操作都是确定的。传递给该函数的参数以某个确定的顺序压入栈,它的返回值也以确定的方式传回调用者。同样,这就是虚拟机实现中本地方法栈的行为。
3,能本地方法接口需要回调Java虚拟机中的Java方法,在这种情况下,该线程会保存本地方法栈的状态并进入到另一个Java栈。
4,描绘了这样一个情景,就是当一个线程调用一个本地方法时,本地方法又回调虚拟机中的另一个Java方法。
堆
heap定义
堆内存被所有线程共享
通过new出来的对象都是在堆里面
堆会出现内存溢出问题
堆的大小
有初始 大小
有最大值大小
//字节大小 K -> M -> G -> T
double initialMemory= Runtime.getRuntime().totalMemory()/1024/1024;
double maxMemory = Runtime.getRuntime().maxMemory()/1024/1024;
System.out.println("堆内存的初始总量Xms:"+initialMemory);
System.out.println("堆内存的最大总量Xmx:"+maxMemory);
默认情况下,初始内存大小=物理电脑 内存大小/64,最大内存大小=物理电脑内存/4
修改初始值和最大值的内存大小
-Xms10m -Xmx10m
-Xms10m 初始值的大小 -XX:InitialHeapSize=10m
-Xmx10m 最大值的大小 -XX:MaxHeapSize=10m
查看正在运行的java程序状态
jps:查看系统上运行的java程序进程
jstat:查看java程序的运行状态
jvisualvm:可以调用jdk提供的可视化工具进行查看
堆内存结构
堆内存要分一下结构
新生代+老年代+永生代(1.7)
新生代+老年代+元空间(1.8)
新生代:新创建的对象就是放在新生代
eden区:所有对象最先出生的地方
Servivor区:又分为s0和s1也就是from和to
新生代和老年代的比例是1:2
如果eden满了会发生MinorGC
在eden区满了之后会触发MinorGC,将eden中存活下来的对象放入到Servivor区的s1,然后清空eden区的内存
等eden的区的内存在在满了之后,又会触发MinorGC,会将S1中所有存活下来的对象和eden中存活下来的对象都放入到s0的区域,然后将s1和eden区的对象全部清空
如果在minorGC中,Servivor区放不下了,就会放入到老年代中
Servivor区如果满了,会将一部分对象放到老年代
老年代:
生命周期长的储存对象
一般而言新生代的对象经过15次GC没有被释放就会被放入老年代
如果老年代满了就会进行MajorGC
如果MajorGC并释放老年代的对象,那么就会OOM
永生代
在1.7的时候大小是固定的
在1.8之后就改成了matespace元空间,大小不受限制,和物理内存一样大
内存的活动的流程:
程序运行会先加载程序的元数据放入到meta区
在程序的过程中逐步的创建对象放入到Eden区
当Eden区中的内存满了以后,会进行Young GC/minGC,其中大多数的对象被回收,没有被回收的拷贝到(Copying)s0区
再次YGC,将eden区和S0区的没有被回收的对象拷贝到S1区
再次YGC,eden+S1 -> S0,然后循环4,5
年龄足够拷贝到老年代(一般是15次,CMS6次)
如果新创建的对象占用内存很大,则直接分配到老年代,当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。
关于GC
在内存GC的时候所有线程都会暂停SWT
minGC触发条件,就是eden区内存满了
oldGC触发条件,就是老年代内存满了
fullGC=oldGC+minGC+mateGC
调用System.gc()时,系统建议执行Full GC,但是不必然执行。
- 老年代的空间不足。
- 方法区的空间不足。
- 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。
- 年轻代放不下,放到老年代中,若该对象大小大于老年代,也会触发Full GC。
尽量减少内存的碎片
方法区
和堆内存差不多,逻辑上其实就是堆内存
主要是存放的内容
方法区需要储存每个加载的类(类,接口,枚举,注解)
域(属性)信息
JVM需要保存所有方法的信息及其声明的顺序
non-final的类变量(static)
方法区大小设置
1.7的设置
-XX:Permsize 设置永久代初始分配空间
-XX:MaxPermsize 设定永久代最大可分配空间
OutOfMemoryError:PermGen space OOM错误
1.8的设置
-XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 元数据区大小,其默认值依赖于平台。windows下,-XX:MetaspaceSize=21M -XX:MaxMetaspaceSize=-1 即没有限制。
方法区大小测试
//-XX:MaxMetaspaceSize=100m
public class TestMethod extends ClassLoader {
public static void main(String[] args) {
int j=0;
TestMethod test = new TestMethod();
try{
for(int i=0;i<300000;i++){
//ClassWriter作用是生成类的二进制字节码
ClassWriter cw = new ClassWriter(0);
//版本号,public,类名,包名,父类,接口
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/Object",null);
//返回byte[]
byte[] code = cw.toByteArray();
//执行了类的加载
test.defineClass("Class"+i,code,0,code.length);
j++;
}
}finally {
System.out.println(j);
}
}
}
垃圾回收
如何定义垃圾
引用计数法(reference count):如果指向当前对象引用是0那么这个对象就会被回收,虽然简单,但是不能解决循环引用的问题
根可达性分析(RootSearch):为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
垃圾如何清理
标记清理(Mark-Sweep)最基础的垃圾回收算法,分为两个阶段,
标记和清除.
标记阶段标记出所有要清除的对象,
清除阶段清除被标记的对象所占用的空间
该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到利用空间的问题
复制算法(copying)为了解决Mark-Sweep算法内存碎片化的缺陷而提出的算法,按内存的容量划分为两块等大小的.
但是最大的问题是可用内存被压缩到了原本的一半.并且存货对象增多的话,Copying算法的效率大大降低
-
标记压缩算法(Mark-Compact**)**:结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象
-
分带收集算法:分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
垃圾回收算法
在1.8默认情况下使用的是UseParallelGC
查看默认的垃圾回收器的算法
java -XX:+PrintCommandLineFlags -version
如何切换垃圾回收器
-XX:+UseG1GC
可以通过jinfo查看,垃圾回收器的配置
小结:
JVM的基本构成
代码运行机制
内存结构
垃圾回收算法
代码运行机制
解释翻译+JIT既使翻译
双亲委派机制
内存结构
程序计数器
唯一没有内存泄漏问题
虚拟机栈
每个线程是独立拥有的
设置栈的大小
本地方法栈
执行C方法的内存空间
堆空间
新生代
eden区
suvivior区
老年代
元空间
垃圾回收方式
minGC
oldGC
fullGC=minGC+oldGC
设置堆大小的指令和查看内存变化的工具
方法区
用于储存代码,静态变量的区域
垃圾回收
如何定义垃圾
回收的基本算法
各种垃圾回收器
在1.8设置使用G1垃圾回收器