Java SE API know how-JNI-异常-日志

Posted 卢延吉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java SE API know how-JNI-异常-日志相关的知识,希望对你有一定的参考价值。

Java SE API know how

Java原生接口 (Java Native Interface)

访问特殊的,操作系统特有的函数或者C/C++函数库就需要JNI

跨越JNI边界的成本很高,调用现有C库首先要编写胶水代码(健壮性难以保证)

如果涉及的参数类型不是基本类型的开销:

  1. 简单的引用也需要进行地址转换
  2. 对基于数组的数据,操作需要在原生代码中进行特殊处理。
  3. String对象本质上也是一个数组,访问数组中的元素必须执行特殊的调用,让对象固定在内存中,当不需要数组的时候必须显式释放

异常

根据良好的程序设计准则使用异常,主要是应该仅在发生意料之外的事情时抛出异常

影响异常处理的一般性能:

  1. 创建一个try-catch块成本很高
    jvm层面当异常块中的代码调用jvm会为该方法创建一个栈帧,将栈帧压入当前线程的虚拟机栈,在栈帧中,jvm会为try-catch分配一个异常表,记录try块中抛出的异常和对应的catch块的处理逻辑。当try块发生异常,jvm会根据异常类型在异常表中找到能够处理该异常的catch块,并将该程序跳转到catch块逻辑。如果没有在异常表找到处理逻辑的catch块,异常会向上抛出,上一级处理。
  2. 在异常点获取栈运行轨迹——当栈运行轨迹很长可想而知开销更大

100%命中异常的处理时间

  1. 受查的异常,浅层和深层的差异根本原因是栈轨迹的构造时间,这取决于栈的深度
  2. 非受查异常,JVM会在访问空指针的时候创建异常
    在某一时刻发生的事情编译器会优化系统生成异常。jvm会重用同一个对象而不是每次都是新的对象生成。
    每次执行相关代码,无论调用栈是什么样,该对象都会重用,减少构建站轨迹的时间,只有在完整栈信息的异常抛出相当长时间才会出现这种优化的现象
  3. 没有异常抛出的情况
    防御性编程做了相当多的工作来创建对象,对照其他例子的区别在于创建,抛出和捕获异常的实际时间

禁用栈轨迹:

-XX:-StackTraceInThrowable 默认true

影响代码的弊端导致栈轨迹无法分析异常造成的意外。

日志

  1. GC日志,该日志可以定向到一个单独的文件中,文件大小由JVM管理
  2. HTTP服务器生成访问日志,每当有请求时更新。对于服务器上运行的任何测试,关闭日志会提升性能。
  3. 应用程序日志:
    1.在日志数据和日志级别之间找到平衡
    2.使用细粒度的日志记录器,类粒度和一组类的粒度记录
    3.无作用代码中使用日志开关处理日志

读Java性能权威指南(第2版)笔记03_ Java SE API技巧中

1. 缓冲I/O

1.1. 对于文件和套接字,压缩和字符串编码的操作,必须适当地对I/O进行缓冲

1.1.1. 两个流操作的是字节块(来自缓冲流)而不是一系列的单字节(来自ObjectOutputStream),它们会运行得更好

1.2. InputStream.read()

1.3. OutputStream.write()

1.4. 操作的是单个字符

1.5. FileInputStream.read()

1.6. FileInputStream.write()

1.7. 

1.8. 二进制数据的文件I/O

1.8.1. BufferedInputStream或BufferedOutputStream来包装底层的文件流

1.9. 使用字符(字符串)数据的文件I/O

1.9.1. BufferedReader或BufferedWriter来包装底层的流

1.10. ByteArrayInputStream类和ByteArrayOutputStream类

1.10.1. 用缓冲过滤流包装它们,意味着数据会被复制两次

1.10.1.1. 被复制到过滤流的缓冲区

1.10.1.2. 被复制到ByteArrayInputStream的缓冲区

1.10.1.3. 输出流也是如此

1.10.2. 在没有其他流参与的时候,应该避免缓冲I/O

1.11. GZIPOutputStream

1.11.1. 操作数据块比操作单字节数据更高效

1.12. ObjectOutputStream

1.12.1. 将单字节数据发送到下一个流

1.12.2. 下一个流是最终目的地

1.12.2.1. ByteArrayOutputStream,则无须缓冲

1.12.2.2. 中间有另一个过滤流,如GZIPOutputStream,有必要缓冲

2. 随机数

2.1. java.util.Random

2.1.1. 主要操作(nextGaussian()方法)是同步的

2.1.2. 锁上都会产生竞争

2.2. java.util.concurrent.ThreadLocalRandom

2.2.1. 当每个线程都有自己的随机数生成器时,Random类的同步就不再是问题

2.3. 伪随机算法

2.3.1. 确定性的

2.3.1.1. 并不能真正做到随机

2.3.2. 通过特定生成器查看这个数字序列,并最终算出下一个数字会是什么

2.4. java.security.SecureRandom

2.4.1. 使用一个系统接口来为其随机数据获取种子

2.4.2. 提供的数据基于真正的随机事件(如鼠标的移动)

2.4.3. 基于熵的随机性(entropy-based randomness)

2.4.3.1. 更安全

2.4.4. generateSeed()方法花费的时间无法确定,这取决于系统有多少未使用的熵

2.4.4.1. 性能本身变成了随机

2.4.4.2. 更好的解决方案是设置操作系统,使其提供更多的熵,这可以通过运行rngd守护进程来实现

2.4.5. SecureRandom类的阻塞问题可以通过修改配置来避免,但最好在操作系统层面通过给系统增加熵来解决

3. 类数据共享

3.1. Java 11

3.2. class data sharing,CDS

3.2.1. JVM之间共享类元数据的一种机制

3.2.2. 可以缩短JVM的启动时间

3.3. 只适用于从模块或JAR文件加载的类,不能共享(或加速加载)来自文件系统或网络URL的类

3.4. 常规的CDS(共享默认的JDK类)

3.5. 应用程序类数据共享

3.5.1. 可以共享任何一组类

3.6. XX:+DumpLoadedClassList=filename标志来运行你的应用程序

3.6.1. 将(在filename文件中)生成一个列表,其中包含你的应用程序已经加载的所有类

3.7. 使用这个类列表来生成共享存档

$ java -Xshare:dump -XX:SharedClassListFile=filename \\
    -XX:SharedArchiveFile=myclasses.jsa \\
    ……类路径参数……

3.8. 使用共享存档来运行应用程序

$ java -Xshare:auto -XX:SharedArchiveFile=myclasses.jsa ……其他参数……

3.9. 要验证类是否从共享存档加载,可以在命令行加上类加载日志(-Xlog:class+load=info)命令

4. Java原生接口

4.1. (Java Native Interface,JNI)

4.2. 想要真正快速的代码,应该使用原生代码

4.3. 编写尽可能快的代码感兴趣,应该避免使用Java原生接口

4.4. 某个应用程序是用Java编写的,那么出于性能原因调用原生代码几乎总是一个坏主意

4.4.1. JNI并不能解决性能问题

4.4.2. Java代码几乎总是比调用原生代码运行得更快

4.5. 尽可能避免从Java到C的调用

4.5.1. 从C调用回Java不会有很大的性能损失(取决于所涉及的参数)

4.5.2. 当使用JNI时,要限制从Java到C的调用次数,跨越JNI边界的调用开销很大

4.6. 参数不是基本类型,那么JNI代码会表现得更差

4.7. 要让固定数组和字符串的时间尽可能短

4.7.1. 垃圾回收器才不会受到影响

以上是关于Java SE API know how-JNI-异常-日志的主要内容,如果未能解决你的问题,请参考以下文章

读Java性能权威指南(第2版)笔记02_ Java SE API技巧上

读Java性能权威指南(第2版)笔记03_ Java SE API技巧中

读Java性能权威指南(第2版)笔记04_ Java SE API技巧下

Java核心技术读书笔记12-Java SE 8引入的流

J2SE 的 API 包含有类和接口

Get to know Lambda and Functional Interfaces in Java 8