JVM
JVM
类加载过程
加载:
将编译成的字节码文件加载到JVM内存中, 并生成一个代表该类的 java.lang.Class 对象
验证:
检查字节码是否规范,
确保二进制字节流格式符合预期(比如说是否以 cafe bene 咖啡北鼻开头)。
是否所有方法都遵守访问控制关键字的限定。
方法调用的参数个数和类型是否正确。
确保变量在使用之前被正确初始化了。
检查变量是否被赋予恰当类型的值
准备:
对类变量(也称为静态变量,static 关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)
如
public static int value1 = 123;
在此阶段初始值为0但
public static final int value2 = 123;
在此阶段初始值为123解析:
将常量池中的符号引用转化为直接引用
直接引用通过对符号引用进行解析,找到引用的实际内存地址
初始化:
为变量赋予代码中的值
java中的native
作用: 扩展java的功能, 使java可以调用其他语言的代码完成任务. 比如用户上传一个视频文件,需要后台给视频加上水印,或者后台分离视频流和音频流,这个事Java就做不了,只能交给C语言去处理,然后Java调用C语言的接口. 因为涉及操作系统底层的事件,Java是处理不了的
一个native方法就是一个Java调用非Java代码的接口。一个native方法是指该方法的实现由非Java语言实现,比如用C或C++实现
Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
JNI的书写步骤如下:
- a.编写带有native声明的方法的Java类
- b.使用javac命令编译编写的Java类
- c.使用javah -jni ****来生成后缀名为.h的头文件
- d.使用其他语言(C、C++)实现本地方法
- e.将本地方法编写的文件生成动态链接库
一个jni的实例
环境为linux, windows由于gcc编译时的一些问题, 搜寻未果
HelloWorld.java
1
2
3
4
5
6
7
8
9class HelloWorld {
public native void hello();
static {
System.loadLibrary("hello");
}
public static void main(String[] args) {
new HelloWorld().hello();
}
}用于调用其他语言, 本例中为c
javac HelloWorld.java
, 生成HelloWorld.classjavah -jni HelloWorld
, 生成HelloWorld.hHelloWorld.c
1
2
3
4
5
6
7
JNIEXPORT void JNICALL Java_HelloWorld_hello(JNIEnv * env, jobject obj) {
printf("Hello Worlddd!\n");
return;
}被调用的c代码
生成动态链接库文件 libhello.so
1
2
3
4gcc -Wall -fPIC -c HelloWorld.c -I ./ -I $JAVA_HOME/include/linux/ -I $JAVA_HOME/include/
生成HelloWorld.o
gcc -Wall -rdynamic -shared -o libhello.so HelloWorld.o
生成libhello.soexport LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
, 暂时将当前目录添加进LD_LIBRARY_PATHjava HelloWorld
- 最终: HelloWorld.class libhello.so两个文件
==静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关==
栈
- 存放8大基本类型, 对象引用, 实例的方法
- 程序运行, main最先进栈, 如果main调用方法a(), a()进栈…方法结束, 方法出栈
- 程序正在执行的方法, 一定在栈顶
堆(heap)
一个JVM只有一个堆内存, 大小可以调节
划分
- 新生区(new)(young)(==PSYoungGen==)
- 伊甸园区(==eden==)
- 幸村区0区(==from==)
- 幸村区1区(==to==)
- 养老区(old)(==ParOldGen==)
- 永久区(permanent)(元空间 ==Metaspace== (JDK8以后))
- 新生区(new)(young)(==PSYoungGen==)
GC主要发生在伊甸园区和养老区
OOM, 堆内存溢出
新生区
- 类诞生 成长甚至死亡的地方
- 所有的对象都是在伊甸园new出来的
- 伊甸园满后, 一次轻GC, 存活下来的到幸存0区, 0区和1区是交换的
元空间
- 包含方法区,方法区中包含常量池
- 还包含运行时的一些环境
OOM分析
调大堆内存, 若还溢出, 说明代码可能有问题
idea->Edit Configurations…
-Xms1024m -Xmx1024m -XX:+PrintGCDetails
Jprofiler
- idea 安装插件Jprofiler, 用于产生dump文件, 内含java对象信息
- 本机安装客户端, 用于查看dump文件
- 还需增加虚拟机配置
-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
GC
- 自动回收, 可以手动建议JVM进行回收, 但回不回收由JVM决定
- 大部分的回收发生在新生代
- 轻GC(普通GC)发生在新生代和幸存区(满的时候), 重GC是全局
GC算法
- 标记清除法
- 第一次扫描, 对活着的对象进行标记
- 第二次扫描, 对标记的对象进行清除
- 优点: 不会浪费额外的空间
- 缺点: 两次扫描浪费时间, 会产生内存碎片
- 标记整理(压缩)法
- 在标记清除法基础上还来扫描, 移动对象, 避免产生内存碎片
- 可以标记清除几次再来压缩, 以减少开销
- 复制算法
- 适用于对象存活率低的场景(新生区)
- 当幸存0区和1区都有对象时, 会将其中一个区的对象复制到另一个区, 保证有一个是空的, 称为to
- 默认经历过15次GC后, 对象还没有死, 就会进入养老区
- 优点: 没有内存碎片
- 缺点: 浪费一半空间(0区 1区)
- 引用计数器
- 记录每个对象的引用次数, 当次数为0时, 清除
GC算法对比
- 内存效率:复制算法>标记清除算法>标记压缩算法(时间复杂度)
- 内存整齐度:复制算法=标记压缩算法>标记清除算法
- 内存利用率:标记压缩算法=标记清除算法>复制算法
分代收集算法
年轻代:存活率低->复制算法
老年代:区域大, 存活率高->标记清除 标记压缩混合实现