我们学的是oracle的jvm学习路线:程序计数器:定义:ProgramcounterRegister程序计数器(寄存器)作用,是记住下一条jabm指令得执行地址特点是线程私有得,
我们学的是oracle的jvm
学习路线:
程序计数器:
定义:
Program counter Register 程序计数器(寄存器)
作用,是记住下一条jabm指令得执行地址
特点
是线程私有得,每一个线程都有自己得程序计数器(主要用于记住,当前执行到线程代码得地址)
不会存在内存溢出
回顾数据结构栈:
先进后出,后进先出的原则,
参考递归
栈-线程运行需要的内存空间,
一个栈有多个栈帧组成
栈帧对应一次方法的调用,所以,在线程运行的时候每个方法需要的内存我们称为栈帧。
栈帧:每个方法运行时需要的内存;
这里回顾一下方法调用的操作:main 调用方法a ,a方法内部调用的b,这个时候按照栈的加载进入程序,最后释放的是main方法;
Java Virtual Machine Stacks(java虚拟机栈)
*)每个线程运行时需要的内存,称为虚拟机栈
*)每个栈帧由多个(Rrame重新载入帧)组成,对应着每次方法调用时所占用的内存
*)每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法 (栈顶部栈帧称为活动栈帧)
问题::
1.垃圾回收是否涉及我们的栈内存?
不需要,栈内存是栈帧的一次次调用,栈帧内存在每一次结束调用后,都会被弹出栈,会被自动回收掉,不需要垃圾回收,垃圾回收回收的是堆内存中的无用对象,栈内存不需要。不会也不需要
2.栈内存分配越大越好么?
内存越大,线程数越小,(物理内存是固定的,),假设:一个线程使用的栈内存使用的1兆的内存,我物理内存是500,理论上可以运行五百个线程;
如果我给每个给每个栈内存设置了2兆的内存,理论上就变成只能运行250个线程。
建议使用系统默认内存
错,栈内存过大,相应就是代码增多,提高了解耦难度,冗余度也随之提高
这里可以指定栈内存的大小(-Xss),不指认大小,会跟根据操作系统默认大小
Windows,会根据自身的虚拟内存来影响每个栈的大小
3.方法内部的局部变量是否线程安全?
1.分析这个变量是否私有还是线程共有:是否static修饰
线程不共用一个变量的时候:
局部变量,对应一个栈帧,线程内每次调用一个方法都会产生一个栈帧,
也就是说不同线程拿到的成员变量都是自己私有的一个变量。他们各自++互不干扰;
我们通过案来理解
这个栈帧是安全的,栈帧加载后创建出的局部变量,其他线程是访问不到的
这个局部变量是不安全的,它以形参方式传递进来,有可能会被其他线程访问并且修改掉数据(形参共享)
局部变量本身是安全的,但是被当成返回结果返回了,这就有可能被其他线程拿到这个结果来引用,并且并发的去修改它,这个时候就不再是安全的了。
栈内存溢出:
学习什么情况下导致 内存溢出
1.栈帧过多,栈内存是固定的
导致溢出
递归调用如果没有设置一个正确的计数条件,就会导致不停的产生栈帧。(在不明却递归条件的值是否安全是,避免使用递归)
2.栈帧过大!
基本不会出现这种情况,基本不存在,栈帧过大出现溢出,都是栈帧过多。
设置栈内存的大小:
1
2
3:日常开发中,第三方库也会导致 stackOverflowError
转json用OjectMapper的时候,部门包含员工,员工包含部门,形成一个递归映射的关系,导致现栈内存溢出。 (两个类之间的循环引用关系导致递归死循环)
解决方式:
在转json转的过程中打断这种关系,比如:
我转换员工的时候,遇到这个部门转换,我们就不转了;(把双向关系,打破转变成单项关系)
线程运行诊断:
案例1:cpu占用过多:!!!
定位线程(Linux)
一定要避开这个问题!!
案例2:程序运行很长时间没有结果:
Native Mthod Stacks (本地方法栈)
就是一些java基本构成的一些方法,他们没有具体返回值,native的方法声明,它的方法实现都是通过c来实现的, 我们的java代码通过这个native间接的去调用本地方法接口去调用c或从c++的实现,Object里面的方法基本都是。
Heap(堆)
通过new关键字,创建对象都会使用堆内存
特点:
*)它是线程共享的,堆中对象都要考虑线程安全的问题;
*)有垃圾回收机制
outOfMemoryError错误经典案例:
4G运行了26次空间不够
-Xmx8m堆内存空间设置(8m)
17次空间不够
堆内存诊断(Heap stacks)
jps工具:
查看当前系统中有那些Java进程
命令jps查看进程id
jmap工具:
查看堆内存占用清空(工具使用代码:jmap -heap)
jconsole工具
图形界面的,多功能监测工具,可以连续检测
(命令就是jconsole)
最好用的工具jvistualvm:
案例:垃圾回收后,内存占用仍然很高
堆赚储
Method Area(方法区):
CGlib动态代理的实现Spring,mybatis,ClassWriter。他们底层都实现了一个叫ClassVisitor的字节码接口。用于实现动态代理,
CGlib也有ClassWriter,他们都出自一个类,都是在运行期间动态生成类字节码。代理技术里面广泛引用到这个技术。
堆中方法区,定义永久代
元空间(现在使用操作操作 系统的分配)
1.6的时候存储在永久代,但是1.8之后永久代这个实现被废弃了,
但是方法区还是概念上的东西;
它的实现,变成了method space(元空间),不过它已经不占用堆内存了。
已经不是由jvm在管理,移存到本地内存去了(heap堆中);
线程共享区域,存储了一些类结构的信息;
field 成员变量;
method data 方法数据;
code for methods and consrructors
就是我们的成员方法以及构造器方法他们的代码 包括一些special method特殊方法;
还有运行时常量池:run-time-constant-pool(单独说)
方法区内存溢出:
1.8以前会导致永久代内存溢出
-XX:MaxMetaspaceSize=8m
永久代会被GC管理
这里主要演示的是元空间的内存溢出,元空间中是基于系统内存来分配的,跟物理运行内存有关;
public class PermGenOverflowTest extends ClassLoader{
@Test
public void permGenOverflow(){
int j=0;
try{
PermGenOverflowTest test = new PermGenOverflowTest();
for (int i = 0; i <10000; i++,j++) {
//ClassWriter 生成二进制字节码
ClassWriter writer = new ClassWriter(0);
//版本号 权限修饰符 类名 父类 接口
writer.visit(Opcodes.V1_8,Opcodes.ACC_PUBLIC,"Class"+i,null,"java/lang/Object",null);
byte [] code = writer.toByteArray();
test.defineClass("Class"+i,code,0, code.length);//只执行了类的加载
}
}finally {
System.out.println(j);
}
}
}
Metaspace空间导致的内存溢出
1.8之后会导致元空间内存溢出
这里介绍一个类:Clasloader类//用来加载类的二进制字节码
ClassWtier作用时生成类的二进制字节码
添加参数给元内存空间指定大小
动态生成场景
运行时常量池:
常量池中进行的赋值操作,程序计数器也是在这里
javap -c 类名.class
常量池与串池的关系:
常量池中的信息,都会被加载到运行时常量池中;这时都是常量池中的符号,还没
有变为java中的字符串对象;
类的组成
反编译一个类查看
javap -v App.class//反编译一个类,查看这个类信息
这个是时候,串池是这样的StringTable[]
串池:
是一个hashtable的结构,不能扩容
1.如果字符a在串池中没有,StringTable[“a”]中添加a,都是串池先找一圈;
2.懒惰的模式;执行到,用到它的时候才会去创建或查找它;
在串池中,相同数据(地址)只会存储一次
位置不一样new的在堆里面
所以这里是新存储了两条地址,里面有new操作
hash链表特性:相同值,相同地址认定为同一个数据,相同数据会
讲解:
这里说一下,intern操作是把当前对象(数据)放入串池(结果是true)
String a = new String("b")+new String("a");
String b= a.intern();
System.out.println(a==b);
这里讲解一下如果intern放入串池的内容存在不会再放入,返回原值
所以这里a是false b是true
String c = "ba";
String a = new String("b")+new String("a");
String b= a.intern();
System.out.println(a==c);
System.out.println(b==c);
1.8是直接将对象放入串池
主要是看水先入池
String a = new String("b")+new String("a");
a.intern();
String c = "ba";
System.out.println(a==c);
1.6是复制一份放入串池
这里是1.6测试:
String Tabele位置
确认位置通过报错这里先看1.6:
1.8的
关掉垃圾回收机制后得出报错是heap溢出
String Table 调优:
添加的垃圾回收的信息:会打印内存占用,如果有垃圾会收会打印GC
这里是一些类名啊,变量名啊,他们也是以字符串存储的;也属于常量池;SymbolTable
String Table 底层类似于hashtable的实现;是数组加链表的结构,数组的个数称为桶
默认桶个数60013(红黑树结构 )
这里没有引发垃圾回收机制因为100次还不足以引发但是可以看到串池的增长是一个百个
这里存一万个,发现只存了7000多
这里打印了一条由于内存空间分配失败触发了垃圾回收
GC垃圾回收的规则
就是我用了98%的经历来回收垃圾,但是只起到2%的作用,我就开始抛出异常不在处理
StringTable也是回受到垃圾回收机制的管理的, 当内存空间不足时,string table 那些 没有被引用的字符串常量,也会被回收;
演示StringTable的垃圾回收机制: