内存结构
1.程序计数器
作用:记录程序执行的下一条指令。
特点:
1.线程私有
cpu给这个线程非配一段时间,但是这段时间没有执行完,换下一个,程序计数器记下下一条指令。每个线程都有自己的程序计数器。
2.虚拟机栈
1.什么是栈?
线程运行时需要的内存空间。
内存空间中:
方法区:存储.class的相关信息(执行进栈,new进堆)
堆:new出来的东西
栈:存储方法中的局部变量
1.
栈由多个栈帧组成。每个线程,只能有一个活动栈帧,对应着正在执行的那个方法。
栈帧:每个方法运行时需要的内存。方法;(参数,局部变量,返回值)
2.动态过程:
数组和学对象那里都讲过了!!自己复习。
3.线程问题:
1.垃圾回收是否涉及栈内存??
不需要。方法执行完,自动出栈,不需要垃圾回收来管理。垃圾回收回收的是堆内存中的无用对象。
2.栈的内存是否越大越好?
默认1024kb
栈内存太大,线程数变少。因为物理内存大小是一定的。栈帧*线程数(之和)=总大小(个人理解)一般使用默认就够了。
3.方法中的局部变量是否线程安全??
看它是否被线程共享(static),
或者当做参数,又被下一个方法使用,
或者他被当做返回值返回了,返回就意味着可能会被其他线程拿到,可能就被共享了。
只有被共享时才会存在安全问题。
注意:判断是否安全,不仅要看他是否是局部变量,还要看他是否 逃离了方法的舒服范围。
4.栈内存溢出(StackOverflowError)
导致栈内存溢出的情况:
1.栈帧只进不出就会导致,比如递归,一定要有终止条件。
2.栈帧过大
3.对象转json时 对象的两个类之间循环引用(设置@JsonIgnore,转换时忽略某个属性,解决这个问题)
自己测试:
如何设置栈内存:
idea中——Edit—— -Xxs256kb
5.线程诊断
1.cpu占用太高:
估计代码写的有问题了(如何排查 查看错误的进程编号 Linux:top命令 ps命令查看线程对cpu的占有情况)jstack 进程id(列出详细线程信息 但是怎么用????)
2.程序运行很长时间没有结果:
可能多个线程发生了死锁。
什么是死锁(deadLock)????两个锁相互嵌套(互锁)。
3.本地方法栈
调用本地方法时,提供的内存。本地方法指不是由java编写的代码。由c或c++编写的更底层的方法。就需要给他提供这种环境,间接调用native,通过接口。(比如hashcode,notify,wait等都是native方法)
4.堆(Heap):共享
注意:前面三个都是现成私有的。后面这两个是共享的。那么就要考虑现成安全问题。
1.安全问题:
2.垃圾回收问题:空闲对象,及时回收。System.gc():垃圾回收
那么为什么还会出现堆内存溢出(OutOfMemoryError)?生产太多,而且一直被用着。比如list加东西,被放入了死循环中。一直用着,停不下来。
-Xmx8m测试(想测试问题,就把内存改小一点)
1.堆内存诊断
jps:查看当前系统有哪些进程
jmap:查看堆内存占用情况
jhsdb jmap --heap --pid 13540
jconsole:连续化监测工具
hhhhhh 有点好玩
案例:执行多次垃圾回收后,内存占用率依然很高。
使用jvisualvm工具查找
5.方法区:共享
所有线程共享的区。
1.内存溢出
1.8之前:永久代内存溢出(类+类加载器+常量池(SstringTable))
1.8之后:(MetaSpace)元空间内存溢出(类+类加载器+常量池)(与之前不同的是,StringTable不在常量池中了,在堆中)
元空间使用的是系统内存(操作系统)
加载到栈时,栈空间不够了,就也是内存溢出了(OutOfMemoryError)
框架里,一般都会用到一些技术(cglib),用到一些代理类,动态产生class字节码技术,一不小心很肯能发生这种错误。
2.常量池
反编译:javap -v ***.class
字节码文件(类基本信息+常量池+类定义信息,包含了虚拟机指令)
常量池:(就是一张表)提供常量符号,虚拟指令根据常量表,去查自己执行的类名,方法名,参数类型等。
运行时常量池: 常量池放在*。class文件中,当该类被加载,他的常量池信息,就会被放入运行时常量池,并把里面的符号地址变为真实地址。
3.StringTable(面试题)
//注意:常量池中的信息,都会被加载到运行时常量池中,这时"a","b","ab"嗾使常量池中的符号,当被使用时才会变成java对象
字符串延迟加载:
String s="ab";
String s1="a";
String s2="b";
String s3="a"+"b"; //常量拼接,值是不可改变的,在编译期的时候结果就能确定,所以在常量池中
String s4=s1+s2; //两个变量拼接,值有可能改变,所以必须动态拼接,查看底层。
//new StringBuilder.append("a").append("b").toString() 它的toString()方法里实际是 new String() 所以他存在在堆中
System.out.println(s==s3); //true
System.out.println(s==s4); //false
这里也要注意**==和equals的区别**:
1.8之后:
String s1=new String("a")+new String("b");
String s2 = s1.intern(); //把字符串对象放入串池中,如果串池中原本没有,
// 则返回串池中的对象;如果有,不会放入
System.out.println(s1=="ab"); //true
System.out.println(s2=="ab"); //true
1.6:
String s1=new String("a")+new String("b");
String s2 = s1.intern(); //1.6是代表把堆中的复制了一份,把字符串对象放入串池中,如果串池中原本没有,
// 则返回串池中的对象;如果有,不会放入
System.out.println(s1=="ab"); //false
System.out.println(s2=="ab"); //true
注意:1.s.intern()的位置很重要,池中没有,他就变成常量了,如果有,还是堆中的那个对象!!!!!
2.jdk1.6 1.8的区别:
3.左边是池中对象,右边是你自己入池不入池
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "a" + "b"; // ab
String s4 = s1 + s2; // new String("ab")
String s5 = "ab";
String s6 = s4.intern();
// 问
System.out.println(s3 == s4); // false
System.out.println(s3 == s5); // true
System.out.println(s3 == s6); // true
String x2 = new String("c") + new String("d"); // new String("cd")
x2.intern();
String x1 = "cd";
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
System.out.println(x1 == x2);
StringTable位置:
1.8后:Minor Gc: 触发回收,用的堆空间
1.6:PermGen(永久代)才触发回收 内存中用到常量池很多,这就有点晚了。很容易触发内存不足。
测试StringTable发生内存溢出的情况:
jdk8: -Xmx10m -XX:-UseGCOverheadLimit
-Xmx:堆内存
jdk6:-XX:MaxPermSize=10m
StringTable垃圾回收:
内存紧张时,会垃圾回收,一般都给放进去了。
演示:
-Xmx10m -XX:PrintStringTableStatistics -XX:PrintGCDetails -verbose:gc
字符串常量也会被垃圾回收
底层类似于HashTable——桶机制
StringtTable:性能调优
与哈希表中的桶的个数密切相关
StringTable调优:1:实际就是调整桶的个数 更好的哈希分布 减少哈希冲突;2.考虑字符串是否入池(intern() 方法来调优)
什么是哈希冲突??
如何解决哈希冲突??
-Xsx500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=200000
6.直接内存
不属于jvm,属于操作系统内存。那么他是否能够进行垃圾回收。
allocateDirect();可以回收。那么原理是什么呢??
有一个unsafe类:管理释放和回收直接内存,freeMemory()。
byteBuffer的关联——查看源码,里面有调用unsafe类
System.gc():显式垃圾回收。(回收新生代还有老年代,影响性能)
禁用显式回收对内存。(调优) 手动unsafe的freeMemory()释放。
-XX: +DisableExplictGC 禁用显式
- 常用于NIO操作,用户数据缓冲区(Buffer)
- 分配回收成本较高,但读写性能非常高
- 不受jvm内存回收管理
什么是NIO??????
为什么Buffer或直接内存效率这么高???
java自己没有读写方法,必须调用操作系统的。
java不能直接操作,要进行读写。
那么Direct Memory(直接内存,划分出一片区域,java也可以用),少了从缓冲区的复制操作。