javac将源码编译为.class字节码文件。
javap java自带的反编译工具
javac test.java
javap -c test
Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。
为什么JVM不直接将源码解析成机器码去执行?
Java虚拟机
JVM屏蔽底层操作系统的不同,减少基于原生语言开发的复杂性。
JVM主要有Class Loader、RuntimData Area 、Execution Engine、Native Interface四部分组成,主要通过Class Loader将符合格式要求的文件,加载到内存中。并通过Execution Engine解析内部字节码,提交给操作系统执行。
待调用方法:
public class Robot {
private String name;
// 公共的方法
public void sayHi(String hello) {
System.out.println(hello + ":" + name);
}
// 私有化的方法
private String throwHello(String tag) {
return "Hello :" + tag;
}
}
调用:
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class robot = Class.forName("com.lydms.IO.Robot");
Robot r = (Robot) robot.newInstance();
System.out.println("Class name is" + robot.getName());
// 1、执行private方法throwHello,并接受返回值
Method throwHello = robot.getDeclaredMethod("throwHello", String.class);
// 将私有化方法设置为可见
throwHello.setAccessible(true);
// 执行方法,并传入参数,获取返回值
Object str = throwHello.invoke(r, "Bob");
System.out.println("Result is " + str);
// 2、执行publi方法sayHi,并指定name的值
Method sayHi = robot.getMethod("sayHi", String.class);
sayHi.invoke(r, "Welcome");
// 3、执行publi方法sayHi,并指定name的值
// 获取私有变量的对象
Field name = robot.getDeclaredField("name");
// 给私有变量设置可见
name.setAccessible(true);
name.set(r, "Alice");
sayHi.invoke(r, "Welcome");
}
}
类从编译到执行的过程
Classloader 在 Java 中有着非常重要的作用,它主要工作在 Class装载的加载阶段,其主要作用是从系统外部获得 Class二进制数据流。它是Java的核心组件,所有的Class都是由 Classloader进行加载的,Classloader负责通过将 Class文件里的二进制数据流装载进系统,然后交绐Java虚拟机进行连接、初始化等操作。
ClassLoader的种类
Extclassloader:(扩展库 Javax.*)
String property = System.getProperty("java.ext.dirs");
AppClassLoader:(加载程序所在目录)
String property = System.getProperty("java.class.path");
自定义ClassLoader的实现
关键函数
寻找class文件,读取二进制文件
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
定义类(将字节流转为class)
protected final Class<?> defineClass(byte[] b, int off, int len)throws ClassFormatError{
return defineClass(null, b, off, len, null);
}
根据名称/位置加载Class文件,调用defineClass去解析字节流
待执行文件:
public class Wali{
static{
System.out.println("Hello Wali");
}
}
自定义类加载器
import java.io.*;
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path, String classLoaderName) {
this.path = path;
this.classLoaderName = classLoaderName;
}
// 用于寻找类文件
@Override
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
// 用于加载类文件
private byte[] loadClassData(String name) {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
} catch (Exception e) {
e.printStackTrace();
}
return out.toByteArray();
}
}
调用类加载器
public class ClassLoaderChecker {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader m = new MyClassLoader("D:\\", "myClass");
Class<?> c = m.loadClass("Wali");
// 会调用自定义的findClass( )
ClassLoader classLoader = c.getClassLoader();
System.out.println(classLoader);
// 执行加载文件里的方法
c.newInstance();
}
}
类加载器的双亲委派机制
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1、首先,检查是否已经加载的类
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
2、没有的话,代用父类的加载器
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果类没有找到抛出ClassNotFoundException 没有父类加载
}
if (c == null) {
// 如果仍然没有找到,然后调用findClass去寻找class
long t1 = System.nanoTime();
c = findClass(name);
这是定义的类装入器;记录数据;
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
为什么要使用双亲委派机制去加载类?
没必要保存相同的类对象class。不使用委托机制,则每个类加载一次Class文件,内存中会有多份class文件。使用委托机制,自定义classLoader开始逐层向上查询是否装载,如果有则不再装载。保证内存中只有一份class文件。
类的装载过程
loadClass和forName的区别
package com.lydms.IO;
public class Robot {
static {
System.out.println("Hello Robot");
}
}
采用classLoader方式加载(未执行static方法)
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader = Robot.class.getClassLoader();
}
采用Class.forName( )方式加载(执行static中方法)
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.lydms.IO.Robot");
}
在程序执行的过程中,需要不断将内存的逻辑地址与物理地址进行映射,找到相关的指令以及数据去执行,java运行时面对着与其它进程完全相同的内存限制即受限于操作系统架构提供的可寻址地址空间,操作系统架构提供的可寻址地址空间由操作系统的位数决定,32位处理器提供了2^32的可寻址范围即4GB.
内核空间:内核是主要的操作系统程序和C运行时的空间,包含用于连接计算机硬件,调度程序,提供联网和虚拟内存等服务的逻辑和基于C的进程。
用户空间:用户空间是java进程实际运行时使用的内存空间,32位系统用户最多可以访问3GB,内核可以访问所有物理内存,而64位系统用户进程最大可以访问超过512gb,内核代码同样可以访问所有物理空间。
内存简介
地址空间的划分
Java内存模型(Runtime Data Area)
JVM内存模型—JDK8
程序计数器(Program Counter Register)
Java虚拟机栈(Stack)
局部变量表和操作数栈
public class ByteCodeSample{
public static int add(int a, int b){
int c=0;
c = a + b;
return c;
}
}
javap 反编译后
执行 add(1,2)
0:iconst_0:0压入操作数栈,1/2放入局部变量表
递归为什么会引发 java.lang StackOverflow Error 异常
当线程执行一个方法时,就创建一个栈针,并将创建的栈针压入虚拟机栈中,当方法执行完毕时,将栈针出栈。一直自己调用自己,不能释放栈针,递归过深,栈帧数超过虚拟栈深度,抛出StackOverflow Error异常。
递归过深,栈帧数超出虚拟栈深度。
虚拟机栈过多会引发 java.lang.OutofMemory Error(内存溢出)异常
栈内存不需要通过GC回收,就能够自动释放内存。
本地方法栈(Native Method Stack)
与虚拟机栈相似,主要作用于标注了 native的方法
元空间:JDK1.8后,类的元数据放在本地堆内存中,叫做元空间(MetaSpace)。这一块区域在JDK1.7以前属于永久代,元空间与永久代都是用来存储class的相关信息,包括class的method与field等,元空间与永久代都是方法区的实现,只是实现方法有所不同.所以说方法区只是JVM的规范,原先位于方法区的JVM常量池已被移动到堆中,并且在jdk8以后用元空间替代了永久代。
,
MetaSpace相比 Permgen的优势
Java堆(Heap)
java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,所有的对象实例都在这里分配内存.也是GC管理的主要区域。
32位处理器java进程的内存布局:
现在收集器基本都采用分代收集算法,所以java堆还可以分为新生代和老年代:
JVM三大性能调优参数-Xms-Xmx-Xss的含义
通常将-Xms与-Xmx写成一样的,避免内存抖动,不必再为扩展内存空间而消耗性能;
Java内存模型中堆和栈的区别->内存分配策略
程序运行时有三种内存分配策略,静态的,栈式的,堆式的。
Java内存模型中堆和栈的区别
区别:
元空间、堆、线程独占部分间的联系—内存角度
public class HelloWorld {
private String name;
public void getName() {
System.out.println("Hello" + name);
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) {
int a = 1;
HelloWorld hw = new HelloWorld();
hw.setName("Test");
hw.getName();
}
}
不同版本之间的intern( )方法的区别—JDK6 VS JDK6+
String s = new String( "a")
s .intern( );
JVM中的常量池从永久代中,移动到了堆中。
public class PermGenErrTest {
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
// 将返回的随机字符串添加到字符串常量池中
getRandomString(100000).intern();
}
System.out.println("Creat End");
}
// 返回指定长度的随机字符串
private static String getRandomString(int length) {
// 字符串源
String str = "asdfasdfiouwerwelsdfkjsafjiasfnsnfsdafsfs";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(20);
sb.append(str.charAt(number));
}
return sb.toString();
}
}
设置永久代的大小
-XX: MaxPermSize=6M 永久代最大为6M
-XX: PermSize=6M 初始时永久代大小
-XX:MaxDirectMemorySize 可以设置java堆外内存的峰值
JDK6(报出异常—永久代内存溢出)
JDK7+(正常执行)
public class Test {
public static void main(String[] args) {
// 存在堆中
String s = new String("a");
// 想放到常量池中,但是其中已经存在,放失败
s.intern();
// 常量池中
String s2="a";
System.out.println("s==s2: "+(s==s2));
// 拼接为"aa",在进行比较
String s3 = new String("a") + new String("a");
s3.intern();
String s4="aa";
System.out.println("s3==s4: "+(s3==s4));
}
}
输出结果:
JDK6中:
s==s2: false
s3==s4: false
JDK6+中:
s==s2: false
s3==s4: true
解析:
是在常量池中创建"a’,再在堆中创建"a"的空间。也就是占用2份内存空间。
String s = new String("a");
在堆中创建出"aa",并将"aa"放置到常量池中
String s3 = new String("a") + new String("a");
s3.intern();
s==s2: false
String s = new String("a");在堆中创建内存空间,s指的是堆内存地址。
String s2="a";引用的是常量池中的地址。
s3==s4: true
JDK6:中副本,指向的是常量池和堆中
JDK6+:常量池中可以放置引用。
生成的s3值"aa",存在于堆中,将其引用放在常量池中。
s4创建的时候,查看常量池中有"aa"的引用(指向堆中),则使用该引用。则S3==S4(true)
堆中"aa",常量池中有"aa"的引用。