定义:
Java VirtualMachine -java 程序的运行环境 (ava 二进制字节码的运行环境)
好处:
比较:
jvm jre jdk
自己百度查找
Program Counter Register 程序计数器(寄存器)
如下图所示
右边就是简单的java代码打印操作,编译成左侧的二进制字节码。
经过解释器——>机器码——>CPU执行。
程序计数器在这里面的作用就是记住下一条jvm指令的执行地址。
第一条指令地址是0,第一条指令交给解释器去执行的同时会把第二条指令的地址3放入程序计数器。第一条执行完之后,解释器会去取出3来执行......
物理实现: 通过CPU中寄存器(速度快)实现
每个线程都有自己的程序计数器。
每一个线程会有被分配一个时间片,在当前时间片内不能执行完会去执行别的线程的代码,直到轮到下一个时间片。
切换到别的线程时要记住当前执行到哪里,还是要用到程序计数器。通过私有的程序计数器知道下一行代码的地址。
栈是一种普通的先进后出的数据结构。
java的虚拟机栈则是线程运行需要的内存空间。
一段代码有多个方法组成,一个栈帧表示一次方法的调用,栈帧就是每个方法运行需要的内存。
运行:调用第一个方法时会给第一个方法划分一个栈帧空间,并压入栈内,执行完后会出栈,也会释放该方法占用的内存。
然后方法1调用方法2时会产生一个方法2的栈帧并入栈,然后方法2调用方法3也会产生并入栈,如下图所示。
Java Virtual Machine Stacks (Java 虚拟机栈)
栈帧大小由方法里的参数以及局部变量的个数决定
1.垃圾回收是否涉及栈内存?
栈内存是一次次方法调用产生的栈帧内存,调用结束后会弹出栈,会自动回收,不需要垃圾回收 管理,垃圾回收是回收堆内存中的无用对象。
2.栈内存分配越大越好吗?
运行java代码时是可以指定栈内存大小的,使用-Xss size,下图还有不同系统下默认栈内存的大小和设定内存的示例。
栈内存越大会让线程数变少,512mb的物理内存下,每个线程的栈内存设置1mb大小可以运行512个,设置2mb大小可以运行256个线程。不会提高线程效率,但可以增加递归的层数。
3.方法内的局部变量是否线程安全?
根据该变量是每个线程共享还是线程私有判断。下图是一个方法,方法内有一个局部变量。
该方法被调用两次时会有两个不同的栈。每个线程都会有私有的局部变量。因此这里不会有线程干扰的问题。
假如将x改为static int x=0;的话就会出现线程干扰,如果不加保护的话会有线程安全问题。
总结:共享需要考虑线程安全,私有就不需要考虑。
一般不会有单片过大,栈帧里都是方法参数和局部变量。可以通过设置栈内存大小达到
在将对象转换成json对象时也会有栈溢出,这种两个类的循环问题会导致json解释器出现问题。
可以通过一个@JsonIgnore注解达到在json转换对象时忽略变量的效果。
linux环境下运行一段java代码导致cpu占用过高,可以使用top命令定位到哪一个进程占用,但看不见是哪一个线程导致的。
在linux下使用ps H -eo pid,tid,%cpu 命令可以看见所有线程的pid(进程号),tid(线程号),%cpu(cpu占用)。
使用ps H -eo pid,tid,%cpu | grep 32655 后面加上| grep pid过滤无关进程的线程。
生产环境不推荐jstack,因为打印线程信息jvm会暂停其他线程
然后将线程编号32665转换成16进制(7F99)在输出内容中查找
在jstack 输出内容中可以看见一个nid=Ox7f99的线程,状态为RUNNABLE.
看见问题出在第8行代码。如下图源码第8行是个死循环。
nid、pid 和 tid 是计算机系统中常用的三个标识
线程死锁导致的无结果下使用jstack命令查看,下输出内容最后可以看见有关死锁信息。
两个线程都想获得a,b,但是都在等对方放开拥有的对象,然后陷入死锁。
产生死锁的四个必要条件:互斥、不可剥夺、请求和保持、循环等待。
定义: java虚拟机在调用本地方法时需要给本地方法提供的内存空间
在Object这个类中就有很多,比如Object的clone方法的声明是native,这个native的实现是c/c++,java代码是间接调用native
通过 new 关键字,创建对象都会使用堆内存
特点:
下图所示方法中String类型的对象a会一次次变大,直至堆溢出。
使用-Xmx size改变堆空间大小。
修改前26次才溢出,修改后17次溢出。
有可能堆内存较大,运行时间短,在系统前期看不出问题,后期才会爆掉,故测试时可以将堆内存设置较小进行排查。
1.jps 工具
查看当前系统中有哪些 java 进程
2. jmap 工具
查看堆内存占用情况 jmap -heap 进程id (只能看某一瞬间的情况)
3.jconsole 工具
图形界面的,多功能的监测工具,可以连续监测
4.jvisualVM 工具
图形化界面,可以抓取当前快照
new一个10MB的数组对象,后面置为null,然后gc显式回收。
运行后通过jps查看进程id,jmap -heap 18756在1~2,2~3,3之后三个时间点抓取快照信息。
最大堆内存占用MaxHeapSize是4个G
Eden Space就是专门为new 出来的对象准备的。
1~2之间
数组创建之前使用了6Mb
2~3之间
创建数组对象之后使用16mb,
3之后
垃圾回收之后变成1.2mb
使用jconsole工具的界面。
垃圾回收之后,内存占用任然很高。
新生代被回收了,老年代没有被回收。
使用新的工具jvisualvm可视化虚拟机
保存快照之后进行查找最大的类
源代码
两百个Student对象,每个都开了一个1mb大小的byte数组。并且一直在作用范围内,无法回收,内存占用居高不下。
通过可视化界面的堆 dump按钮进行排查。
按照jdk_jvm_1.8中的定义
永久代和元空间都是方法区这个概念的实现。
永久代和元空间最本质的区别就是 前者使用的是jvm内存 后者使用的是操作系统内存。
图中常量池是运行时常量池。
下图代码就是一个加载了10000个类的代码,最外层继承实现了类加载器,在循环内指定版本号,类名,包名,父类,接口等信息创建一个新类。
这里元空间和永久代都没有设置上限,这里需要设置元空间和永久代大小。
-XX:MaxMetaspaceSize=8m 元空间
-XX:MaxPermSize=8m 永久代
元空间运行报异常
场景:
spring和mybatis都使用到了cglib技术。
下面的这段代码的二进制字节码含有如下信息。
使用如下命令查看该代码反编译后的结果
javap -v HelloWorld.class
常量池部分
虚拟机指令部分
执行指令时下面第一条就是获取静态变量,#2在常量池里面找。
ldc是找到一个引用地址。
运行时常量池里面#1,#2...这些会变成内存地址。