java概念图描述:
1.内存溢出模拟:
往堆里无限存储对象,直到堆内存溢出
新建Demo类
public class Demo {
}
创建Main:
public class Main {
public static void main(String[] args) {
List<Demo> demoList=new ArrayList<>();
while(true) {
demoList.add(new Demo());
}
}
}
运行程序会发现:当运行内存达到6.3G时会报java堆空间问题:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
使用eclipse的工具:配置vm参数:堆存储快照
-XX:+HeapDumpOnOutOfMemoryError
指定vm的运行大小为20M
-XX:+HeapDumpOnOutOfMemoryError -Xms20m -Xmx20m
将堆存储成快照,并保存下来了:
Dumping heap to java_pid4820.hprof ...
可以通过eclipse的Memory Analyzer插件打开快照
(打开支配树,shallow heap对象本身所占内存,不包括引用对象,retained heap当前对象+引用对象,二者总的内存,垃圾回收就能释放这样多的内存)
进一步分析发现是由于demo类多次创建从而导致内存溢出:
java监视和管理控制台
jdk/bin/jConsole.exe实际依赖于lib/tools.jar包
内存池是垃圾回收器最喜欢光顾的地方
运行方式:
code(java语言编写的代码)-----》编译器------》class(字节码文件)-------》jvm(java虚拟机)
lamda表达式和函数式编程
类似 x -> y
x是函数的参数,y是关键的执行语句
jb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("click");
}
})
//简写成
//jb.addActionListener(event -> System.out.println("click"));
java内存管理:
1.程序计数器:
类似于图中的红色箭头:指向当前正在执行的代码行:
1.不过图中的123456的行号,如果是java方法,在计数器中记录的正在执行的虚拟机字节码指令的地址。 是native方法,计数器的值为undefined
2.此区域是唯一一个在java虚拟机规范中没有规定任何outOfMemoryError情况的区域
java虚拟机栈:(为执行java方法服务)
栈帧:
用于存储局部变量表,操作数栈,动态链接,方法出口等
每个方法都有栈帧,调用方法,栈帧进站,方法执行完后,栈帧就出站,然后被销毁、
(栈帧就是用来动态描述方法执行过程的)
局部变量表:
存放编译期可知的基本数据类型,引用类型,returnAddress类型。。(里面也存着对象的引用【对象句柄】)
局部变量表的内存空间在编译器完成分配,当进入一个方法时,这个方法需要在帧分配多少内存是固定的,在方法运行期间是不会改变局部变量表的大小
public class Stack {
private void tes() {
System.out.println("方法正在执行");
tes();
}
public static void main(String[] args) {
new Stack().tes();
}
}
会报StackOverFlowError或者OutOfMemory
本地方法栈(为虚拟机执行native方法服务的)
java堆:
1.1 存放对象实例的(内存管理最大的一块,对象都会放在这里)
1.2 垃圾收集器管理的主要区域
1.3 新生代,老年代 ,Eden空间
方法区
存储虚拟机加载的类信息【类的版本,字段,方法,接口】,常量,静态变量,即时编译器编译后的代码等数据
运行时常量池:
java中虽然没有指针,但引用就是引用了它的地址(开发者而言是隐式的,看不到),引用就相当于指针,它是对象的内存地址
字符串的创建会放到常量池里面(常量池是方法区的一部分)
s1的实例abc和s2的实例abc都会被丢到hashSet里面,有由于hashset是集合,无重复元素,故只有一个实例abc会被创建,所以s1和s2引用的都是同一个实例,因此s1==s2,new关键字创建的实例一定是在堆中的,字符串的实例是放在运行时常量池中的
public class Main {
public static void main(String[] args) {
String s1="abc";
String s2="abc";
String s3=new String("abc");
System.out.println(s1==s2); //true
System.out.println(s1==s3); //false
//intern()将堆中的常量移到运行时常量池中,它是原生本地的方法实现的
//public native String intern();
System.out.println(s1==s3.intern()); //true
}
}
直接内存(堆之外的内存,,比如new Input/Output 即NIO)
String a=new String("小明");
String a="小明";
String a=new String();
a="小明";
String a=new String(); a="小明";
这里小明是字符串对象a的实例,字符串对象的实例一般放在常量池中,这里的小明是放在常量池中的,a是对象的引用,存放在栈中
String a=new String("小明"); new出来的对象的实例是在堆中的,abc是字符串对象a的实例,但它是new出来的,所以放在堆中,
再看一个:
public class Main {
public static void main(String[] args) {
String s1="abc";
String s2="abc";
String s3=new String();
s3="abc";
System.out.println(s1==s2); //true
System.out.println(s1==s3); //false
//intern()将堆中的常量移到运行时常量池中,它是原生本地的方法实现的
//public native String intern();
System.out.println(s1==s3.intern()); //true
}
}
运行结果: