JVM内存模型详解
Java反射机制
在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
简而言之:就是把类里面的方法变量转化成
public void sayHi(String helloSententce) {
System.out.println(helloSententce + " " + name);
}
...
private String throwHello(String tag) {
return "hello " + tag;
}
//调用实现以上方法
Class<?> rc = Class.forName("com.czb.Robot");
System.out.println(rc.getName());
Robot r = (Robot) rc.getDeclaredConstructor().newInstance();
Method getHello =rc.getDeclaredMethod("throwHello", String.class);
getHello.setAccessible(true);
//invoke默认返回Object
Object str = getHello.invoke(r,"Bob");
System.out.println("getHelloresult is "+str);
Method sayHi = rc.getMethod("sayHi", String.class);
Field name = rc.getDeclaredField("name");
name.setAccessible(true);
name.set(r,"ALice");
sayHi.invoke(r,"Welcome");
getDeclaredMethod
来获取除了继承或实现的的所有方法
getMethod
只能获取public方法,包括继承或者实现的方法
ClassLoader的作用
通过将.class
文件的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。
1.BootstrapClassloader
:C++编写,加载核心库java.*
2.ExtClassLoader
:Java编写,加载扩展库javax.*
3.AppClassLoader
:Java编写,加载程序所在目录,加载路径(classpath)
4.自定义ClassLoader
:Java编写
为什么要使用双亲委派机制去加载类
避免多分同样的字节码的加载
例如:System静态Class字节码,只需要一份就可以了,第一次加载是在BootstrapClassLoader
隐式加载:new
显示加载:loadClass,forName
Java9之前newInstentce生成实例
Java9之后getDeclaredConstructor().newInstance()
▲类装载过程(比较loadClass和forName)
1.ClassLoader加载.class,生成Class对象,
加载到内存中,并将这些静态数据转化成运行时数据区中方法区的类型数据,在运行时,数据区堆中生成一个代表这个类的Java.lang.class对象作为方法区类数据的访问入口
2.链接:
校验:检查加载的class的正确性和安全性;
准备:为类变量分配存储空间并设置类变量初始值,类变量(static)随类型信息存放在方法区中,生命周期很长,使用不当容易造成内存泄漏
解析:JVM将常量池内的符号引用转换为直接引用(不一定非要解析)
3.初始化:执行类变量赋值和静态代码块
▲forName初始化完毕,loadClass只完成加载,还没有链接
loadClass可以快速加载配置文件;
如果你要连接Mysql,driver有一段static代码段,所以要用forName进行加载(因为静态代码段是第三步初始化里面完成的)
▲Java内存模型(JDK8)
私有内存区域
1.程序计数器(线程私有,不会内存溢出)
当前线程所执行的字节码行号指示器(逻辑)
通过改变计数器的值来选取下一条需要执行的字节码指令(分支、循环、跳转、异常处理、线程恢复)
对Java方法计数,如果是Native方法,计数器的值为Undefined
2.Java虚拟机栈(线程私有)
Java方法执行的内存模型
每个方法(字节码指令)执行时,都会创建一个栈帧(存储:局部变量表、操作数栈、动态连接、返回地址),栈帧持有局部变量和部分结果以及参与结果的调用和返回,方法调用结束时,栈帧就会被销毁
局部变量表:包含了方法执行过程中的所有变量
操作数栈:在执行字节码指令过程中,类似原生CPU寄存器,JVM字节码大部分时间都在操作数栈的操作上(入栈、出栈、复制、交换、产生消费变量),
3.本地方法栈
native方法
共享内存区域
1.元空间(MetaSpace)和永久代(PermGen)
两者都是来存储class的相关信息(Methon和Field),
▲‘两者均是方法区的实现,Java 8
之后元空间
替代了永久带
元空间用的是本地内存,永久带用的是JVM内存,也就是说本地内存多大,理论上元空间就可以有多大,这样就直接解决了空间不足的问题
●字符串常量池存在永久带
中,容易出现性能问题和内存溢出
●类和方法的信息大小难以确定,给永久带的大小指定带来困难
●永久带
会为GC带来不必要的复杂性,回收效率偏低
●方便HotSpot
与其他JVM
如Jrockit
的继承
Java堆(Heap)
存放对象实例
物理内存可以不连续
是GC管理的主要区域(GC堆,垃圾堆)
●方法区(No-Heap)存在堆里
JVM 三大性能调优参数-Xms, -Xmx, -Xss
-Xms:堆的初始值(不够会扩容,扩容时会内存抖动)
-Xmx:堆能到达的最大值
-Xss:规定每个线程虚拟机栈(及堆栈)的大小(一般情况256k就可以了,会影响并发线程数的大小)
Java内存中堆和栈的区别
静态存储:编译时确定每个数据目标在运行时的存储空间需求
栈式存储:数据区需求在编译的时候未知,运行时进入一个程序模块前必须知道其大小
堆式存储:编译时、运行时都不知道所需内存大小,动态分配
●存储
堆:创建好的对象和数组都会存到堆里面
栈:基本数据类型,引用对象、数组时,栈定义变量保存在堆中目标的首地址,局部变量,参数放在堆栈里面。
●管理方式
栈自动释放,堆需要GC
●空间
栈比堆小
●碎片相关
栈产生的碎片远小于小于堆(GC垃圾回收器不是实时的)
●分配方式
栈支持静态分配和动态分配,而堆只支持动态分配
●效率
栈比堆效率高,栈不够灵活
intern函数到jdk6+后的改变
如果先前在常量池已经创建了,则返回池中该字符串的引用。
(Java 7之后常量池从方法区(之前还是永久带
)移到了堆里面)
String s1 = new ("asasas")//
s1.intern();
String s2 = "asasas";
"asasas"
否则就看他在不在Java堆里面,如果有就添加到常量池并返回该引用,如果堆里面没有,就在池里创建并返回引用(多了一个创建)