深入分析JavaWeb内幕读书笔记(三)

javac编译原理简介

  1. 词法分析: 从源代码中找出一些规范化的ToKen流,就像在人类语言中分析动词、名词和标点符号。
  2. 语法分析: 检查Token流是否符合java语言规范,就像是人类语言中的语法,是否有主谓宾,主谓宾的结合是否正确,然后生成语法树。
  3. 语义分析: 将复杂的语法转化为最简单的语法,例如将foreach转化为for循环结构,就像将文言文转换为白话文,或是将成语进行注解。最后形成一个注解过后抽象语法树,这棵语法树更接近目标语言的语法规则。
  4. 经过以上三步之后通过代码生成器生成字节码,根据抽象语法树生成字节码,也就是将一个数据结构转化为另一个数据结构,就像将中文词语都翻译成英文单词后,按照英文语法组装成英文语句。

这个过程可以用下面的方式来表示:
源码 → Token流(词法分析器组件)→ 语法树(语法分析器组件)→ 注解语法树(语义分析器组件) → 字节码(代码生成器组件)

生成java字节码需要经过两个步骤:

  1. 将java方法中的代码块转化成符合jvm语法的命令形式,jvm的操作都是基于栈的,所有的操作都必须经过出栈和进栈来完成
  2. 按照jvm文件组织各市将字节码输出到以class为扩展名的文件中

类加载器

AppClassLoader,他的父类是ExtClassLoader。所有在System.getProperty(“java.class.path”)目录下的类都可以被这个类加载器加载,这个目录就是classpath;不论是实现classLoader类,还是继承URLClassloader类,或者其子类,他的父加载器都是AppClassLoader,因为创建的对象都必须最终调用getSystemClassLoader()作为父加载器,而该方法获取到的就是AppClassLoader;

在java应用中没有定义其他ClassLoader的情况下,除了System.getProperty(“java.ext.dirs”)目录下的类是由ExtClassLoader加载外,其他类都由AppClassLoader来加载:

隐式加载:不通过在代码里调用ClassLoader来加载需要的类,由jvm自动加载需要的类到内存;例如,当类中继承或引用某个类时,jvm解析当前类时发现引用的类不在内存中,就会自动将这些类加载到内存中

显式加载:通过调用ClassLoader来加载一个类;例如,调用this.getClass.getClassLoader.loadClass()或者Class.forName,或者我们自己实现的ClassLoader的findClass()方法等。

一般来说,这两种方式是混合使用的

通过this.getClass().getClassLoader.getResource("").toString();可以获得当前的classpath。

JVM内存管理

总线宽度决定了处理器一次可以从寄存器或者内存中获取多少位,也决定了处理器的最大寻址的地址空间;例如32位的总线可以寻址范围为0x0000 0000~0xffff ffff,这个范围是2的32次方个内存位置,每个地址会引用一个字节,所有32位总线宽度可以有4GB的内存空间;如果swap交互分区被频繁使用,则说明物理内存已经严重不足或某些程序没有及时释放内存;一旦通过-Xms等参数分配java堆的大小,则堆大小固定,当内存空闲时不会释放,不够时不会申请;如果PermGen(永久代)不能对已经失效的类卸载,则会导致内存泄漏;

一个类被卸载需要满足以下3个条件:

  1. 堆中没有对表示该类加载器的java.lang.ClassLoader对象的引用
  2. 堆中没有对表示该类加载器的类的任何java.lang.Class对象的引用
  3. 堆上该类加载器加载的任何类的所有对象都不再存活(被引用)

常量池是方法区的一部分,同样受方法区的规范约束

JAVA中使用内存的组件
  • JAVA堆
    堆用于存储Java对象的内存区域。它在JVM启动时向操作系统申请完成,通过-Xms(最大堆内存)和-Xms(初始堆内存)来控制大小。分配完成后,JVM不能在内存不够时向操作系统重新申请,也不能在空闲时,将多余内存空间交还给操作系统。也就是说,当内存分配完成后,虚拟机会锁定这部分内存作为自己的堆内存,而这部分内存逻辑上将不在被操作系统管理。堆中的内存空间管理由JVM控制,对象创建由Java应用程序控制,对象占用空间的释放由GC完成。

  • 线程
    JVM运行实际程序的实体是线程,每个线程在创建时JVM都会为其创建一个堆栈,大小根据JVM实现确定,通常在256kb~756kb之间。线程过多的情况下,线程堆栈的总内存使用量可能非常大。当前很多应用程序根据CPU核数来分配可创建的线程数,如果运行的应用程序的数据超出可用于处理他们的处理器数量多的话,效率会很低,并且可能导致性能变差以及更高的内存占用率。

  • 类和类加载器
    类和类加载器本身也需要存储空间,它们也被存储在JAVA堆中。由于这块存储空间特殊性,为了区分它与JAVA堆,因此,它也被称为非堆、方法区或永久代(PermGen区)。JVM只会加载在你应用程序中明确使用的类到内存中,可以通过添加虚拟机参数-verbose:class来查看JVM加载了哪些类。
    理论上,使用的Java类越多,占用的内存越多,还有一种情况就是重复加载同一个类。通常JVM只会加载一个类到内存一次,但如果是自己实现的类加载器可能会出现重复加载的情况,若永久代不能对已经失效的类卸载,此时可能会导致永久代内存泄漏。
    通常一个类能够被卸载,需满足以下条件:

    • 在Java堆中没有对表示该类加载器的java.lang.Classloader对象的引用(加载该类的ClassLoader被GC回收)
    • Java堆没有对表示类加载器加载的类的任何java.lang.Class对象的引用(该类的java.lang.Class对象没有被引用)
    • 在Java堆上该类加载器加载的任何类的所有对象都不再存活(所有实例都不被引用)
      注意:Bootstrap Classloader、ExtClassloader和AppClassloader都不可能满足这些条件,因此任何系统类(java.lang.String)或通过应用程序类加载器加载的任何应用程序类都不能在运行时释放。
  • NIO
    基于通道和缓冲区来执行I/O的方式(通常称为NIO direct memory),使用java.nio.ByteBuffer.allocateDirect()方法来分配内存。ByteBuffer.allocateDirect()分配的是并非Java堆上的内存,而是本机物理内存,每次分配时都会调用操作系统的os::malloc()函数。ByteBuffer产生的数据如果和网络或磁盘交互时,都在操作系统内核空间中发生,无需复制到Java内存中。其优点在于可以避免在Java堆与本机堆间复制数据。如果频繁发送小数据,那这种系统调用的开销可能会抵消它避免数据在堆之间复制所带来的好处。
    直接回收ByteBuffer对象会自动清理本机缓冲区,但这个过程只作为GC的一部分来执行,它不会自动响应施加在本机堆上的压力。GC仅在Java堆本填满时发生,因此不会在为堆分配请求提供服务或是在应用程序中显示请求时发生清理本机缓冲区的行为。许多NIO框架通过显式调用System.gc()来释放NIO持有的内存。但这种方式增加了GC的次数,导致应用程序性能下降。一般情况下通过设置-XX:DisableExplicitGC来控制System.gc()的影响,但又可能因此导致NIO内存泄漏。

  • JNI
    JNI使本机代码可以调用Java方法,Java运行时也依赖JNI代码实现文件操作、网络I/O或者其他系统调用的类库功能,因此JNI也会增加Java运行时的本机内存占用。


自己使用整理收集,如有侵权 请联系删除!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值